Merge "Fix the translation doesn't work" into udc-dev
diff --git a/Android.bp b/Android.bp
index bc6cda3..cff863b 100644
--- a/Android.bp
+++ b/Android.bp
@@ -343,6 +343,7 @@
             "hardware/interfaces/biometrics/fingerprint/aidl",
             "hardware/interfaces/graphics/common/aidl",
             "hardware/interfaces/keymaster/aidl",
+            "system/hardware/interfaces/media/aidl",
         ],
     },
     dxflags: [
@@ -632,6 +633,7 @@
             "hardware/interfaces/biometrics/fingerprint/aidl",
             "hardware/interfaces/graphics/common/aidl",
             "hardware/interfaces/keymaster/aidl",
+            "system/hardware/interfaces/media/aidl",
         ],
     },
     // These are libs from framework-internal-utils that are required (i.e. being referenced)
diff --git a/ApiDocs.bp b/ApiDocs.bp
index 90b6603..a46ecce 100644
--- a/ApiDocs.bp
+++ b/ApiDocs.bp
@@ -422,27 +422,26 @@
         "$(location merge_zips) $(out) $(location :ds-docs-java{.docs.zip}) $(genDir)/ds-docs-kt-moved.zip",
 }
 
-// Disable doc generation until Doclava is migrated to JDK 17 (b/240421555).
-// java_genrule {
-//     name: "ds-docs-switched",
-//     tools: [
-//         "switcher4",
-//         "soong_zip",
-//     ],
-//     srcs: [
-//         ":ds-docs-java{.docs.zip}",
-//         ":ds-docs-kt{.docs.zip}",
-//     ],
-//     out: ["ds-docs-switched.zip"],
-//     dist: {
-//         targets: ["docs"],
-//     },
-//     cmd: "unzip -q $(location :ds-docs-java{.docs.zip}) -d $(genDir) && " +
-//         "unzip -q $(location :ds-docs-kt{.docs.zip}) -d $(genDir)/en/reference/kotlin && " +
-//         "SWITCHER=$$(cd $$(dirname $(location switcher4)) && pwd)/$$(basename $(location switcher4)) && " +
-//         "(cd $(genDir)/en/reference && $$SWITCHER --work platform) > /dev/null && " +
-//         "$(location soong_zip) -o $(out) -C $(genDir) -D $(genDir)",
-// }
+java_genrule {
+    name: "ds-docs-switched",
+    tools: [
+        "switcher4",
+        "soong_zip",
+    ],
+    srcs: [
+        ":ds-docs-java{.docs.zip}",
+        ":ds-docs-kt{.docs.zip}",
+    ],
+    out: ["ds-docs-switched.zip"],
+    dist: {
+        targets: ["docs"],
+    },
+    cmd: "unzip -q $(location :ds-docs-java{.docs.zip}) -d $(genDir) && " +
+        "unzip -q $(location :ds-docs-kt{.docs.zip}) -d $(genDir)/en/reference/kotlin && " +
+        "SWITCHER=$$(cd $$(dirname $(location switcher4)) && pwd)/$$(basename $(location switcher4)) && " +
+        "(cd $(genDir)/en/reference && $$SWITCHER --work platform) > /dev/null && " +
+        "$(location soong_zip) -o $(out) -C $(genDir) -D $(genDir)",
+}
 
 droiddoc {
     name: "ds-static-docs",
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 1d93eb3..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 =
@@ -2625,7 +2625,7 @@
 
                 final Bundle mostRecentDeliveryOptions = BroadcastOptions.makeBasic()
                         .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
-                        .setDeferUntilActive(true)
+                        .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
                         .toBundle();
 
                 mIdleIntent = new Intent(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
@@ -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 1151bb7..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")
@@ -1948,7 +1949,7 @@
                             | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
             mTimeTickOptions = BroadcastOptions.makeBasic()
                     .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
-                    .setDeferUntilActive(true)
+                    .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
                     .toBundle();
             mTimeTickTrigger = new IAlarmListener.Stub() {
                 @Override
@@ -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 c1c44f0..02de1cd 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -224,6 +224,7 @@
     field @Deprecated public static final String PERSISTENT_ACTIVITY = "android.permission.PERSISTENT_ACTIVITY";
     field public static final String POST_NOTIFICATIONS = "android.permission.POST_NOTIFICATIONS";
     field @Deprecated public static final String PROCESS_OUTGOING_CALLS = "android.permission.PROCESS_OUTGOING_CALLS";
+    field public static final String PROVIDE_REMOTE_CREDENTIALS = "android.permission.PROVIDE_REMOTE_CREDENTIALS";
     field public static final String QUERY_ALL_PACKAGES = "android.permission.QUERY_ALL_PACKAGES";
     field public static final String READ_ASSISTANT_APP_SEARCH_DATA = "android.permission.READ_ASSISTANT_APP_SEARCH_DATA";
     field public static final String READ_BASIC_PHONE_STATE = "android.permission.READ_BASIC_PHONE_STATE";
@@ -11403,7 +11404,7 @@
   public class RestrictionsManager {
     method public static android.os.Bundle convertRestrictionsToBundle(java.util.List<android.content.RestrictionEntry>);
     method public android.content.Intent createLocalApprovalIntent();
-    method @Deprecated public android.os.Bundle getApplicationRestrictions();
+    method public android.os.Bundle getApplicationRestrictions();
     method @NonNull @WorkerThread public java.util.List<android.os.Bundle> getApplicationRestrictionsPerAdmin();
     method public java.util.List<android.content.RestrictionEntry> getManifestRestrictions(String);
     method public boolean hasRestrictionsProvider();
@@ -27345,7 +27346,9 @@
     field public static final int TIME_SHIFT_STATUS_UNAVAILABLE = 2; // 0x2
     field public static final int TIME_SHIFT_STATUS_UNKNOWN = 0; // 0x0
     field public static final int TIME_SHIFT_STATUS_UNSUPPORTED = 1; // 0x1
+    field public static final String TV_MESSAGE_KEY_RAW_DATA = "android.media.tv.TvInputManager.raw_data";
     field public static final String TV_MESSAGE_KEY_STREAM_ID = "android.media.tv.TvInputManager.stream_id";
+    field public static final String TV_MESSAGE_KEY_SUBTYPE = "android.media.tv.TvInputManager.subtype";
     field public static final int TV_MESSAGE_TYPE_CLOSED_CAPTION = 2; // 0x2
     field public static final int TV_MESSAGE_TYPE_OTHER = 1000; // 0x3e8
     field public static final int TV_MESSAGE_TYPE_WATERMARK = 1; // 0x1
@@ -33864,7 +33867,7 @@
 
   public class UserManager {
     method public static android.content.Intent createUserCreationIntent(@Nullable String, @Nullable String, @Nullable String, @Nullable android.os.PersistableBundle);
-    method @Deprecated @WorkerThread public android.os.Bundle getApplicationRestrictions(String);
+    method @WorkerThread public android.os.Bundle getApplicationRestrictions(String);
     method public long getSerialNumberForUser(android.os.UserHandle);
     method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.CREATE_USERS"}) public int getUserCount();
     method public long getUserCreationTime(android.os.UserHandle);
@@ -40591,7 +40594,7 @@
     method @NonNull public android.service.credentials.BeginCreateCredentialResponse.Builder addCreateEntry(@NonNull android.service.credentials.CreateEntry);
     method @NonNull public android.service.credentials.BeginCreateCredentialResponse build();
     method @NonNull public android.service.credentials.BeginCreateCredentialResponse.Builder setCreateEntries(@NonNull java.util.List<android.service.credentials.CreateEntry>);
-    method @NonNull @RequiresPermission("android.permission.PROVIDE_REMOTE_CREDENTIALS") public android.service.credentials.BeginCreateCredentialResponse.Builder setRemoteCreateEntry(@Nullable android.service.credentials.RemoteEntry);
+    method @NonNull @RequiresPermission(android.Manifest.permission.PROVIDE_REMOTE_CREDENTIALS) public android.service.credentials.BeginCreateCredentialResponse.Builder setRemoteCreateEntry(@Nullable android.service.credentials.RemoteEntry);
   }
 
   public class BeginGetCredentialOption implements android.os.Parcelable {
@@ -40640,7 +40643,7 @@
     method @NonNull public android.service.credentials.BeginGetCredentialResponse.Builder setActions(@NonNull java.util.List<android.service.credentials.Action>);
     method @NonNull public android.service.credentials.BeginGetCredentialResponse.Builder setAuthenticationActions(@NonNull java.util.List<android.service.credentials.Action>);
     method @NonNull public android.service.credentials.BeginGetCredentialResponse.Builder setCredentialEntries(@NonNull java.util.List<android.service.credentials.CredentialEntry>);
-    method @NonNull @RequiresPermission("android.permission.PROVIDE_REMOTE_CREDENTIALS") public android.service.credentials.BeginGetCredentialResponse.Builder setRemoteCredentialEntry(@Nullable android.service.credentials.RemoteEntry);
+    method @NonNull @RequiresPermission(android.Manifest.permission.PROVIDE_REMOTE_CREDENTIALS) public android.service.credentials.BeginGetCredentialResponse.Builder setRemoteCredentialEntry(@Nullable android.service.credentials.RemoteEntry);
   }
 
   public final class CallingAppInfo implements android.os.Parcelable {
@@ -41558,7 +41561,6 @@
 
   public abstract class RecognitionService extends android.app.Service {
     ctor public RecognitionService();
-    method public void clearModelDownloadListener(@NonNull android.content.Intent, @NonNull android.content.AttributionSource);
     method public int getMaxConcurrentSessionsCount();
     method public final android.os.IBinder onBind(android.content.Intent);
     method protected abstract void onCancel(android.speech.RecognitionService.Callback);
@@ -41568,7 +41570,7 @@
     method protected abstract void onStopListening(android.speech.RecognitionService.Callback);
     method public void onTriggerModelDownload(@NonNull android.content.Intent);
     method public void onTriggerModelDownload(@NonNull android.content.Intent, @NonNull android.content.AttributionSource);
-    method public void setModelDownloadListener(@NonNull android.content.Intent, @NonNull android.content.AttributionSource, @NonNull android.speech.ModelDownloadListener);
+    method public void onTriggerModelDownload(@NonNull android.content.Intent, @NonNull android.content.AttributionSource, @NonNull android.speech.ModelDownloadListener);
     field public static final String SERVICE_INTERFACE = "android.speech.RecognitionService";
     field public static final String SERVICE_META_DATA = "android.speech";
   }
@@ -41693,18 +41695,17 @@
   public class SpeechRecognizer {
     method @MainThread public void cancel();
     method public void checkRecognitionSupport(@NonNull android.content.Intent, @NonNull java.util.concurrent.Executor, @NonNull android.speech.RecognitionSupportCallback);
-    method public void clearModelDownloadListener(@NonNull android.content.Intent);
     method @MainThread @NonNull public static android.speech.SpeechRecognizer createOnDeviceSpeechRecognizer(@NonNull android.content.Context);
     method @MainThread public static android.speech.SpeechRecognizer createSpeechRecognizer(android.content.Context);
     method @MainThread public static android.speech.SpeechRecognizer createSpeechRecognizer(android.content.Context, android.content.ComponentName);
     method public void destroy();
     method public static boolean isOnDeviceRecognitionAvailable(@NonNull android.content.Context);
     method public static boolean isRecognitionAvailable(@NonNull android.content.Context);
-    method public void setModelDownloadListener(@NonNull android.content.Intent, @NonNull java.util.concurrent.Executor, @NonNull android.speech.ModelDownloadListener);
     method @MainThread public void setRecognitionListener(android.speech.RecognitionListener);
     method @MainThread public void startListening(android.content.Intent);
     method @MainThread public void stopListening();
     method public void triggerModelDownload(@NonNull android.content.Intent);
+    method public void triggerModelDownload(@NonNull android.content.Intent, @NonNull java.util.concurrent.Executor, @NonNull android.speech.ModelDownloadListener);
     field public static final String CONFIDENCE_SCORES = "confidence_scores";
     field public static final String DETECTED_LANGUAGE = "detected_language";
     field public static final int ERROR_AUDIO = 3; // 0x3
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index d573e33..0a893f0 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -255,7 +255,6 @@
     field public static final String PERFORM_SIM_ACTIVATION = "android.permission.PERFORM_SIM_ACTIVATION";
     field public static final String POWER_SAVER = "android.permission.POWER_SAVER";
     field public static final String PROVIDE_DEFAULT_ENABLED_CREDENTIAL_SERVICE = "android.permission.PROVIDE_DEFAULT_ENABLED_CREDENTIAL_SERVICE";
-    field public static final String PROVIDE_REMOTE_CREDENTIALS = "android.permission.PROVIDE_REMOTE_CREDENTIALS";
     field public static final String PROVIDE_RESOLVER_RANKER_SERVICE = "android.permission.PROVIDE_RESOLVER_RANKER_SERVICE";
     field public static final String PROVIDE_TRUST_AGENT = "android.permission.PROVIDE_TRUST_AGENT";
     field public static final String PROVISION_DEMO_DEVICE = "android.permission.PROVISION_DEMO_DEVICE";
@@ -10135,16 +10134,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);
   }
 
 }
@@ -12994,20 +12995,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
@@ -13186,10 +13187,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 {
@@ -13203,9 +13204,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();
@@ -13273,9 +13271,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 0123cc9..445b957 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2027,7 +2027,9 @@
     method public static boolean is64BitAbi(String);
     method public static boolean isDebuggable();
     field @Nullable public static final String BRAND_FOR_ATTESTATION;
+    field @Nullable public static final String DEVICE_FOR_ATTESTATION;
     field public static final boolean IS_EMULATOR;
+    field @Nullable public static final String MANUFACTURER_FOR_ATTESTATION;
     field @Nullable public static final String MODEL_FOR_ATTESTATION;
     field @Nullable public static final String PRODUCT_FOR_ATTESTATION;
   }
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 87643b2..2751b54 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -849,8 +849,10 @@
         @UnsupportedAppUsage
         Intent intent;
         boolean rebind;
+        long bindSeq;
         public String toString() {
-            return "BindServiceData{token=" + token + " intent=" + intent + "}";
+            return "BindServiceData{token=" + token + " intent=" + intent
+                    + " bindSeq=" + bindSeq + "}";
         }
     }
 
@@ -1107,12 +1109,13 @@
         }
 
         public final void scheduleBindService(IBinder token, Intent intent,
-                boolean rebind, int processState) {
+                boolean rebind, int processState, long bindSeq) {
             updateProcessState(processState, false);
             BindServiceData s = new BindServiceData();
             s.token = token;
             s.intent = intent;
             s.rebind = rebind;
+            s.bindSeq = bindSeq;
 
             if (DEBUG_SERVICE)
                 Slog.v(TAG, "scheduleBindService token=" + token + " intent=" + intent + " uid="
@@ -1124,6 +1127,7 @@
             BindServiceData s = new BindServiceData();
             s.token = token;
             s.intent = intent;
+            s.bindSeq = -1;
 
             sendMessage(H.UNBIND_SERVICE, s);
         }
@@ -6653,12 +6657,13 @@
             // Setup a location to store generated/compiled graphics code.
             final Context deviceContext = context.createDeviceProtectedStorageContext();
             final File codeCacheDir = deviceContext.getCodeCacheDir();
-            if (codeCacheDir != null) {
+            final File deviceCacheDir = deviceContext.getCacheDir();
+            if (codeCacheDir != null && deviceCacheDir != null) {
                 try {
                     int uid = Process.myUid();
                     String[] packages = getPackageManager().getPackagesForUid(uid);
                     if (packages != null) {
-                        HardwareRenderer.setupDiskCache(codeCacheDir);
+                        HardwareRenderer.setupDiskCache(deviceCacheDir);
                         RenderScriptCacheDir.setupDiskCache(codeCacheDir);
                     }
                 } catch (RemoteException e) {
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/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 4f77203..6b5f6b0 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -95,7 +95,7 @@
     void processInBackground();
     @UnsupportedAppUsage
     void scheduleBindService(IBinder token,
-            in Intent intent, boolean rebind, int processState);
+            in Intent intent, boolean rebind, int processState, long bindSeq);
     @UnsupportedAppUsage
     void scheduleUnbindService(IBinder token,
             in Intent intent);
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 0ef8e92..09450f5 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -155,6 +155,14 @@
             "android.app.extra.REMOTE_LOCKSCREEN_VALIDATION_SESSION";
 
     /**
+     * A boolean indicating that credential confirmation activity should be a task overlay.
+     * {@link #ACTION_CONFIRM_DEVICE_CREDENTIAL_WITH_USER}.
+     * @hide
+     */
+    public static final String EXTRA_FORCE_TASK_OVERLAY =
+            "android.app.KeyguardManager.FORCE_TASK_OVERLAY";
+
+    /**
      * Result code returned by the activity started by
      * {@link #createConfirmFactoryResetCredentialIntent} or
      * {@link #createConfirmDeviceCredentialForRemoteValidationIntent}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 440ee20..e78fb17 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -8657,13 +8657,13 @@
              * where the platform doesn't support the MIME type, the original text provided in the
              * constructor will be used.
              * @param dataMimeType The MIME type of the content. See
-             * <a href="{@docRoot}notifications/messaging.html"> for the list of supported MIME
-             * types on Android and Android Wear.
+             * {@link android.graphics.ImageDecoder#isMimeTypeSupported(String)} for a list of
+             * supported image MIME types.
              * @param dataUri The uri containing the content whose type is given by the MIME type.
              * <p class="note">
+             * Notification Listeners including the System UI need permission to access the
+             * data the Uri points to. The recommended ways to do this are:
              * <ol>
-             *   <li>Notification Listeners including the System UI need permission to access the
-             *       data the Uri points to. The recommended ways to do this are:</li>
              *   <li>Store the data in your own ContentProvider, making sure that other apps have
              *       the correct permission to access your provider. The preferred mechanism for
              *       providing access is to use per-URI permissions which are temporary and only
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/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java
index 39d77c4..5b95503 100644
--- a/core/java/android/app/WindowConfiguration.java
+++ b/core/java/android/app/WindowConfiguration.java
@@ -875,15 +875,6 @@
     }
 
     /**
-     * Returns true if any visible windows belonging to apps with this window configuration should
-     * be kept on screen when the app is killed due to something like the low memory killer.
-     * @hide
-     */
-    public boolean keepVisibleDeadAppWindowOnScreen() {
-        return mWindowingMode != WINDOWING_MODE_PINNED;
-    }
-
-    /**
      * Returns true if the backdrop on the client side should match the frame of the window.
      * Returns false, if the backdrop should be fullscreen.
      * @hide
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 85daf15..667ec7e 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -5920,9 +5920,8 @@
 
     /**
      * Optional argument to be used with {@link #ACTION_CHOOSER}.
-     * A {@link android.app.PendingIntent} to be sent when the user wants to modify the content that
-     * they're sharing. This can be used to allow the user to return to the source app to, for
-     * example, select different media.
+     * A {@link ChooserAction} to allow the user to modify what is being shared in some way. This
+     * may be integrated into the content preview on sharesheets that have a preview UI.
      */
     public static final String EXTRA_CHOOSER_MODIFY_SHARE_ACTION =
             "android.intent.extra.CHOOSER_MODIFY_SHARE_ACTION";
diff --git a/core/java/android/content/RestrictionsManager.java b/core/java/android/content/RestrictionsManager.java
index 8115292..44a84e4 100644
--- a/core/java/android/content/RestrictionsManager.java
+++ b/core/java/android/content/RestrictionsManager.java
@@ -427,11 +427,12 @@
      * @return the application restrictions as a Bundle. Returns null if there
      * are no restrictions.
      *
-     * @deprecated Use {@link #getApplicationRestrictionsPerAdmin} instead.
-     * Starting from Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, it is
-     * possible for there to be multiple managing agents on the device with the ability to set
-     * restrictions. This API will only to return the restrictions set by device policy controllers
-     * (DPCs)
+     * <p>Starting from Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+     * it is possible for there to be multiple managing apps on the device with the ability to set
+     * restrictions, e.g. a Device Policy Controller (DPC) and a Supervision admin.
+     * This API will only return the restrictions set by the DPCs. To retrieve restrictions
+     * set by all managing apps, use
+     * {@link android.content.RestrictionsManager#getApplicationRestrictionsPerAdmin} instead.
      *
      * @see DevicePolicyManager
      */
@@ -453,8 +454,8 @@
      * stable between multiple calls.
      *
      * <p>Starting from Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
-     * it is possible for there to be multiple managing agents on the device with the ability to set
-     * restrictions, e.g. an Enterprise DPC and a Supervision admin.
+     * it is possible for there to be multiple managing apps on the device with the ability to set
+     * restrictions, e.g. an Enterprise Device Policy Controller (DPC) and a Supervision admin.
      *
      * <p>Each {@link Bundle} consists of key-value pairs, as defined by the application,
      * where the types of values may be:
@@ -471,6 +472,7 @@
      * package. Returns an empty {@link List} if there are no saved restrictions.
      *
      * @see UserManager#KEY_RESTRICTIONS_PENDING
+     * @see DevicePolicyManager
      */
     @WorkerThread
     @UserHandleAware
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/IncrementalStatesInfo.java b/core/java/android/content/pm/IncrementalStatesInfo.java
index 0393d34b..f15afdf 100644
--- a/core/java/android/content/pm/IncrementalStatesInfo.java
+++ b/core/java/android/content/pm/IncrementalStatesInfo.java
@@ -27,14 +27,18 @@
     private boolean mIsLoading;
     private float mProgress;
 
-    public IncrementalStatesInfo(boolean isLoading, float progress) {
+    private long mLoadingCompletedTime;
+
+    public IncrementalStatesInfo(boolean isLoading, float progress, long loadingCompletedTime) {
         mIsLoading = isLoading;
         mProgress = progress;
+        mLoadingCompletedTime = loadingCompletedTime;
     }
 
     private IncrementalStatesInfo(Parcel source) {
         mIsLoading = source.readBoolean();
         mProgress = source.readFloat();
+        mLoadingCompletedTime = source.readLong();
     }
 
     public boolean isLoading() {
@@ -45,6 +49,10 @@
         return mProgress;
     }
 
+    public long getLoadingCompletedTime() {
+        return mLoadingCompletedTime;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -54,6 +62,7 @@
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeBoolean(mIsLoading);
         dest.writeFloat(mProgress);
+        dest.writeLong(mLoadingCompletedTime);
     }
 
     public static final @android.annotation.NonNull Creator<IncrementalStatesInfo> CREATOR =
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/credentials/CredentialDescription.java b/core/java/android/credentials/CredentialDescription.java
index bf34c1c..a23d7e4 100644
--- a/core/java/android/credentials/CredentialDescription.java
+++ b/core/java/android/credentials/CredentialDescription.java
@@ -42,7 +42,7 @@
     private final String mType;
 
     /**
-     * The flattened JSON string that will be matched with requests.
+     * Flattened semicolon separated keys of JSON values to match with requests.
      */
     @NonNull
     private final String mFlattenedRequestString;
@@ -57,7 +57,8 @@
      * Constructs a {@link CredentialDescription}.
      *
      * @param type the type of the credential returned.
-     * @param flattenedRequestString flattened JSON string that will be matched with requests.
+     * @param flattenedRequestString flattened semicolon separated keys of JSON values
+     *                              to match with requests.
      * @param credentialEntries a list of {@link CredentialEntry}s that are to be shown on the
      *                          account selector if a credential matches with this description.
      *                          Each entry contains information to be displayed within an
@@ -151,4 +152,29 @@
     public List<CredentialEntry> getCredentialEntries() {
         return mCredentialEntries;
     }
+
+    /**
+     * {@link CredentialDescription#mType} and
+     * {@link CredentialDescription#mFlattenedRequestString} are enough for hashing. Constructor
+     * enforces {@link CredentialEntry} to have the same type and
+     * {@link android.app.slice.Slice} contained by the entry can not be hashed.
+     */
+    @Override
+    public int hashCode() {
+        return Objects.hash(mType, mFlattenedRequestString);
+    }
+
+    /**
+     * {@link CredentialDescription#mType} and
+     * {@link CredentialDescription#mFlattenedRequestString} are enough for equality check.
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof CredentialDescription)) {
+            return false;
+        }
+        CredentialDescription other = (CredentialDescription) obj;
+        return mType.equals(other.mType)
+                && mFlattenedRequestString.equals(other.mFlattenedRequestString);
+    }
 }
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/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index b9b310f..c95d081 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -4123,7 +4123,8 @@
      * counterparts.
      * This key will only be present for devices which advertise the
      * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
-     * capability.</p>
+     * capability or devices where {@link CameraCharacteristics#getAvailableCaptureRequestKeys }
+     * lists {@link CaptureRequest#SENSOR_PIXEL_MODE {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode}}</p>
      * <p><b>Units</b>: Pixel coordinates on the image sensor</p>
      * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
      *
@@ -4148,7 +4149,8 @@
      * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.
      * This key will only be present for devices which advertise the
      * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
-     * capability.</p>
+     * capability or devices where {@link CameraCharacteristics#getAvailableCaptureRequestKeys }
+     * lists {@link CaptureRequest#SENSOR_PIXEL_MODE {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode}}</p>
      * <p><b>Units</b>: Pixels</p>
      * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
      *
@@ -4172,7 +4174,8 @@
      * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.
      * This key will only be present for devices which advertise the
      * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
-     * capability.</p>
+     * capability or devices where {@link CameraCharacteristics#getAvailableCaptureRequestKeys }
+     * lists {@link CaptureRequest#SENSOR_PIXEL_MODE {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode}}</p>
      * <p><b>Units</b>: Pixel coordinates on the image sensor</p>
      * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
      *
@@ -4192,14 +4195,29 @@
      * to improve various aspects of imaging such as noise reduction, low light
      * performance etc. These groups can be of various sizes such as 2X2 (quad bayer),
      * 3X3 (nona-bayer). This key specifies the length and width of the pixels grouped under
-     * the same color filter.</p>
-     * <p>This key will not be present if REMOSAIC_REPROCESSING is not supported, since RAW images
-     * will have a regular bayer pattern.</p>
-     * <p>This key will not be present for sensors which don't have the
-     * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
-     * capability.</p>
+     * the same color filter.
+     * In case the device has the
+     * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
+     * capability :</p>
+     * <ul>
+     * <li>This key will not be present if REMOSAIC_REPROCESSING is not supported, since RAW
+     *   images will have a regular bayer pattern.</li>
+     * </ul>
+     * <p>In case the device does not have the
+     * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
+     * capability :</p>
+     * <ul>
+     * <li>This key will be present if
+     *   {@link CameraCharacteristics#getAvailableCaptureRequestKeys }
+     *   lists {@link CaptureRequest#SENSOR_PIXEL_MODE {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode}}, since RAW
+     *   images may not necessarily have a regular bayer pattern when
+     *   {@link CaptureRequest#SENSOR_PIXEL_MODE {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode}} is set to
+     *   {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</li>
+     * </ul>
      * <p><b>Units</b>: Pixels</p>
      * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     *
+     * @see CaptureRequest#SENSOR_PIXEL_MODE
      */
     @PublicKey
     @NonNull
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 722dd08..e6b3069 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -125,6 +125,23 @@
             "camera.enable_landscape_to_portrait";
 
     /**
+     * Enable physical camera availability callbacks when the logical camera is unavailable
+     *
+     * <p>Previously once a logical camera becomes unavailable, no {@link
+     * #onPhysicalCameraAvailable} or {@link #onPhysicalCameraUnavailable} will be called until
+     * the logical camera becomes available again. The results in the app opening the logical
+     * camera not able to receive physical camera availability change.</p>
+     *
+     * <p>With this change, the {@link #onPhysicalCameraAvailable} and {@link
+     * #onPhysicalCameraUnavailable} can still be called while the logical camera is unavailable.
+     * </p>
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    private static final long ENABLE_PHYSICAL_CAMERA_CALLBACK_FOR_UNAVAILABLE_LOGICAL_CAMERA =
+            244358506L;
+
+    /**
      * @hide
      */
     public CameraManager(Context context) {
@@ -1194,6 +1211,14 @@
     }
 
     /**
+     * @hide
+     */
+    public static boolean physicalCallbacksAreEnabledForUnavailableCamera() {
+        return CompatChanges.isChangeEnabled(
+                ENABLE_PHYSICAL_CAMERA_CALLBACK_FOR_UNAVAILABLE_LOGICAL_CAMERA);
+    }
+
+    /**
      * A callback for camera devices becoming available or unavailable to open.
      *
      * <p>Cameras become available when they are no longer in use, or when a new
@@ -1270,9 +1295,10 @@
          * to begin with, {@link #onPhysicalCameraUnavailable} may be invoked after
          * {@link #onCameraAvailable}.</p>
          *
-         * <p>Limitation: Opening a logical camera disables the {@link #onPhysicalCameraAvailable}
-         * and {@link #onPhysicalCameraUnavailable} callbacks for its physical cameras. For example,
-         * if app A opens the camera device:</p>
+         * <p>If {@link android.content.pm.ApplicationInfo#targetSdkVersion targetSdkVersion}
+         * &lt; {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, opening a logical camera
+         * disables the {@link #onPhysicalCameraAvailable} and {@link #onPhysicalCameraUnavailable}
+         * callbacks for its physical cameras. For example, if app A opens the camera device:</p>
          *
          * <ul>
          *
@@ -1284,6 +1310,33 @@
          *
          * </ul>
          *
+         * <p>If {@link android.content.pm.ApplicationInfo#targetSdkVersion targetSdkVersion}
+         * &ge; {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}:</p>
+         *
+         * <ul>
+         *
+         * <li>A physical camera status change will trigger {@link #onPhysicalCameraAvailable}
+         * or {@link #onPhysicalCameraUnavailable} even after the logical camera becomes
+         * unavailable. A {@link #onCameraUnavailable} call for a logical camera doesn't reset the
+         * physical cameras' availability status. This makes it possible for an application opening
+         * the logical camera device to know which physical camera becomes unavailable or available
+         * to use.</li>
+         *
+         * <li>Similar to {@link android.os.Build.VERSION_CODES#TIRAMISU Android 13} and earlier,
+         * the logical camera's {@link #onCameraAvailable} callback implies all of its physical
+         * cameras' status become available. {@link #onPhysicalCameraUnavailable} will be called
+         * for any unavailable physical cameras upon the logical camera becoming available.</li>
+         *
+         * </ul>
+         *
+         * <p>Given the pipeline nature of the camera capture through {@link
+         * android.hardware.camera2.CaptureRequest}, there may be frame drops if the application
+         * requests images from a physical camera of a logical multi-camera and that physical camera
+         * becomes unavailable. The application should stop requesting directly from an unavailable
+         * physical camera as soon as {@link #onPhysicalCameraUnavailable} is received, and also be
+         * ready to robustly handle frame drop errors for requests targeting physical cameras,
+         * since those errors may arrive before the unavailability callback.</p>
+         *
          * <p>The default implementation of this method does nothing.</p>
          *
          * @param cameraId The unique identifier of the logical multi-camera.
@@ -1306,9 +1359,10 @@
          * cameras of its parent logical multi-camera, when {@link #onCameraUnavailable} for
          * the logical multi-camera is invoked.</p>
          *
-         * <p>Limitation: Opening a logical camera disables the {@link #onPhysicalCameraAvailable}
-         * and {@link #onPhysicalCameraUnavailable} callbacks for its physical cameras. For example,
-         * if app A opens the camera device:</p>
+         * <p>If {@link android.content.pm.ApplicationInfo#targetSdkVersion targetSdkVersion}
+         * &lt; {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, opening a logical camera
+         * disables the {@link #onPhysicalCameraAvailable} and {@link #onPhysicalCameraUnavailable}
+         * callbacks for its physical cameras. For example, if app A opens the camera device:</p>
          *
          * <ul>
          *
@@ -1320,6 +1374,33 @@
          *
          * </ul>
          *
+         * <p>If {@link android.content.pm.ApplicationInfo#targetSdkVersion targetSdkVersion}
+         * &ge; {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}:</p>
+         *
+         * <ul>
+         *
+         * <li>A physical camera status change will trigger {@link #onPhysicalCameraAvailable}
+         * or {@link #onPhysicalCameraUnavailable} even after the logical camera becomes
+         * unavailable. A {@link #onCameraUnavailable} call for a logical camera doesn't reset the
+         * physical cameras' availability status. This makes it possible for an application opening
+         * the logical camera device to know which physical camera becomes unavailable or available
+         * to use.</li>
+         *
+         * <li>Similar to {@link android.os.Build.VERSION_CODES#TIRAMISU Android 13} and earlier,
+         * the logical camera's {@link #onCameraAvailable} callback implies all of its physical
+         * cameras' status become available. {@link #onPhysicalCameraUnavailable} will be called
+         * for any unavailable physical cameras upon the logical camera becoming available.</li>
+         *
+         * </ul>
+         *
+         * <p>Given the pipeline nature of the camera capture through {@link
+         * android.hardware.camera2.CaptureRequest}, there may be frame drops if the application
+         * requests images from a physical camera of a logical multi-camera and that physical camera
+         * becomes unavailable. The application should stop requesting directly from an unavailable
+         * physical camera as soon as {@link #onPhysicalCameraUnavailable} is received, and also be
+         * ready to robustly handle frame drop errors for requests targeting physical cameras,
+         * since those errors may arrive before the unavailability callback.</p>
+         *
          * <p>The default implementation of this method does nothing.</p>
          *
          * @param cameraId The unique identifier of the logical multi-camera.
@@ -2283,7 +2364,8 @@
                 postSingleUpdate(callback, executor, id, null /*physicalId*/, status);
 
                 // Send the NOT_PRESENT state for unavailable physical cameras
-                if (isAvailable(status) && mUnavailablePhysicalDevices.containsKey(id)) {
+                if ((isAvailable(status) || physicalCallbacksAreEnabledForUnavailableCamera())
+                        && mUnavailablePhysicalDevices.containsKey(id)) {
                     ArrayList<String> unavailableIds = mUnavailablePhysicalDevices.get(id);
                     for (String unavailableId : unavailableIds) {
                         postSingleUpdate(callback, executor, id, unavailableId,
@@ -2416,7 +2498,8 @@
                 return;
             }
 
-            if (!isAvailable(mDeviceStatus.get(id))) {
+            if (!physicalCallbacksAreEnabledForUnavailableCamera()
+                    && !isAvailable(mDeviceStatus.get(id))) {
                 Log.i(TAG, String.format("Camera %s is not available. Ignore physical camera "
                         + "status change callback(s)", id));
                 return;
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 705afc5..ed2a198 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -3645,17 +3645,13 @@
     //
 
     /**
-     * <p>This is the default sensor pixel mode. This is the only sensor pixel mode
-     * supported unless a camera device advertises
-     * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }.</p>
+     * <p>This is the default sensor pixel mode.</p>
      * @see CaptureRequest#SENSOR_PIXEL_MODE
      */
     public static final int SENSOR_PIXEL_MODE_DEFAULT = 0;
 
     /**
-     * <p>This sensor pixel mode is offered by devices with capability
-     * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }.
-     * In this mode, sensors typically do not bin pixels, as a result can offer larger
+     * <p>In this mode, sensors typically do not bin pixels, as a result can offer larger
      * image sizes.</p>
      * @see CaptureRequest#SENSOR_PIXEL_MODE
      */
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 381c87d..929868b 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -1430,7 +1430,9 @@
      * mode.</p>
      * <p>For camera devices with the
      * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
-     * capability,
+     * capability or devices where
+     * {@link CameraCharacteristics#getAvailableCaptureRequestKeys }
+     * lists {@link CaptureRequest#SENSOR_PIXEL_MODE {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode}}
      * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
      * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution} must be used as the
      * coordinate system for requests where {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
@@ -1660,7 +1662,10 @@
      * mode.</p>
      * <p>For camera devices with the
      * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
-     * capability, {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
+     * capability or devices where
+     * {@link CameraCharacteristics#getAvailableCaptureRequestKeys }
+     * lists {@link CaptureRequest#SENSOR_PIXEL_MODE {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode}},
+     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
      * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution} must be used as the
      * coordinate system for requests where {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
      * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
@@ -1882,7 +1887,10 @@
      * mode.</p>
      * <p>For camera devices with the
      * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
-     * capability, {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
+     * capability or devices where
+     * {@link CameraCharacteristics#getAvailableCaptureRequestKeys }
+     * lists {@link CaptureRequest#SENSOR_PIXEL_MODE {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode}},
+     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
      * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution} must be used as the
      * coordinate system for requests where {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
      * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
@@ -3169,7 +3177,9 @@
      * {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} for details.</p>
      * <p>For camera devices with the
      * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
-     * capability, {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
+     * capability or devices where {@link CameraCharacteristics#getAvailableCaptureRequestKeys }
+     * lists {@link CaptureRequest#SENSOR_PIXEL_MODE {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode}}</p>
+     * <p>{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
      * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution} must be used as the
      * coordinate system for requests where {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
      * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
@@ -3517,13 +3527,10 @@
      * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_DEFAULT } mode.
      * When operating in
      * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_DEFAULT } mode, sensors
-     * with {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
-     * capability would typically perform pixel binning in order to improve low light
+     * would typically perform pixel binning in order to improve low light
      * performance, noise reduction etc. However, in
      * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }
-     * mode (supported only
-     * by {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
-     * sensors), sensors typically operate in unbinned mode allowing for a larger image size.
+     * mode, sensors typically operate in unbinned mode allowing for a larger image size.
      * The stream configurations supported in
      * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }
      * mode are also different from those of
@@ -3537,7 +3544,32 @@
      * <code>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap}</code>
      * must not be mixed in the same CaptureRequest. In other words, these outputs are
      * exclusive to each other.
-     * This key does not need to be set for reprocess requests.</p>
+     * This key does not need to be set for reprocess requests.
+     * This key will be be present on devices supporting the
+     * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
+     * capability. It may also be present on devices which do not support the aforementioned
+     * capability. In that case:</p>
+     * <ul>
+     * <li>
+     * <p>The mandatory stream combinations listed in
+     *   {@link android.hardware.camera2.CameraCharacteristics.mandatoryMaximumResolutionStreamCombinations }
+     *   would not apply.</p>
+     * </li>
+     * <li>
+     * <p>The bayer pattern of {@code RAW} streams when
+     *   {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }
+     *   is selected will be the one listed in {@link android.sensor.info.binningFactor }.</p>
+     * </li>
+     * <li>
+     * <p>The following keys will always be present:</p>
+     * <ul>
+     * <li>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION android.scaler.streamConfigurationMapMaximumResolution}</li>
+     * <li>{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution}</li>
+     * <li>{@link CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.pixelArraySizeMaximumResolution}</li>
+     * <li>{@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution}</li>
+     * </ul>
+     * </li>
+     * </ul>
      * <p><b>Possible values:</b></p>
      * <ul>
      *   <li>{@link #SENSOR_PIXEL_MODE_DEFAULT DEFAULT}</li>
@@ -3548,6 +3580,9 @@
      *
      * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP
      * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION
+     * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
+     * @see CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE_MAXIMUM_RESOLUTION
+     * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
      * @see #SENSOR_PIXEL_MODE_DEFAULT
      * @see #SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION
      */
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 635e79c..a429f30 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -849,7 +849,9 @@
      * mode.</p>
      * <p>For camera devices with the
      * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
-     * capability,
+     * capability or devices where
+     * {@link CameraCharacteristics#getAvailableCaptureRequestKeys }
+     * lists {@link CaptureRequest#SENSOR_PIXEL_MODE {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode}}
      * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
      * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution} must be used as the
      * coordinate system for requests where {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
@@ -1329,7 +1331,10 @@
      * mode.</p>
      * <p>For camera devices with the
      * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
-     * capability, {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
+     * capability or devices where
+     * {@link CameraCharacteristics#getAvailableCaptureRequestKeys }
+     * lists {@link CaptureRequest#SENSOR_PIXEL_MODE {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode}},
+     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
      * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution} must be used as the
      * coordinate system for requests where {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
      * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
@@ -1962,7 +1967,10 @@
      * mode.</p>
      * <p>For camera devices with the
      * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
-     * capability, {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
+     * capability or devices where
+     * {@link CameraCharacteristics#getAvailableCaptureRequestKeys }
+     * lists {@link CaptureRequest#SENSOR_PIXEL_MODE {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode}},
+     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
      * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution} must be used as the
      * coordinate system for requests where {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
      * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
@@ -3831,7 +3839,9 @@
      * {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} for details.</p>
      * <p>For camera devices with the
      * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
-     * capability, {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
+     * capability or devices where {@link CameraCharacteristics#getAvailableCaptureRequestKeys }
+     * lists {@link CaptureRequest#SENSOR_PIXEL_MODE {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode}}</p>
+     * <p>{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
      * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution} must be used as the
      * coordinate system for requests where {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
      * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
@@ -4442,13 +4452,10 @@
      * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_DEFAULT } mode.
      * When operating in
      * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_DEFAULT } mode, sensors
-     * with {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
-     * capability would typically perform pixel binning in order to improve low light
+     * would typically perform pixel binning in order to improve low light
      * performance, noise reduction etc. However, in
      * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }
-     * mode (supported only
-     * by {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
-     * sensors), sensors typically operate in unbinned mode allowing for a larger image size.
+     * mode, sensors typically operate in unbinned mode allowing for a larger image size.
      * The stream configurations supported in
      * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }
      * mode are also different from those of
@@ -4462,7 +4469,32 @@
      * <code>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap}</code>
      * must not be mixed in the same CaptureRequest. In other words, these outputs are
      * exclusive to each other.
-     * This key does not need to be set for reprocess requests.</p>
+     * This key does not need to be set for reprocess requests.
+     * This key will be be present on devices supporting the
+     * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
+     * capability. It may also be present on devices which do not support the aforementioned
+     * capability. In that case:</p>
+     * <ul>
+     * <li>
+     * <p>The mandatory stream combinations listed in
+     *   {@link android.hardware.camera2.CameraCharacteristics.mandatoryMaximumResolutionStreamCombinations }
+     *   would not apply.</p>
+     * </li>
+     * <li>
+     * <p>The bayer pattern of {@code RAW} streams when
+     *   {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }
+     *   is selected will be the one listed in {@link android.sensor.info.binningFactor }.</p>
+     * </li>
+     * <li>
+     * <p>The following keys will always be present:</p>
+     * <ul>
+     * <li>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION android.scaler.streamConfigurationMapMaximumResolution}</li>
+     * <li>{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution}</li>
+     * <li>{@link CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.pixelArraySizeMaximumResolution}</li>
+     * <li>{@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution}</li>
+     * </ul>
+     * </li>
+     * </ul>
      * <p><b>Possible values:</b></p>
      * <ul>
      *   <li>{@link #SENSOR_PIXEL_MODE_DEFAULT DEFAULT}</li>
@@ -4473,6 +4505,9 @@
      *
      * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP
      * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION
+     * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
+     * @see CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE_MAXIMUM_RESOLUTION
+     * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
      * @see #SENSOR_PIXEL_MODE_DEFAULT
      * @see #SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION
      */
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index db83e62..2fa8b87 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -101,15 +101,15 @@
 
 
     // Lock to synchronize cross-thread access to device public interface
-    final Object mInterfaceLock = new Object(); // access from this class and Session only!
+    final Object mInterfaceLock;
 
     /**
      * @hide
      */
     @RequiresPermission(android.Manifest.permission.CAMERA)
     public static CameraAdvancedExtensionSessionImpl createCameraAdvancedExtensionSession(
-            @NonNull CameraDevice cameraDevice, @NonNull Context ctx,
-            @NonNull ExtensionSessionConfiguration config, int sessionId)
+            @NonNull android.hardware.camera2.impl.CameraDeviceImpl cameraDevice,
+            @NonNull Context ctx, @NonNull ExtensionSessionConfiguration config, int sessionId)
             throws CameraAccessException, RemoteException {
         long clientId = CameraExtensionCharacteristics.registerClient(ctx);
         if (clientId < 0) {
@@ -209,7 +209,8 @@
     }
 
     private CameraAdvancedExtensionSessionImpl(long extensionClientId,
-            @NonNull IAdvancedExtenderImpl extender, @NonNull CameraDevice cameraDevice,
+            @NonNull IAdvancedExtenderImpl extender,
+            @NonNull android.hardware.camera2.impl.CameraDeviceImpl cameraDevice,
             @Nullable Surface repeatingRequestSurface, @Nullable Surface burstCaptureSurface,
             @Nullable Surface postviewSurface,
             @NonNull CameraExtensionSession.StateCallback callback, @NonNull Executor executor,
@@ -228,6 +229,7 @@
         mInitialized = false;
         mInitializeHandler = new InitializeSessionHandler();
         mSessionId = sessionId;
+        mInterfaceLock = cameraDevice.mInterfaceLock;
     }
 
     /**
@@ -599,13 +601,14 @@
         public void onConfigured(@NonNull CameraCaptureSession session) {
             synchronized (mInterfaceLock) {
                 mCaptureSession = session;
-                try {
-                    CameraExtensionCharacteristics.initializeSession(mInitializeHandler);
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Failed to initialize session! Extension service does"
-                            + " not respond!");
-                    notifyConfigurationFailure();
-                }
+            }
+
+            try {
+                CameraExtensionCharacteristics.initializeSession(mInitializeHandler);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to initialize session! Extension service does"
+                        + " not respond!");
+                notifyConfigurationFailure();
             }
         }
     }
@@ -613,46 +616,56 @@
     private class InitializeSessionHandler extends IInitializeSessionCallback.Stub {
         @Override
         public void onSuccess() {
-            boolean status = true;
-            synchronized (mInterfaceLock) {
-                try {
-                    if (mSessionProcessor != null) {
-                        mSessionProcessor.onCaptureSessionStart(mRequestProcessor);
-                        mInitialized = true;
-                    } else {
-                        Log.v(TAG, "Failed to start capture session, session released before " +
-                                "extension start!");
-                        status = false;
-                        mCaptureSession.close();
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    boolean status = true;
+                    synchronized (mInterfaceLock) {
+                        try {
+                            if (mSessionProcessor != null) {
+                                mSessionProcessor.onCaptureSessionStart(mRequestProcessor);
+                                mInitialized = true;
+                            } else {
+                                Log.v(TAG, "Failed to start capture session, session " +
+                                                " released before extension start!");
+                                status = false;
+                            }
+                        } catch (RemoteException e) {
+                            Log.e(TAG, "Failed to start capture session,"
+                                    + " extension service does not respond!");
+                            status = false;
+                            mInitialized = false;
+                        }
                     }
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Failed to start capture session,"
-                            + " extension service does not respond!");
-                    status = false;
-                    mCaptureSession.close();
-                }
-            }
 
-            if (status) {
-                final long ident = Binder.clearCallingIdentity();
-                try {
-                    mExecutor.execute(
-                            () -> mCallbacks.onConfigured(CameraAdvancedExtensionSessionImpl.this));
-                } finally {
-                    Binder.restoreCallingIdentity(ident);
+                    if (status) {
+                        final long ident = Binder.clearCallingIdentity();
+                        try {
+                            mExecutor.execute(() -> mCallbacks.onConfigured(
+                                    CameraAdvancedExtensionSessionImpl.this));
+                        } finally {
+                            Binder.restoreCallingIdentity(ident);
+                        }
+                    } else {
+                        onFailure();
+                    }
                 }
-            } else {
-                notifyConfigurationFailure();
-            }
+            });
         }
 
         @Override
         public void onFailure() {
-            mCaptureSession.close();
-            Log.e(TAG, "Failed to initialize proxy service session!"
-                    + " This can happen when trying to configure multiple "
-                    + "concurrent extension sessions!");
-            notifyConfigurationFailure();
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mCaptureSession.close();
+
+                    Log.e(TAG, "Failed to initialize proxy service session!"
+                            + " This can happen when trying to configure multiple "
+                            + "concurrent extension sessions!");
+                    notifyConfigurationFailure();
+                }
+            });
         }
     }
 
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index c2b3656..9c878c7 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -117,7 +117,7 @@
     private boolean mInternalRepeatingRequestEnabled = true;
 
     // Lock to synchronize cross-thread access to device public interface
-    final Object mInterfaceLock = new Object(); // access from this class and Session only!
+    final Object mInterfaceLock;
 
     private static int nativeGetSurfaceFormat(Surface surface) {
         return SurfaceUtils.getSurfaceFormat(surface);
@@ -128,7 +128,7 @@
      */
     @RequiresPermission(android.Manifest.permission.CAMERA)
     public static CameraExtensionSessionImpl createCameraExtensionSession(
-            @NonNull CameraDevice cameraDevice,
+            @NonNull android.hardware.camera2.impl.CameraDeviceImpl cameraDevice,
             @NonNull Context ctx,
             @NonNull ExtensionSessionConfiguration config,
             int sessionId)
@@ -251,7 +251,7 @@
             @NonNull IPreviewExtenderImpl previewExtender,
             @NonNull List<Size> previewSizes,
             long extensionClientId,
-            @NonNull CameraDevice cameraDevice,
+            @NonNull android.hardware.camera2.impl.CameraDeviceImpl cameraDevice,
             @Nullable Surface repeatingRequestSurface,
             @Nullable Surface burstCaptureSurface,
             @Nullable Surface postviewSurface,
@@ -279,6 +279,7 @@
         mSupportedRequestKeys = requestKeys;
         mSupportedResultKeys = resultKeys;
         mCaptureResultsSupported = !resultKeys.isEmpty();
+        mInterfaceLock = cameraDevice.mInterfaceLock;
     }
 
     private void initializeRepeatingRequestPipeline() throws RemoteException {
@@ -969,46 +970,56 @@
     private class InitializeSessionHandler extends IInitializeSessionCallback.Stub {
         @Override
         public void onSuccess() {
-            boolean status = true;
-            ArrayList<CaptureStageImpl> initialRequestList =
-                    compileInitialRequestList();
-            if (!initialRequestList.isEmpty()) {
-                try {
-                    setInitialCaptureRequest(initialRequestList,
-                            new InitialRequestHandler(
-                                    mRepeatingRequestImageCallback));
-                } catch (CameraAccessException e) {
-                    Log.e(TAG,
-                            "Failed to initialize the initial capture "
-                                    + "request!");
-                    status = false;
-                }
-            } else {
-                try {
-                    setRepeatingRequest(mPreviewExtender.getCaptureStage(),
-                            new PreviewRequestHandler(null, null, null,
-                                    mRepeatingRequestImageCallback));
-                } catch (CameraAccessException | RemoteException e) {
-                    Log.e(TAG,
-                            "Failed to initialize internal repeating "
-                                    + "request!");
-                    status = false;
-                }
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    boolean status = true;
+                    ArrayList<CaptureStageImpl> initialRequestList =
+                            compileInitialRequestList();
+                    if (!initialRequestList.isEmpty()) {
+                        try {
+                            setInitialCaptureRequest(initialRequestList,
+                                    new InitialRequestHandler(
+                                            mRepeatingRequestImageCallback));
+                        } catch (CameraAccessException e) {
+                            Log.e(TAG,
+                                    "Failed to initialize the initial capture "
+                                            + "request!");
+                            status = false;
+                        }
+                    } else {
+                        try {
+                            setRepeatingRequest(mPreviewExtender.getCaptureStage(),
+                                    new PreviewRequestHandler(null, null, null,
+                                            mRepeatingRequestImageCallback));
+                        } catch (CameraAccessException | RemoteException e) {
+                            Log.e(TAG,
+                                    "Failed to initialize internal repeating "
+                                            + "request!");
+                            status = false;
+                        }
 
-            }
+                    }
 
-            if (!status) {
-                notifyConfigurationFailure();
-            }
+                    if (!status) {
+                        notifyConfigurationFailure();
+                    }
+                }
+            });
         }
 
         @Override
         public void onFailure() {
-            mCaptureSession.close();
-            Log.e(TAG, "Failed to initialize proxy service session!"
-                    + " This can happen when trying to configure multiple "
-                    + "concurrent extension sessions!");
-            notifyConfigurationFailure();
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mCaptureSession.close();
+                    Log.e(TAG, "Failed to initialize proxy service session!"
+                            + " This can happen when trying to configure multiple "
+                            + "concurrent extension sessions!");
+                    notifyConfigurationFailure();
+                }
+            });
         }
     }
 
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 631df01..9743c1f 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -1618,15 +1618,20 @@
     }
 
     private StreamConfigurationMap getStreamConfigurationMapMaximumResolution() {
-        if (!isUltraHighResolutionSensor()) {
-            return null;
-        }
         StreamConfiguration[] configurations = getBase(
                 CameraCharacteristics.SCALER_AVAILABLE_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION);
         StreamConfigurationDuration[] minFrameDurations = getBase(
                 CameraCharacteristics.SCALER_AVAILABLE_MIN_FRAME_DURATIONS_MAXIMUM_RESOLUTION);
         StreamConfigurationDuration[] stallDurations = getBase(
                 CameraCharacteristics.SCALER_AVAILABLE_STALL_DURATIONS_MAXIMUM_RESOLUTION);
+        // If the at least these keys haven't been advertised, there cannot be a meaningful max
+        // resolution StreamConfigurationMap
+        if (configurations == null ||
+                minFrameDurations == null ||
+                stallDurations == null) {
+            return null;
+        }
+
         StreamConfiguration[] depthConfigurations = getBase(
                 CameraCharacteristics.DEPTH_AVAILABLE_DEPTH_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION);
         StreamConfigurationDuration[] depthMinFrameDurations = getBase(
diff --git a/core/java/android/hardware/input/InputDeviceSensorManager.java b/core/java/android/hardware/input/InputDeviceSensorManager.java
index 8a40d00..aa55e54 100644
--- a/core/java/android/hardware/input/InputDeviceSensorManager.java
+++ b/core/java/android/hardware/input/InputDeviceSensorManager.java
@@ -56,7 +56,7 @@
     private static final int MSG_SENSOR_ACCURACY_CHANGED = 1;
     private static final int MSG_SENSOR_CHANGED = 2;
 
-    private InputManager mInputManager;
+    private InputManagerGlobal mGlobal;
 
     // sensor map from device id to sensor list
     @GuardedBy("mInputSensorLock")
@@ -70,15 +70,15 @@
     private final HandlerThread mSensorThread;
     private final Handler mSensorHandler;
 
-    public InputDeviceSensorManager(InputManager inputManager) {
-        mInputManager = inputManager;
+    public InputDeviceSensorManager(InputManagerGlobal inputManagerGlobal) {
+        mGlobal = inputManagerGlobal;
 
         mSensorThread = new HandlerThread("SensorThread");
         mSensorThread.start();
         mSensorHandler = new Handler(mSensorThread.getLooper());
 
         // Register the input device listener
-        mInputManager.registerInputDeviceListener(this, mSensorHandler);
+        mGlobal.registerInputDeviceListener(this, mSensorHandler);
         // Initialize the sensor list
         initializeSensors();
     }
@@ -100,7 +100,7 @@
         final InputDevice inputDevice = InputDevice.getDevice(deviceId);
         if (inputDevice != null && inputDevice.hasSensor()) {
             final InputSensorInfo[] sensorInfos =
-                    mInputManager.getSensorList(deviceId);
+                    mGlobal.getSensorList(deviceId);
             populateSensorsForInputDeviceLocked(deviceId, sensorInfos);
         }
     }
@@ -154,7 +154,7 @@
     private void initializeSensors() {
         synchronized (mInputSensorLock) {
             mSensors.clear();
-            int[] deviceIds = mInputManager.getInputDeviceIds();
+            int[] deviceIds = mGlobal.getInputDeviceIds();
             for (int i = 0; i < deviceIds.length; i++) {
                 final int deviceId = deviceIds[i];
                 updateInputDeviceSensorInfoLocked(deviceId);
@@ -455,7 +455,7 @@
                     Slog.e(TAG, "The device doesn't have the sensor:" + sensor);
                     return false;
                 }
-                if (!mInputManager.enableSensor(deviceId, sensor.getType(), delayUs,
+                if (!mGlobal.enableSensor(deviceId, sensor.getType(), delayUs,
                         maxBatchReportLatencyUs)) {
                     Slog.e(TAG, "Can't enable the sensor:" + sensor);
                     return false;
@@ -467,7 +467,7 @@
             // Register the InputManagerService sensor listener if not yet.
             if (mInputServiceSensorListener == null) {
                 mInputServiceSensorListener = new InputSensorEventListener();
-                if (!mInputManager.registerSensorListener(mInputServiceSensorListener)) {
+                if (!mGlobal.registerSensorListener(mInputServiceSensorListener)) {
                     Slog.e(TAG, "Failed registering the sensor listener");
                     return false;
                 }
@@ -516,7 +516,7 @@
             }
             // If no delegation remains, unregister the listener to input service
             if (mInputServiceSensorListener != null && mInputSensorEventListeners.size() == 0) {
-                mInputManager.unregisterSensorListener(mInputServiceSensorListener);
+                mGlobal.unregisterSensorListener(mInputServiceSensorListener);
                 mInputServiceSensorListener = null;
             }
             // For each sensor type check if it is still in use by other listeners.
@@ -539,7 +539,7 @@
                     if (DEBUG) {
                         Slog.d(TAG, "device " + deviceId + " sensor " + sensorType + " disabled");
                     }
-                    mInputManager.disableSensor(deviceId, sensorType);
+                    mGlobal.disableSensor(deviceId, sensorType);
                 }
             }
         }
@@ -553,7 +553,7 @@
             }
             for (Sensor sensor : mInputSensorEventListeners.get(idx).getSensors()) {
                 final int deviceId = sensor.getId();
-                if (!mInputManager.flushSensor(deviceId, sensor.getType())) {
+                if (!mGlobal.flushSensor(deviceId, sensor.getType())) {
                     return false;
                 }
             }
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 490589f..054ae21 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -44,8 +44,6 @@
 import android.os.IBinder;
 import android.os.IVibratorStateListener;
 import android.os.InputEventInjectionSync;
-import android.os.Looper;
-import android.os.Message;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -53,7 +51,6 @@
 import android.os.Vibrator;
 import android.os.VibratorManager;
 import android.util.Log;
-import android.util.SparseArray;
 import android.view.Display;
 import android.view.InputDevice;
 import android.view.InputEvent;
@@ -66,9 +63,7 @@
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodSubtype;
 
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.os.SomeArgs;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -88,10 +83,6 @@
     // To enable these logs, run: 'adb shell setprop log.tag.InputManager DEBUG' (requires restart)
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
-    private static final int MSG_DEVICE_ADDED = 1;
-    private static final int MSG_DEVICE_REMOVED = 2;
-    private static final int MSG_DEVICE_CHANGED = 3;
-
     private static InputManager sInstance;
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
@@ -112,33 +103,6 @@
     @Nullable
     private Boolean mIsStylusPointerIconEnabled = null;
 
-    // Guarded by mInputDevicesLock
-    private final Object mInputDevicesLock = new Object();
-    private SparseArray<InputDevice> mInputDevices;
-    private InputDevicesChangedListener mInputDevicesChangedListener;
-    private final ArrayList<InputDeviceListenerDelegate> mInputDeviceListeners = new ArrayList<>();
-
-    // Guarded by mTabletModeLock
-    private final Object mTabletModeLock = new Object();
-    @SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"})
-    private TabletModeChangedListener mTabletModeChangedListener;
-    private ArrayList<OnTabletModeChangedListenerDelegate> mOnTabletModeChangedListeners;
-
-    private final Object mBatteryListenersLock = new Object();
-    // Maps a deviceId whose battery is currently being monitored to an entry containing the
-    // registered listeners for that device.
-    @GuardedBy("mBatteryListenersLock")
-    private SparseArray<RegisteredBatteryListeners> mBatteryListeners;
-    @GuardedBy("mBatteryListenersLock")
-    private IInputDeviceBatteryListener mInputDeviceBatteryListener;
-
-    private final Object mKeyboardBacklightListenerLock = new Object();
-    @GuardedBy("mKeyboardBacklightListenerLock")
-    private ArrayList<KeyboardBacklightListenerDelegate> mKeyboardBacklightListeners;
-    @GuardedBy("mKeyboardBacklightListenerLock")
-    private IKeyboardBacklightListener mKeyboardBacklightListener;
-
-    private InputDeviceSensorManager mInputDeviceSensorManager;
     /**
      * Broadcast Action: Query available keyboard layouts.
      * <p>
@@ -403,27 +367,7 @@
      */
     @Nullable
     public InputDevice getInputDevice(int id) {
-        synchronized (mInputDevicesLock) {
-            populateInputDevicesLocked();
-
-            int index = mInputDevices.indexOfKey(id);
-            if (index < 0) {
-                return null;
-            }
-
-            InputDevice inputDevice = mInputDevices.valueAt(index);
-            if (inputDevice == null) {
-                try {
-                    inputDevice = mIm.getInputDevice(id);
-                } catch (RemoteException ex) {
-                    throw ex.rethrowFromSystemServer();
-                }
-                if (inputDevice != null) {
-                    mInputDevices.setValueAt(index, inputDevice);
-                }
-            }
-            return inputDevice;
-        }
+        return mGlobal.getInputDevice(id);
     }
 
     /**
@@ -433,34 +377,7 @@
      * @hide
      */
     public InputDevice getInputDeviceByDescriptor(String descriptor) {
-        if (descriptor == null) {
-            throw new IllegalArgumentException("descriptor must not be null.");
-        }
-
-        synchronized (mInputDevicesLock) {
-            populateInputDevicesLocked();
-
-            int numDevices = mInputDevices.size();
-            for (int i = 0; i < numDevices; i++) {
-                InputDevice inputDevice = mInputDevices.valueAt(i);
-                if (inputDevice == null) {
-                    int id = mInputDevices.keyAt(i);
-                    try {
-                        inputDevice = mIm.getInputDevice(id);
-                    } catch (RemoteException ex) {
-                        throw ex.rethrowFromSystemServer();
-                    }
-                    if (inputDevice == null) {
-                        continue;
-                    }
-                    mInputDevices.setValueAt(i, inputDevice);
-                }
-                if (descriptor.equals(inputDevice.getDescriptor())) {
-                    return inputDevice;
-                }
-            }
-            return null;
-        }
+        return mGlobal.getInputDeviceByDescriptor(descriptor);
     }
 
     /**
@@ -468,16 +385,7 @@
      * @return The input device ids.
      */
     public int[] getInputDeviceIds() {
-        synchronized (mInputDevicesLock) {
-            populateInputDevicesLocked();
-
-            final int count = mInputDevices.size();
-            final int[] ids = new int[count];
-            for (int i = 0; i < count; i++) {
-                ids[i] = mInputDevices.keyAt(i);
-            }
-            return ids;
-        }
+        return mGlobal.getInputDeviceIds();
     }
 
     /**
@@ -547,17 +455,7 @@
      * @see #unregisterInputDeviceListener
      */
     public void registerInputDeviceListener(InputDeviceListener listener, Handler handler) {
-        if (listener == null) {
-            throw new IllegalArgumentException("listener must not be null");
-        }
-
-        synchronized (mInputDevicesLock) {
-            populateInputDevicesLocked();
-            int index = findInputDeviceListenerLocked(listener);
-            if (index < 0) {
-                mInputDeviceListeners.add(new InputDeviceListenerDelegate(listener, handler));
-            }
-        }
+        mGlobal.registerInputDeviceListener(listener, handler);
     }
 
     /**
@@ -568,28 +466,7 @@
      * @see #registerInputDeviceListener
      */
     public void unregisterInputDeviceListener(InputDeviceListener listener) {
-        if (listener == null) {
-            throw new IllegalArgumentException("listener must not be null");
-        }
-
-        synchronized (mInputDevicesLock) {
-            int index = findInputDeviceListenerLocked(listener);
-            if (index >= 0) {
-                InputDeviceListenerDelegate d = mInputDeviceListeners.get(index);
-                d.removeCallbacksAndMessages(null);
-                mInputDeviceListeners.remove(index);
-            }
-        }
-    }
-
-    private int findInputDeviceListenerLocked(InputDeviceListener listener) {
-        final int numListeners = mInputDeviceListeners.size();
-        for (int i = 0; i < numListeners; i++) {
-            if (mInputDeviceListeners.get(i).mListener == listener) {
-                return i;
-            }
-        }
-        return -1;
+        mGlobal.unregisterInputDeviceListener(listener);
     }
 
     /**
@@ -618,20 +495,7 @@
      */
     public void registerOnTabletModeChangedListener(
             OnTabletModeChangedListener listener, Handler handler) {
-        if (listener == null) {
-            throw new IllegalArgumentException("listener must not be null");
-        }
-        synchronized (mTabletModeLock) {
-            if (mOnTabletModeChangedListeners == null) {
-                initializeTabletModeListenerLocked();
-            }
-            int idx = findOnTabletModeChangedListenerLocked(listener);
-            if (idx < 0) {
-                OnTabletModeChangedListenerDelegate d =
-                    new OnTabletModeChangedListenerDelegate(listener, handler);
-                mOnTabletModeChangedListeners.add(d);
-            }
-        }
+        mGlobal.registerOnTabletModeChangedListener(listener, handler);
     }
 
     /**
@@ -641,37 +505,7 @@
      * @hide
      */
     public void unregisterOnTabletModeChangedListener(OnTabletModeChangedListener listener) {
-        if (listener == null) {
-            throw new IllegalArgumentException("listener must not be null");
-        }
-        synchronized (mTabletModeLock) {
-            int idx = findOnTabletModeChangedListenerLocked(listener);
-            if (idx >= 0) {
-                OnTabletModeChangedListenerDelegate d = mOnTabletModeChangedListeners.remove(idx);
-                d.removeCallbacksAndMessages(null);
-            }
-        }
-    }
-
-    private void initializeTabletModeListenerLocked() {
-        final TabletModeChangedListener listener = new TabletModeChangedListener();
-        try {
-            mIm.registerTabletModeChangedListener(listener);
-        } catch (RemoteException ex) {
-            throw ex.rethrowFromSystemServer();
-        }
-        mTabletModeChangedListener = listener;
-        mOnTabletModeChangedListeners = new ArrayList<>();
-    }
-
-    private int findOnTabletModeChangedListenerLocked(OnTabletModeChangedListener listener) {
-        final int N = mOnTabletModeChangedListeners.size();
-        for (int i = 0; i < N; i++) {
-            if (mOnTabletModeChangedListeners.get(i).mListener == listener) {
-                return i;
-            }
-        }
-        return -1;
+        mGlobal.unregisterOnTabletModeChangedListener(listener);
     }
 
     /**
@@ -1389,11 +1223,7 @@
      * @hide
      */
     public InputSensorInfo[] getSensorList(int deviceId) {
-        try {
-            return mIm.getSensorList(deviceId);
-        } catch (RemoteException ex) {
-            throw ex.rethrowFromSystemServer();
-        }
+        return mGlobal.getSensorList(deviceId);
     }
 
     /**
@@ -1403,12 +1233,8 @@
      */
     public boolean enableSensor(int deviceId, int sensorType, int samplingPeriodUs,
             int maxBatchReportLatencyUs) {
-        try {
-            return mIm.enableSensor(deviceId, sensorType, samplingPeriodUs,
-                    maxBatchReportLatencyUs);
-        } catch (RemoteException ex) {
-            throw ex.rethrowFromSystemServer();
-        }
+        return mGlobal.enableSensor(deviceId, sensorType, samplingPeriodUs,
+                maxBatchReportLatencyUs);
     }
 
     /**
@@ -1417,11 +1243,7 @@
      * @hide
      */
     public void disableSensor(int deviceId, int sensorType) {
-        try {
-            mIm.disableSensor(deviceId, sensorType);
-        } catch (RemoteException ex) {
-            throw ex.rethrowFromSystemServer();
-        }
+        mGlobal.disableSensor(deviceId, sensorType);
     }
 
     /**
@@ -1430,11 +1252,7 @@
      * @hide
      */
     public boolean flushSensor(int deviceId, int sensorType) {
-        try {
-            return mIm.flushSensor(deviceId, sensorType);
-        } catch (RemoteException ex) {
-            throw ex.rethrowFromSystemServer();
-        }
+        return mGlobal.flushSensor(deviceId, sensorType);
     }
 
     /**
@@ -1443,11 +1261,7 @@
      * @hide
      */
     public boolean registerSensorListener(IInputSensorEventListener listener) {
-        try {
-            return mIm.registerSensorListener(listener);
-        } catch (RemoteException ex) {
-            throw ex.rethrowFromSystemServer();
-        }
+        return mGlobal.registerSensorListener(listener);
     }
 
     /**
@@ -1456,11 +1270,7 @@
      * @hide
      */
     public void unregisterSensorListener(IInputSensorEventListener listener) {
-        try {
-            mIm.unregisterSensorListener(listener);
-        } catch (RemoteException ex) {
-            throw ex.rethrowFromSystemServer();
-        }
+        mGlobal.unregisterSensorListener(listener);
     }
 
     /**
@@ -1543,133 +1353,7 @@
      */
     @Nullable
     public HostUsiVersion getHostUsiVersion(@NonNull Display display) {
-        Objects.requireNonNull(display, "display should not be null");
-
-        // Return the first valid USI version reported by any input device associated with
-        // the display.
-        synchronized (mInputDevicesLock) {
-            populateInputDevicesLocked();
-
-            for (int i = 0; i < mInputDevices.size(); i++) {
-                final InputDevice device = getInputDevice(mInputDevices.keyAt(i));
-                if (device != null && device.getAssociatedDisplayId() == display.getDisplayId()) {
-                    if (device.getHostUsiVersion() != null) {
-                        return device.getHostUsiVersion();
-                    }
-                }
-            }
-        }
-
-        // If there are no input devices that report a valid USI version, see if there is a config
-        // that specifies the USI version for the display. This is to handle cases where the USI
-        // input device is not registered by the kernel/driver all the time.
-        try {
-            return mIm.getHostUsiVersionFromDisplayConfig(display.getDisplayId());
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    private void populateInputDevicesLocked() {
-        if (mInputDevicesChangedListener == null) {
-            final InputDevicesChangedListener listener = new InputDevicesChangedListener();
-            try {
-                mIm.registerInputDevicesChangedListener(listener);
-            } catch (RemoteException ex) {
-                throw ex.rethrowFromSystemServer();
-            }
-            mInputDevicesChangedListener = listener;
-        }
-
-        if (mInputDevices == null) {
-            final int[] ids;
-            try {
-                ids = mIm.getInputDeviceIds();
-            } catch (RemoteException ex) {
-                throw ex.rethrowFromSystemServer();
-            }
-
-            mInputDevices = new SparseArray<>();
-            for (int id : ids) {
-                mInputDevices.put(id, null);
-            }
-        }
-    }
-
-    private void onInputDevicesChanged(int[] deviceIdAndGeneration) {
-        if (DEBUG) {
-            Log.d(TAG, "Received input devices changed.");
-        }
-
-        synchronized (mInputDevicesLock) {
-            for (int i = mInputDevices.size(); --i > 0; ) {
-                final int deviceId = mInputDevices.keyAt(i);
-                if (!containsDeviceId(deviceIdAndGeneration, deviceId)) {
-                    if (DEBUG) {
-                        Log.d(TAG, "Device removed: " + deviceId);
-                    }
-                    mInputDevices.removeAt(i);
-                    sendMessageToInputDeviceListenersLocked(MSG_DEVICE_REMOVED, deviceId);
-                }
-            }
-
-            for (int i = 0; i < deviceIdAndGeneration.length; i += 2) {
-                final int deviceId = deviceIdAndGeneration[i];
-                int index = mInputDevices.indexOfKey(deviceId);
-                if (index >= 0) {
-                    final InputDevice device = mInputDevices.valueAt(index);
-                    if (device != null) {
-                        final int generation = deviceIdAndGeneration[i + 1];
-                        if (device.getGeneration() != generation) {
-                            if (DEBUG) {
-                                Log.d(TAG, "Device changed: " + deviceId);
-                            }
-                            mInputDevices.setValueAt(index, null);
-                            sendMessageToInputDeviceListenersLocked(MSG_DEVICE_CHANGED, deviceId);
-                        }
-                    }
-                } else {
-                    if (DEBUG) {
-                        Log.d(TAG, "Device added: " + deviceId);
-                    }
-                    mInputDevices.put(deviceId, null);
-                    sendMessageToInputDeviceListenersLocked(MSG_DEVICE_ADDED, deviceId);
-                }
-            }
-        }
-    }
-
-    private void sendMessageToInputDeviceListenersLocked(int what, int deviceId) {
-        final int numListeners = mInputDeviceListeners.size();
-        for (int i = 0; i < numListeners; i++) {
-            InputDeviceListenerDelegate listener = mInputDeviceListeners.get(i);
-            listener.sendMessage(listener.obtainMessage(what, deviceId, 0));
-        }
-    }
-
-    private static boolean containsDeviceId(int[] deviceIdAndGeneration, int deviceId) {
-        for (int i = 0; i < deviceIdAndGeneration.length; i += 2) {
-            if (deviceIdAndGeneration[i] == deviceId) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-
-    private void onTabletModeChanged(long whenNanos, boolean inTabletMode) {
-        if (DEBUG) {
-            Log.d(TAG, "Received tablet mode changed: "
-                    + "whenNanos=" + whenNanos + ", inTabletMode=" + inTabletMode);
-        }
-        synchronized (mTabletModeLock) {
-            final int numListeners = mOnTabletModeChangedListeners.size();
-            for (int i = 0; i < numListeners; i++) {
-                OnTabletModeChangedListenerDelegate listener =
-                        mOnTabletModeChangedListeners.get(i);
-                listener.sendTabletModeChanged(whenNanos, inTabletMode);
-            }
-        }
+        return mGlobal.getHostUsiVersion(display);
     }
 
     /**
@@ -1795,10 +1479,7 @@
      */
     @NonNull
     public SensorManager getInputDeviceSensorManager(int deviceId) {
-        if (mInputDeviceSensorManager == null) {
-            mInputDeviceSensorManager = new InputDeviceSensorManager(this);
-        }
-        return mInputDeviceSensorManager.getSensorManager(deviceId);
+        return mGlobal.getInputDeviceSensorManager(deviceId);
     }
 
     /**
@@ -1808,15 +1489,7 @@
      */
     @NonNull
     public BatteryState getInputDeviceBatteryState(int deviceId, boolean hasBattery) {
-        if (!hasBattery) {
-            return new LocalBatteryState();
-        }
-        try {
-            final IInputDeviceBatteryState state = mIm.getBatteryState(deviceId);
-            return new LocalBatteryState(state.isPresent, state.status, state.capacity);
-        } catch (RemoteException ex) {
-            throw ex.rethrowFromSystemServer();
-        }
+        return mGlobal.getInputDeviceBatteryState(deviceId, hasBattery);
     }
 
     /**
@@ -1952,49 +1625,7 @@
      */
     public void addInputDeviceBatteryListener(int deviceId, @NonNull Executor executor,
             @NonNull InputDeviceBatteryListener listener) {
-        Objects.requireNonNull(executor, "executor should not be null");
-        Objects.requireNonNull(listener, "listener should not be null");
-
-        synchronized (mBatteryListenersLock) {
-            if (mBatteryListeners == null) {
-                mBatteryListeners = new SparseArray<>();
-                mInputDeviceBatteryListener = new LocalInputDeviceBatteryListener();
-            }
-            RegisteredBatteryListeners listenersForDevice = mBatteryListeners.get(deviceId);
-            if (listenersForDevice == null) {
-                // The deviceId is currently not being monitored for battery changes.
-                // Start monitoring the device.
-                listenersForDevice = new RegisteredBatteryListeners();
-                mBatteryListeners.put(deviceId, listenersForDevice);
-                try {
-                    mIm.registerBatteryListener(deviceId, mInputDeviceBatteryListener);
-                } catch (RemoteException e) {
-                    throw e.rethrowFromSystemServer();
-                }
-            } else {
-                // The deviceId is already being monitored for battery changes.
-                // Ensure that the listener is not already registered.
-                final int numDelegates = listenersForDevice.mDelegates.size();
-                for (int i = 0; i < numDelegates; i++) {
-                    InputDeviceBatteryListener registeredListener =
-                            listenersForDevice.mDelegates.get(i).mListener;
-                    if (Objects.equals(listener, registeredListener)) {
-                        throw new IllegalArgumentException(
-                                "Attempting to register an InputDeviceBatteryListener that has "
-                                        + "already been registered for deviceId: "
-                                        + deviceId);
-                    }
-                }
-            }
-            final InputDeviceBatteryListenerDelegate delegate =
-                    new InputDeviceBatteryListenerDelegate(listener, executor);
-            listenersForDevice.mDelegates.add(delegate);
-
-            // Notify the listener immediately if we already have the latest battery state.
-            if (listenersForDevice.mInputDeviceBatteryState != null) {
-                delegate.notifyBatteryStateChanged(listenersForDevice.mInputDeviceBatteryState);
-            }
-        }
+        mGlobal.addInputDeviceBatteryListener(deviceId, executor, listener);
     }
 
     /**
@@ -2004,44 +1635,7 @@
      */
     public void removeInputDeviceBatteryListener(int deviceId,
             @NonNull InputDeviceBatteryListener listener) {
-        Objects.requireNonNull(listener, "listener should not be null");
-
-        synchronized (mBatteryListenersLock) {
-            if (mBatteryListeners == null) {
-                return;
-            }
-            RegisteredBatteryListeners listenersForDevice = mBatteryListeners.get(deviceId);
-            if (listenersForDevice == null) {
-                // The deviceId is not currently being monitored.
-                return;
-            }
-            final List<InputDeviceBatteryListenerDelegate> delegates =
-                    listenersForDevice.mDelegates;
-            for (int i = 0; i < delegates.size();) {
-                if (Objects.equals(listener, delegates.get(i).mListener)) {
-                    delegates.remove(i);
-                    continue;
-                }
-                i++;
-            }
-            if (!delegates.isEmpty()) {
-                return;
-            }
-
-            // There are no more battery listeners for this deviceId. Stop monitoring this device.
-            mBatteryListeners.remove(deviceId);
-            try {
-                mIm.unregisterBatteryListener(deviceId, mInputDeviceBatteryListener);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
-            if (mBatteryListeners.size() == 0) {
-                // There are no more devices being monitored, so the registered
-                // IInputDeviceBatteryListener will be automatically dropped by the server.
-                mBatteryListeners = null;
-                mInputDeviceBatteryListener = null;
-            }
-        }
+        mGlobal.removeInputDeviceBatteryListener(deviceId, listener);
     }
 
     /**
@@ -2067,30 +1661,7 @@
     @RequiresPermission(Manifest.permission.MONITOR_KEYBOARD_BACKLIGHT)
     public void registerKeyboardBacklightListener(@NonNull Executor executor,
             @NonNull KeyboardBacklightListener listener) throws IllegalArgumentException {
-        Objects.requireNonNull(executor, "executor should not be null");
-        Objects.requireNonNull(listener, "listener should not be null");
-
-        synchronized (mKeyboardBacklightListenerLock) {
-            if (mKeyboardBacklightListener == null) {
-                mKeyboardBacklightListeners = new ArrayList<>();
-                mKeyboardBacklightListener = new LocalKeyboardBacklightListener();
-
-                try {
-                    mIm.registerKeyboardBacklightListener(mKeyboardBacklightListener);
-                } catch (RemoteException e) {
-                    throw e.rethrowFromSystemServer();
-                }
-            }
-            final int numListeners = mKeyboardBacklightListeners.size();
-            for (int i = 0; i < numListeners; i++) {
-                if (mKeyboardBacklightListeners.get(i).mListener == listener) {
-                    throw new IllegalArgumentException("Listener has already been registered!");
-                }
-            }
-            KeyboardBacklightListenerDelegate delegate =
-                    new KeyboardBacklightListenerDelegate(listener, executor);
-            mKeyboardBacklightListeners.add(delegate);
-        }
+        mGlobal.registerKeyboardBacklightListener(executor, listener);
     }
 
     /**
@@ -2103,23 +1674,7 @@
     @RequiresPermission(Manifest.permission.MONITOR_KEYBOARD_BACKLIGHT)
     public void unregisterKeyboardBacklightListener(
             @NonNull KeyboardBacklightListener listener) {
-        Objects.requireNonNull(listener, "listener should not be null");
-
-        synchronized (mKeyboardBacklightListenerLock) {
-            if (mKeyboardBacklightListeners == null) {
-                return;
-            }
-            mKeyboardBacklightListeners.removeIf((delegate) -> delegate.mListener == listener);
-            if (mKeyboardBacklightListeners.isEmpty()) {
-                try {
-                    mIm.unregisterKeyboardBacklightListener(mKeyboardBacklightListener);
-                } catch (RemoteException e) {
-                    throw e.rethrowFromSystemServer();
-                }
-                mKeyboardBacklightListeners = null;
-                mKeyboardBacklightListener = null;
-            }
-        }
+        mGlobal.unregisterKeyboardBacklightListener(listener);
     }
 
     /**
@@ -2149,7 +1704,7 @@
     public interface InputDeviceListener {
         /**
          * Called whenever an input device has been added to the system.
-         * Use {@link InputManager#getInputDevice} to get more information about the device.
+         * Use {@link InputManagerGlobal#getInputDevice} to get more information about the device.
          *
          * @param deviceId The id of the input device that was added.
          */
@@ -2172,37 +1727,6 @@
         void onInputDeviceChanged(int deviceId);
     }
 
-    private final class InputDevicesChangedListener extends IInputDevicesChangedListener.Stub {
-        @Override
-        public void onInputDevicesChanged(int[] deviceIdAndGeneration) throws RemoteException {
-            InputManager.this.onInputDevicesChanged(deviceIdAndGeneration);
-        }
-    }
-
-    private static final class InputDeviceListenerDelegate extends Handler {
-        public final InputDeviceListener mListener;
-
-        public InputDeviceListenerDelegate(InputDeviceListener listener, Handler handler) {
-            super(handler != null ? handler.getLooper() : Looper.myLooper());
-            mListener = listener;
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_DEVICE_ADDED:
-                    mListener.onInputDeviceAdded(msg.arg1);
-                    break;
-                case MSG_DEVICE_REMOVED:
-                    mListener.onInputDeviceRemoved(msg.arg1);
-                    break;
-                case MSG_DEVICE_CHANGED:
-                    mListener.onInputDeviceChanged(msg.arg1);
-                    break;
-            }
-        }
-    }
-
     /** @hide */
     public interface OnTabletModeChangedListener {
         /**
@@ -2235,170 +1759,4 @@
         void onKeyboardBacklightChanged(
                 int deviceId, @NonNull KeyboardBacklightState state, boolean isTriggeredByKeyPress);
     }
-
-    private final class TabletModeChangedListener extends ITabletModeChangedListener.Stub {
-        @Override
-        public void onTabletModeChanged(long whenNanos, boolean inTabletMode) {
-            InputManager.this.onTabletModeChanged(whenNanos, inTabletMode);
-        }
-    }
-
-    private static final class OnTabletModeChangedListenerDelegate extends Handler {
-        private static final int MSG_TABLET_MODE_CHANGED = 0;
-
-        public final OnTabletModeChangedListener mListener;
-
-        public OnTabletModeChangedListenerDelegate(
-                OnTabletModeChangedListener listener, Handler handler) {
-            super(handler != null ? handler.getLooper() : Looper.myLooper());
-            mListener = listener;
-        }
-
-        public void sendTabletModeChanged(long whenNanos, boolean inTabletMode) {
-            SomeArgs args = SomeArgs.obtain();
-            args.argi1 = (int) whenNanos;
-            args.argi2 = (int) (whenNanos >> 32);
-            args.arg1 = inTabletMode;
-            obtainMessage(MSG_TABLET_MODE_CHANGED, args).sendToTarget();
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            if (msg.what == MSG_TABLET_MODE_CHANGED) {
-                SomeArgs args = (SomeArgs) msg.obj;
-                long whenNanos = (args.argi1 & 0xFFFFFFFFL) | ((long) args.argi2 << 32);
-                boolean inTabletMode = (boolean) args.arg1;
-                mListener.onTabletModeChanged(whenNanos, inTabletMode);
-            }
-        }
-    }
-
-    // Implementation of the android.hardware.BatteryState interface used to report the battery
-    // state via the InputDevice#getBatteryState() and InputDeviceBatteryListener interfaces.
-    private static final class LocalBatteryState extends BatteryState {
-        private final boolean mIsPresent;
-        private final int mStatus;
-        private final float mCapacity;
-
-        LocalBatteryState() {
-            this(false /*isPresent*/, BatteryState.STATUS_UNKNOWN, Float.NaN /*capacity*/);
-        }
-
-        LocalBatteryState(boolean isPresent, int status, float capacity) {
-            mIsPresent = isPresent;
-            mStatus = status;
-            mCapacity = capacity;
-        }
-
-        @Override
-        public boolean isPresent() {
-            return mIsPresent;
-        }
-
-        @Override
-        public int getStatus() {
-            return mStatus;
-        }
-
-        @Override
-        public float getCapacity() {
-            return mCapacity;
-        }
-    }
-
-    private static final class RegisteredBatteryListeners {
-        final List<InputDeviceBatteryListenerDelegate> mDelegates = new ArrayList<>();
-        IInputDeviceBatteryState mInputDeviceBatteryState;
-    }
-
-    private static final class InputDeviceBatteryListenerDelegate {
-        final InputDeviceBatteryListener mListener;
-        final Executor mExecutor;
-
-        InputDeviceBatteryListenerDelegate(InputDeviceBatteryListener listener, Executor executor) {
-            mListener = listener;
-            mExecutor = executor;
-        }
-
-        void notifyBatteryStateChanged(IInputDeviceBatteryState state) {
-            mExecutor.execute(() ->
-                    mListener.onBatteryStateChanged(state.deviceId, state.updateTime,
-                            new LocalBatteryState(state.isPresent, state.status, state.capacity)));
-        }
-    }
-
-    private class LocalInputDeviceBatteryListener extends IInputDeviceBatteryListener.Stub {
-        @Override
-        public void onBatteryStateChanged(IInputDeviceBatteryState state) {
-            synchronized (mBatteryListenersLock) {
-                if (mBatteryListeners == null) return;
-                final RegisteredBatteryListeners entry = mBatteryListeners.get(state.deviceId);
-                if (entry == null) return;
-
-                entry.mInputDeviceBatteryState = state;
-                final int numDelegates = entry.mDelegates.size();
-                for (int i = 0; i < numDelegates; i++) {
-                    entry.mDelegates.get(i)
-                            .notifyBatteryStateChanged(entry.mInputDeviceBatteryState);
-                }
-            }
-        }
-    }
-
-    // Implementation of the android.hardware.input.KeyboardBacklightState interface used to report
-    // the keyboard backlight state via the KeyboardBacklightListener interfaces.
-    private static final class LocalKeyboardBacklightState extends KeyboardBacklightState {
-
-        private final int mBrightnessLevel;
-        private final int mMaxBrightnessLevel;
-
-        LocalKeyboardBacklightState(int brightnessLevel, int maxBrightnessLevel) {
-            mBrightnessLevel = brightnessLevel;
-            mMaxBrightnessLevel = maxBrightnessLevel;
-        }
-
-        @Override
-        public int getBrightnessLevel() {
-            return mBrightnessLevel;
-        }
-
-        @Override
-        public int getMaxBrightnessLevel() {
-            return mMaxBrightnessLevel;
-        }
-    }
-
-    private static final class KeyboardBacklightListenerDelegate {
-        final KeyboardBacklightListener mListener;
-        final Executor mExecutor;
-
-        KeyboardBacklightListenerDelegate(KeyboardBacklightListener listener, Executor executor) {
-            mListener = listener;
-            mExecutor = executor;
-        }
-
-        void notifyKeyboardBacklightChange(int deviceId, IKeyboardBacklightState state,
-                boolean isTriggeredByKeyPress) {
-            mExecutor.execute(() ->
-                    mListener.onKeyboardBacklightChanged(deviceId,
-                            new LocalKeyboardBacklightState(state.brightnessLevel,
-                                    state.maxBrightnessLevel), isTriggeredByKeyPress));
-        }
-    }
-
-    private class LocalKeyboardBacklightListener extends IKeyboardBacklightListener.Stub {
-
-        @Override
-        public void onBrightnessChanged(int deviceId, IKeyboardBacklightState state,
-                boolean isTriggeredByKeyPress) {
-            synchronized (mKeyboardBacklightListenerLock) {
-                if (mKeyboardBacklightListeners == null) return;
-                final int numListeners = mKeyboardBacklightListeners.size();
-                for (int i = 0; i < numListeners; i++) {
-                    mKeyboardBacklightListeners.get(i)
-                            .notifyKeyboardBacklightChange(deviceId, state, isTriggeredByKeyPress);
-                }
-            }
-        }
-    }
 }
diff --git a/core/java/android/hardware/input/InputManagerGlobal.java b/core/java/android/hardware/input/InputManagerGlobal.java
index 82dddfc..524d820 100644
--- a/core/java/android/hardware/input/InputManagerGlobal.java
+++ b/core/java/android/hardware/input/InputManagerGlobal.java
@@ -16,18 +16,74 @@
 
 package android.hardware.input;
 
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.content.Context;
+import android.hardware.BatteryState;
+import android.hardware.SensorManager;
+import android.hardware.input.InputManager.InputDeviceBatteryListener;
+import android.hardware.input.InputManager.InputDeviceListener;
+import android.hardware.input.InputManager.KeyboardBacklightListener;
+import android.hardware.input.InputManager.OnTabletModeChangedListener;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.InputDevice;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.SomeArgs;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
 
 /**
  * Manages communication with the input manager service on behalf of
- * an application process.  You're probably looking for {@link InputManager}.
+ * an application process. You're probably looking for {@link InputManager}.
  *
  * @hide
  */
 public final class InputManagerGlobal {
     private static final String TAG = "InputManagerGlobal";
+    // To enable these logs, run: 'adb shell setprop log.tag.InputManagerGlobal DEBUG'
+    // (requires restart)
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    @GuardedBy("mInputDeviceListeners")
+    @Nullable private SparseArray<InputDevice> mInputDevices;
+    @GuardedBy("mInputDeviceListeners")
+    @Nullable private InputDevicesChangedListener mInputDevicesChangedListener;
+    @GuardedBy("mInputDeviceListeners")
+    private final ArrayList<InputDeviceListenerDelegate> mInputDeviceListeners = new ArrayList<>();
+
+    @GuardedBy("mOnTabletModeChangedListeners")
+    private final ArrayList<OnTabletModeChangedListenerDelegate> mOnTabletModeChangedListeners =
+            new ArrayList<>();
+
+    private final Object mBatteryListenersLock = new Object();
+    // Maps a deviceId whose battery is currently being monitored to an entry containing the
+    // registered listeners for that device.
+    @GuardedBy("mBatteryListenersLock")
+    @Nullable private SparseArray<RegisteredBatteryListeners> mBatteryListeners;
+    @GuardedBy("mBatteryListenersLock")
+    @Nullable private IInputDeviceBatteryListener mInputDeviceBatteryListener;
+
+    private final Object mKeyboardBacklightListenerLock = new Object();
+    @GuardedBy("mKeyboardBacklightListenerLock")
+    @Nullable private ArrayList<KeyboardBacklightListenerDelegate> mKeyboardBacklightListeners;
+    @GuardedBy("mKeyboardBacklightListenerLock")
+    @Nullable private IKeyboardBacklightListener mKeyboardBacklightListener;
+
+    @Nullable private InputDeviceSensorManager mInputDeviceSensorManager;
 
     private static InputManagerGlobal sInstance;
 
@@ -79,4 +135,772 @@
             sInstance = null;
         }
     }
+
+    /**
+     * @see InputManager#getInputDevice(int)
+     */
+    @Nullable
+    public InputDevice getInputDevice(int id) {
+        synchronized (mInputDeviceListeners) {
+            populateInputDevicesLocked();
+
+            int index = mInputDevices.indexOfKey(id);
+            if (index < 0) {
+                return null;
+            }
+
+            InputDevice inputDevice = mInputDevices.valueAt(index);
+            if (inputDevice == null) {
+                try {
+                    inputDevice = mIm.getInputDevice(id);
+                } catch (RemoteException ex) {
+                    throw ex.rethrowFromSystemServer();
+                }
+                if (inputDevice != null) {
+                    mInputDevices.setValueAt(index, inputDevice);
+                }
+            }
+            return inputDevice;
+        }
+    }
+
+    @GuardedBy("mInputDeviceListeners")
+    private void populateInputDevicesLocked() {
+        if (mInputDevicesChangedListener == null) {
+            final InputDevicesChangedListener
+                    listener = new InputDevicesChangedListener();
+            try {
+                mIm.registerInputDevicesChangedListener(listener);
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
+            }
+            mInputDevicesChangedListener = listener;
+        }
+
+        if (mInputDevices == null) {
+            final int[] ids;
+            try {
+                ids = mIm.getInputDeviceIds();
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
+            }
+
+            mInputDevices = new SparseArray<>();
+            for (int id : ids) {
+                mInputDevices.put(id, null);
+            }
+        }
+    }
+
+    private final class InputDevicesChangedListener extends IInputDevicesChangedListener.Stub {
+        @Override
+        public void onInputDevicesChanged(int[] deviceIdAndGeneration) throws RemoteException {
+            InputManagerGlobal.this.onInputDevicesChanged(deviceIdAndGeneration);
+        }
+    }
+
+    private void onInputDevicesChanged(int[] deviceIdAndGeneration) {
+        if (DEBUG) {
+            Log.d(TAG, "Received input devices changed.");
+        }
+
+        synchronized (mInputDeviceListeners) {
+            for (int i = mInputDevices.size(); --i > 0; ) {
+                final int deviceId = mInputDevices.keyAt(i);
+                if (!containsDeviceId(deviceIdAndGeneration, deviceId)) {
+                    if (DEBUG) {
+                        Log.d(TAG, "Device removed: " + deviceId);
+                    }
+                    mInputDevices.removeAt(i);
+                    sendMessageToInputDeviceListenersLocked(
+                            InputDeviceListenerDelegate.MSG_DEVICE_REMOVED, deviceId);
+                }
+            }
+
+            for (int i = 0; i < deviceIdAndGeneration.length; i += 2) {
+                final int deviceId = deviceIdAndGeneration[i];
+                int index = mInputDevices.indexOfKey(deviceId);
+                if (index >= 0) {
+                    final InputDevice device = mInputDevices.valueAt(index);
+                    if (device != null) {
+                        final int generation = deviceIdAndGeneration[i + 1];
+                        if (device.getGeneration() != generation) {
+                            if (DEBUG) {
+                                Log.d(TAG, "Device changed: " + deviceId);
+                            }
+                            mInputDevices.setValueAt(index, null);
+                            sendMessageToInputDeviceListenersLocked(
+                                    InputDeviceListenerDelegate.MSG_DEVICE_CHANGED, deviceId);
+                        }
+                    }
+                } else {
+                    if (DEBUG) {
+                        Log.d(TAG, "Device added: " + deviceId);
+                    }
+                    mInputDevices.put(deviceId, null);
+                    sendMessageToInputDeviceListenersLocked(
+                            InputDeviceListenerDelegate.MSG_DEVICE_ADDED, deviceId);
+                }
+            }
+        }
+    }
+
+    private static final class InputDeviceListenerDelegate extends Handler {
+        public final InputDeviceListener mListener;
+        static final int MSG_DEVICE_ADDED = 1;
+        static final int MSG_DEVICE_REMOVED = 2;
+        static final int MSG_DEVICE_CHANGED = 3;
+
+        InputDeviceListenerDelegate(InputDeviceListener listener, Handler handler) {
+            super(handler != null ? handler.getLooper() : Looper.myLooper());
+            mListener = listener;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_DEVICE_ADDED:
+                    mListener.onInputDeviceAdded(msg.arg1);
+                    break;
+                case MSG_DEVICE_REMOVED:
+                    mListener.onInputDeviceRemoved(msg.arg1);
+                    break;
+                case MSG_DEVICE_CHANGED:
+                    mListener.onInputDeviceChanged(msg.arg1);
+                    break;
+            }
+        }
+    }
+
+    private static boolean containsDeviceId(int[] deviceIdAndGeneration, int deviceId) {
+        for (int i = 0; i < deviceIdAndGeneration.length; i += 2) {
+            if (deviceIdAndGeneration[i] == deviceId) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @GuardedBy("mInputDeviceListeners")
+    private void sendMessageToInputDeviceListenersLocked(int what, int deviceId) {
+        final int numListeners = mInputDeviceListeners.size();
+        for (int i = 0; i < numListeners; i++) {
+            InputDeviceListenerDelegate listener = mInputDeviceListeners.get(i);
+            listener.sendMessage(listener.obtainMessage(what, deviceId, 0));
+        }
+    }
+
+    /**
+     * @see InputManager#registerInputDeviceListener
+     */
+    void registerInputDeviceListener(InputDeviceListener listener, Handler handler) {
+        if (listener == null) {
+            throw new IllegalArgumentException("listener must not be null");
+        }
+
+        synchronized (mInputDeviceListeners) {
+            populateInputDevicesLocked();
+            int index = findInputDeviceListenerLocked(listener);
+            if (index < 0) {
+                mInputDeviceListeners.add(new InputDeviceListenerDelegate(listener, handler));
+            }
+        }
+    }
+
+    /**
+     * @see InputManager#unregisterInputDeviceListener
+     */
+    void unregisterInputDeviceListener(InputDeviceListener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException("listener must not be null");
+        }
+
+        synchronized (mInputDeviceListeners) {
+            int index = findInputDeviceListenerLocked(listener);
+            if (index >= 0) {
+                InputDeviceListenerDelegate d = mInputDeviceListeners.get(index);
+                d.removeCallbacksAndMessages(null);
+                mInputDeviceListeners.remove(index);
+            }
+        }
+    }
+
+    @GuardedBy("mInputDeviceListeners")
+    private int findInputDeviceListenerLocked(InputDeviceListener listener) {
+        final int numListeners = mInputDeviceListeners.size();
+        for (int i = 0; i < numListeners; i++) {
+            if (mInputDeviceListeners.get(i).mListener == listener) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * @see InputManager#getInputDeviceIds
+     */
+    public int[] getInputDeviceIds() {
+        synchronized (mInputDeviceListeners) {
+            populateInputDevicesLocked();
+
+            final int count = mInputDevices.size();
+            final int[] ids = new int[count];
+            for (int i = 0; i < count; i++) {
+                ids[i] = mInputDevices.keyAt(i);
+            }
+            return ids;
+        }
+    }
+
+    /**
+     * @see InputManager#getInputDeviceByDescriptor
+     */
+    InputDevice getInputDeviceByDescriptor(String descriptor) {
+        if (descriptor == null) {
+            throw new IllegalArgumentException("descriptor must not be null.");
+        }
+
+        synchronized (mInputDeviceListeners) {
+            populateInputDevicesLocked();
+
+            int numDevices = mInputDevices.size();
+            for (int i = 0; i < numDevices; i++) {
+                InputDevice inputDevice = mInputDevices.valueAt(i);
+                if (inputDevice == null) {
+                    int id = mInputDevices.keyAt(i);
+                    try {
+                        inputDevice = mIm.getInputDevice(id);
+                    } catch (RemoteException ex) {
+                        throw ex.rethrowFromSystemServer();
+                    }
+                    if (inputDevice == null) {
+                        continue;
+                    }
+                    mInputDevices.setValueAt(i, inputDevice);
+                }
+                if (descriptor.equals(inputDevice.getDescriptor())) {
+                    return inputDevice;
+                }
+            }
+            return null;
+        }
+    }
+
+    /**
+     * @see InputManager#getHostUsiVersion
+     */
+    @Nullable
+    HostUsiVersion getHostUsiVersion(@NonNull Display display) {
+        Objects.requireNonNull(display, "display should not be null");
+
+        // Return the first valid USI version reported by any input device associated with
+        // the display.
+        synchronized (mInputDeviceListeners) {
+            populateInputDevicesLocked();
+
+            for (int i = 0; i < mInputDevices.size(); i++) {
+                final InputDevice device = getInputDevice(mInputDevices.keyAt(i));
+                if (device != null && device.getAssociatedDisplayId() == display.getDisplayId()) {
+                    if (device.getHostUsiVersion() != null) {
+                        return device.getHostUsiVersion();
+                    }
+                }
+            }
+        }
+
+        // If there are no input devices that report a valid USI version, see if there is a config
+        // that specifies the USI version for the display. This is to handle cases where the USI
+        // input device is not registered by the kernel/driver all the time.
+        try {
+            return mIm.getHostUsiVersionFromDisplayConfig(display.getDisplayId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    private void onTabletModeChanged(long whenNanos, boolean inTabletMode) {
+        if (DEBUG) {
+            Log.d(TAG, "Received tablet mode changed: "
+                    + "whenNanos=" + whenNanos + ", inTabletMode=" + inTabletMode);
+        }
+        synchronized (mOnTabletModeChangedListeners) {
+            final int numListeners = mOnTabletModeChangedListeners.size();
+            for (int i = 0; i < numListeners; i++) {
+                OnTabletModeChangedListenerDelegate listener =
+                        mOnTabletModeChangedListeners.get(i);
+                listener.sendTabletModeChanged(whenNanos, inTabletMode);
+            }
+        }
+    }
+
+    private final class TabletModeChangedListener extends ITabletModeChangedListener.Stub {
+        @Override
+        public void onTabletModeChanged(long whenNanos, boolean inTabletMode) {
+            InputManagerGlobal.this.onTabletModeChanged(whenNanos, inTabletMode);
+        }
+    }
+
+    private static final class OnTabletModeChangedListenerDelegate extends Handler {
+        private static final int MSG_TABLET_MODE_CHANGED = 0;
+
+        public final OnTabletModeChangedListener mListener;
+
+        OnTabletModeChangedListenerDelegate(
+                OnTabletModeChangedListener listener, Handler handler) {
+            super(handler != null ? handler.getLooper() : Looper.myLooper());
+            mListener = listener;
+        }
+
+        public void sendTabletModeChanged(long whenNanos, boolean inTabletMode) {
+            SomeArgs args = SomeArgs.obtain();
+            args.argi1 = (int) whenNanos;
+            args.argi2 = (int) (whenNanos >> 32);
+            args.arg1 = inTabletMode;
+            obtainMessage(MSG_TABLET_MODE_CHANGED, args).sendToTarget();
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.what == MSG_TABLET_MODE_CHANGED) {
+                SomeArgs args = (SomeArgs) msg.obj;
+                long whenNanos = (args.argi1 & 0xFFFFFFFFL) | ((long) args.argi2 << 32);
+                boolean inTabletMode = (boolean) args.arg1;
+                mListener.onTabletModeChanged(whenNanos, inTabletMode);
+            }
+        }
+    }
+
+    /**
+     * @see InputManager#registerInputDeviceListener(InputDeviceListener, Handler)
+     */
+    void registerOnTabletModeChangedListener(
+            OnTabletModeChangedListener listener, Handler handler) {
+        if (listener == null) {
+            throw new IllegalArgumentException("listener must not be null");
+        }
+        synchronized (mOnTabletModeChangedListeners) {
+            if (mOnTabletModeChangedListeners == null) {
+                initializeTabletModeListenerLocked();
+            }
+            int idx = findOnTabletModeChangedListenerLocked(listener);
+            if (idx < 0) {
+                OnTabletModeChangedListenerDelegate d =
+                        new OnTabletModeChangedListenerDelegate(listener, handler);
+                mOnTabletModeChangedListeners.add(d);
+            }
+        }
+    }
+
+    /**
+     * @see InputManager#unregisterOnTabletModeChangedListener(OnTabletModeChangedListener)
+     */
+    void unregisterOnTabletModeChangedListener(OnTabletModeChangedListener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException("listener must not be null");
+        }
+        synchronized (mOnTabletModeChangedListeners) {
+            int idx = findOnTabletModeChangedListenerLocked(listener);
+            if (idx >= 0) {
+                OnTabletModeChangedListenerDelegate d = mOnTabletModeChangedListeners.remove(idx);
+                d.removeCallbacksAndMessages(null);
+            }
+        }
+    }
+
+    @GuardedBy("mOnTabletModeChangedListeners")
+    private void initializeTabletModeListenerLocked() {
+        final TabletModeChangedListener listener = new TabletModeChangedListener();
+        try {
+            mIm.registerTabletModeChangedListener(listener);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    @GuardedBy("mOnTabletModeChangedListeners")
+    private int findOnTabletModeChangedListenerLocked(OnTabletModeChangedListener listener) {
+        final int n = mOnTabletModeChangedListeners.size();
+        for (int i = 0; i < n; i++) {
+            if (mOnTabletModeChangedListeners.get(i).mListener == listener) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    private static final class RegisteredBatteryListeners {
+        final List<InputDeviceBatteryListenerDelegate> mDelegates = new ArrayList<>();
+        IInputDeviceBatteryState mInputDeviceBatteryState;
+    }
+
+    private static final class InputDeviceBatteryListenerDelegate {
+        final InputDeviceBatteryListener mListener;
+        final Executor mExecutor;
+
+        InputDeviceBatteryListenerDelegate(InputDeviceBatteryListener listener, Executor executor) {
+            mListener = listener;
+            mExecutor = executor;
+        }
+
+        void notifyBatteryStateChanged(IInputDeviceBatteryState state) {
+            mExecutor.execute(() ->
+                    mListener.onBatteryStateChanged(state.deviceId, state.updateTime,
+                            new LocalBatteryState(state.isPresent, state.status, state.capacity)));
+        }
+    }
+
+    /**
+     * @see InputManager#addInputDeviceBatteryListener(int, Executor, InputDeviceBatteryListener)
+     */
+    void addInputDeviceBatteryListener(int deviceId, @NonNull Executor executor,
+            @NonNull InputDeviceBatteryListener listener) {
+        Objects.requireNonNull(executor, "executor should not be null");
+        Objects.requireNonNull(listener, "listener should not be null");
+
+        synchronized (mBatteryListenersLock) {
+            if (mBatteryListeners == null) {
+                mBatteryListeners = new SparseArray<>();
+                mInputDeviceBatteryListener = new LocalInputDeviceBatteryListener();
+            }
+            RegisteredBatteryListeners listenersForDevice = mBatteryListeners.get(deviceId);
+            if (listenersForDevice == null) {
+                // The deviceId is currently not being monitored for battery changes.
+                // Start monitoring the device.
+                listenersForDevice = new RegisteredBatteryListeners();
+                mBatteryListeners.put(deviceId, listenersForDevice);
+                try {
+                    mIm.registerBatteryListener(deviceId, mInputDeviceBatteryListener);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            } else {
+                // The deviceId is already being monitored for battery changes.
+                // Ensure that the listener is not already registered.
+                final int numDelegates = listenersForDevice.mDelegates.size();
+                for (int i = 0; i < numDelegates; i++) {
+                    InputDeviceBatteryListener registeredListener =
+                            listenersForDevice.mDelegates.get(i).mListener;
+                    if (Objects.equals(listener, registeredListener)) {
+                        throw new IllegalArgumentException(
+                                "Attempting to register an InputDeviceBatteryListener that has "
+                                        + "already been registered for deviceId: "
+                                        + deviceId);
+                    }
+                }
+            }
+            final InputDeviceBatteryListenerDelegate delegate =
+                    new InputDeviceBatteryListenerDelegate(listener, executor);
+            listenersForDevice.mDelegates.add(delegate);
+
+            // Notify the listener immediately if we already have the latest battery state.
+            if (listenersForDevice.mInputDeviceBatteryState != null) {
+                delegate.notifyBatteryStateChanged(listenersForDevice.mInputDeviceBatteryState);
+            }
+        }
+    }
+
+    /**
+     * @see InputManager#removeInputDeviceBatteryListener(int, InputDeviceBatteryListener)
+     */
+    void removeInputDeviceBatteryListener(int deviceId,
+            @NonNull InputDeviceBatteryListener listener) {
+        Objects.requireNonNull(listener, "listener should not be null");
+
+        synchronized (mBatteryListenersLock) {
+            if (mBatteryListeners == null) {
+                return;
+            }
+            RegisteredBatteryListeners listenersForDevice = mBatteryListeners.get(deviceId);
+            if (listenersForDevice == null) {
+                // The deviceId is not currently being monitored.
+                return;
+            }
+            final List<InputDeviceBatteryListenerDelegate> delegates =
+                    listenersForDevice.mDelegates;
+            for (int i = 0; i < delegates.size();) {
+                if (Objects.equals(listener, delegates.get(i).mListener)) {
+                    delegates.remove(i);
+                    continue;
+                }
+                i++;
+            }
+            if (!delegates.isEmpty()) {
+                return;
+            }
+
+            // There are no more battery listeners for this deviceId. Stop monitoring this device.
+            mBatteryListeners.remove(deviceId);
+            try {
+                mIm.unregisterBatteryListener(deviceId, mInputDeviceBatteryListener);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+            if (mBatteryListeners.size() == 0) {
+                // There are no more devices being monitored, so the registered
+                // IInputDeviceBatteryListener will be automatically dropped by the server.
+                mBatteryListeners = null;
+                mInputDeviceBatteryListener = null;
+            }
+        }
+    }
+
+    private class LocalInputDeviceBatteryListener extends IInputDeviceBatteryListener.Stub {
+        @Override
+        public void onBatteryStateChanged(IInputDeviceBatteryState state) {
+            synchronized (mBatteryListenersLock) {
+                if (mBatteryListeners == null) return;
+                final RegisteredBatteryListeners entry = mBatteryListeners.get(state.deviceId);
+                if (entry == null) return;
+
+                entry.mInputDeviceBatteryState = state;
+                final int numDelegates = entry.mDelegates.size();
+                for (int i = 0; i < numDelegates; i++) {
+                    entry.mDelegates.get(i)
+                            .notifyBatteryStateChanged(entry.mInputDeviceBatteryState);
+                }
+            }
+        }
+    }
+
+    /**
+     * @see InputManager#getInputDeviceBatteryState(int, boolean)
+     */
+    @NonNull
+    BatteryState getInputDeviceBatteryState(int deviceId, boolean hasBattery) {
+        if (!hasBattery) {
+            return new LocalBatteryState();
+        }
+        try {
+            final IInputDeviceBatteryState state = mIm.getBatteryState(deviceId);
+            return new LocalBatteryState(state.isPresent, state.status, state.capacity);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    // Implementation of the android.hardware.BatteryState interface used to report the battery
+    // state via the InputDevice#getBatteryState() and InputDeviceBatteryListener interfaces.
+    private static final class LocalBatteryState extends BatteryState {
+        private final boolean mIsPresent;
+        private final int mStatus;
+        private final float mCapacity;
+
+        LocalBatteryState() {
+            this(false /*isPresent*/, BatteryState.STATUS_UNKNOWN, Float.NaN /*capacity*/);
+        }
+
+        LocalBatteryState(boolean isPresent, int status, float capacity) {
+            mIsPresent = isPresent;
+            mStatus = status;
+            mCapacity = capacity;
+        }
+
+        @Override
+        public boolean isPresent() {
+            return mIsPresent;
+        }
+
+        @Override
+        public int getStatus() {
+            return mStatus;
+        }
+
+        @Override
+        public float getCapacity() {
+            return mCapacity;
+        }
+    }
+
+    private static final class KeyboardBacklightListenerDelegate {
+        final InputManager.KeyboardBacklightListener mListener;
+        final Executor mExecutor;
+
+        KeyboardBacklightListenerDelegate(KeyboardBacklightListener listener, Executor executor) {
+            mListener = listener;
+            mExecutor = executor;
+        }
+
+        void notifyKeyboardBacklightChange(int deviceId, IKeyboardBacklightState state,
+                boolean isTriggeredByKeyPress) {
+            mExecutor.execute(() ->
+                    mListener.onKeyboardBacklightChanged(deviceId,
+                            new LocalKeyboardBacklightState(state.brightnessLevel,
+                                    state.maxBrightnessLevel), isTriggeredByKeyPress));
+        }
+    }
+
+    private class LocalKeyboardBacklightListener extends IKeyboardBacklightListener.Stub {
+
+        @Override
+        public void onBrightnessChanged(int deviceId, IKeyboardBacklightState state,
+                boolean isTriggeredByKeyPress) {
+            synchronized (mKeyboardBacklightListenerLock) {
+                if (mKeyboardBacklightListeners == null) return;
+                final int numListeners = mKeyboardBacklightListeners.size();
+                for (int i = 0; i < numListeners; i++) {
+                    mKeyboardBacklightListeners.get(i)
+                            .notifyKeyboardBacklightChange(deviceId, state, isTriggeredByKeyPress);
+                }
+            }
+        }
+    }
+
+    // Implementation of the android.hardware.input.KeyboardBacklightState interface used to report
+    // the keyboard backlight state via the KeyboardBacklightListener interfaces.
+    private static final class LocalKeyboardBacklightState extends KeyboardBacklightState {
+
+        private final int mBrightnessLevel;
+        private final int mMaxBrightnessLevel;
+
+        LocalKeyboardBacklightState(int brightnessLevel, int maxBrightnessLevel) {
+            mBrightnessLevel = brightnessLevel;
+            mMaxBrightnessLevel = maxBrightnessLevel;
+        }
+
+        @Override
+        public int getBrightnessLevel() {
+            return mBrightnessLevel;
+        }
+
+        @Override
+        public int getMaxBrightnessLevel() {
+            return mMaxBrightnessLevel;
+        }
+    }
+
+    /**
+     * @see InputManager#registerKeyboardBacklightListener(Executor, KeyboardBacklightListener)
+     */
+    @RequiresPermission(Manifest.permission.MONITOR_KEYBOARD_BACKLIGHT)
+    void registerKeyboardBacklightListener(@NonNull Executor executor,
+            @NonNull KeyboardBacklightListener listener) throws IllegalArgumentException {
+        Objects.requireNonNull(executor, "executor should not be null");
+        Objects.requireNonNull(listener, "listener should not be null");
+
+        synchronized (mKeyboardBacklightListenerLock) {
+            if (mKeyboardBacklightListener == null) {
+                mKeyboardBacklightListeners = new ArrayList<>();
+                mKeyboardBacklightListener = new LocalKeyboardBacklightListener();
+
+                try {
+                    mIm.registerKeyboardBacklightListener(mKeyboardBacklightListener);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+            final int numListeners = mKeyboardBacklightListeners.size();
+            for (int i = 0; i < numListeners; i++) {
+                if (mKeyboardBacklightListeners.get(i).mListener == listener) {
+                    throw new IllegalArgumentException("Listener has already been registered!");
+                }
+            }
+            KeyboardBacklightListenerDelegate delegate =
+                    new KeyboardBacklightListenerDelegate(listener, executor);
+            mKeyboardBacklightListeners.add(delegate);
+        }
+    }
+
+    /**
+     * @see InputManager#unregisterKeyboardBacklightListener(KeyboardBacklightListener)
+     */
+    @RequiresPermission(Manifest.permission.MONITOR_KEYBOARD_BACKLIGHT)
+    void unregisterKeyboardBacklightListener(
+            @NonNull KeyboardBacklightListener listener) {
+        Objects.requireNonNull(listener, "listener should not be null");
+
+        synchronized (mKeyboardBacklightListenerLock) {
+            if (mKeyboardBacklightListeners == null) {
+                return;
+            }
+            mKeyboardBacklightListeners.removeIf((delegate) -> delegate.mListener == listener);
+            if (mKeyboardBacklightListeners.isEmpty()) {
+                try {
+                    mIm.unregisterKeyboardBacklightListener(mKeyboardBacklightListener);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+                mKeyboardBacklightListeners = null;
+                mKeyboardBacklightListener = null;
+            }
+        }
+    }
+
+    /**
+     * @see InputManager#getInputDeviceSensorManager(int)
+     */
+    @NonNull
+    SensorManager getInputDeviceSensorManager(int deviceId) {
+        if (mInputDeviceSensorManager == null) {
+            mInputDeviceSensorManager = new InputDeviceSensorManager(this);
+        }
+        return mInputDeviceSensorManager.getSensorManager(deviceId);
+    }
+
+    /**
+     * @see InputManager#getSensorList(int)
+     */
+    InputSensorInfo[] getSensorList(int deviceId) {
+        try {
+            return mIm.getSensorList(deviceId);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @see InputManager#enableSensor(int, int, int, int)
+     */
+    boolean enableSensor(int deviceId, int sensorType, int samplingPeriodUs,
+            int maxBatchReportLatencyUs) {
+        try {
+            return mIm.enableSensor(deviceId, sensorType, samplingPeriodUs,
+                    maxBatchReportLatencyUs);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @see InputManager#disableSensor(int, int)
+     */
+    void disableSensor(int deviceId, int sensorType) {
+        try {
+            mIm.disableSensor(deviceId, sensorType);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @see InputManager#flushSensor(int, int)
+     */
+    boolean flushSensor(int deviceId, int sensorType) {
+        try {
+            return mIm.flushSensor(deviceId, sensorType);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @see InputManager#registerSensorListener(IInputSensorEventListener)
+     */
+    boolean registerSensorListener(IInputSensorEventListener listener) {
+        try {
+            return mIm.registerSensorListener(listener);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @see InputManager#unregisterSensorListener(IInputSensorEventListener)
+     */
+    void unregisterSensorListener(IInputSensorEventListener listener) {
+        try {
+            mIm.unregisterSensorListener(listener);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 9689be2..244632a 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -64,17 +64,27 @@
     /**
      * The product name for attestation. In non-default builds (like the AOSP build) the value of
      * the 'PRODUCT' system property may be different to the one provisioned to KeyMint,
-     * and Keymint attestation would still attest to the product name, it's running on.
+     * and Keymint attestation would still attest to the product name which was provisioned.
      * @hide
      */
     @Nullable
     @TestApi
-    public static final String PRODUCT_FOR_ATTESTATION =
-            getString("ro.product.name_for_attestation");
+    public static final String PRODUCT_FOR_ATTESTATION = getVendorDeviceIdProperty("name");
 
     /** The name of the industrial design. */
     public static final String DEVICE = getString("ro.product.device");
 
+    /**
+     * The device name for attestation. In non-default builds (like the AOSP build) the value of
+     * the 'DEVICE' system property may be different to the one provisioned to KeyMint,
+     * and Keymint attestation would still attest to the device name which was provisioned.
+     * @hide
+     */
+    @Nullable
+    @TestApi
+    public static final String DEVICE_FOR_ATTESTATION =
+            getVendorDeviceIdProperty("device");
+
     /** The name of the underlying board, like "goldfish". */
     public static final String BOARD = getString("ro.product.board");
 
@@ -97,19 +107,29 @@
     /** The manufacturer of the product/hardware. */
     public static final String MANUFACTURER = getString("ro.product.manufacturer");
 
+    /**
+     * The manufacturer name for attestation. In non-default builds (like the AOSP build) the value
+     * of the 'MANUFACTURER' system property may be different to the one provisioned to KeyMint,
+     * and Keymint attestation would still attest to the manufacturer which was provisioned.
+     * @hide
+     */
+    @Nullable
+    @TestApi
+    public static final String MANUFACTURER_FOR_ATTESTATION =
+            getVendorDeviceIdProperty("manufacturer");
+
     /** The consumer-visible brand with which the product/hardware will be associated, if any. */
     public static final String BRAND = getString("ro.product.brand");
 
     /**
      * The product brand for attestation. In non-default builds (like the AOSP build) the value of
      * the 'BRAND' system property may be different to the one provisioned to KeyMint,
-     * and Keymint attestation would still attest to the product brand, it's running on.
+     * and Keymint attestation would still attest to the product brand which was provisioned.
      * @hide
      */
     @Nullable
     @TestApi
-    public static final String BRAND_FOR_ATTESTATION =
-                getString("ro.product.brand_for_attestation");
+    public static final String BRAND_FOR_ATTESTATION = getVendorDeviceIdProperty("brand");
 
     /** The end-user-visible name for the end product. */
     public static final String MODEL = getString("ro.product.model");
@@ -117,13 +137,12 @@
     /**
      * The product model for attestation. In non-default builds (like the AOSP build) the value of
      * the 'MODEL' system property may be different to the one provisioned to KeyMint,
-     * and Keymint attestation would still attest to the product model, it's running on.
+     * and Keymint attestation would still attest to the product model which was provisioned.
      * @hide
      */
     @Nullable
     @TestApi
-    public static final String MODEL_FOR_ATTESTATION =
-                getString("ro.product.model_for_attestation");
+    public static final String MODEL_FOR_ATTESTATION = getVendorDeviceIdProperty("model");
 
     /** The manufacturer of the device's primary system-on-chip. */
     @NonNull
@@ -1527,6 +1546,17 @@
     private static String getString(String property) {
         return SystemProperties.get(property, UNKNOWN);
     }
+    /**
+     * Return attestation specific proerties.
+     * @param property model, name, brand, device or manufacturer.
+     * @return property value or UNKNOWN
+     */
+    private static String getVendorDeviceIdProperty(String property) {
+        String attestProp = getString(
+                TextUtils.formatSimple("ro.product.%s_for_attestation", property));
+        return attestProp.equals(UNKNOWN)
+                ? getString(TextUtils.formatSimple("ro.product.vendor.%s", property)) : UNKNOWN;
+    }
 
     private static String[] getStringList(String property, String separator) {
         String value = SystemProperties.get(property);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 290f929..86e678d 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -5601,16 +5601,15 @@
      *
      * @see #KEY_RESTRICTIONS_PENDING
      *
-     * @deprecated Use
+     * <p>Starting from Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+     * it is possible for there to be multiple managing apps on the device with the ability to set
+     * restrictions, e.g. an Enterprise Device Policy Controller (DPC) and a Supervision admin.
+     * This API will only to return the restrictions set by the DPCs. To retrieve restrictions
+     * set by all managing apps, use
      * {@link android.content.RestrictionsManager#getApplicationRestrictionsPerAdmin} instead.
-     * Starting from Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, it is
-     * possible for there to be multiple managing agents on the device with the ability to set
-     * restrictions. This API will only to return the restrictions set by device policy controllers
-     * (DPCs)
      *
      * @see DevicePolicyManager
      */
-    @Deprecated
     @WorkerThread
     @UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.TIRAMISU)
     public Bundle getApplicationRestrictions(String packageName) {
@@ -5623,12 +5622,15 @@
     }
 
     /**
-     * @deprecated Use
+     * <p>Starting from Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+     * it is possible for there to be multiple managing apps on the device with the ability to set
+     * restrictions, e.g. an Enterprise Device Policy Controller (DPC) and a Supervision admin.
+     * This API will only to return the restrictions set by the DPCs. To retrieve restrictions
+     * set by all managing apps, use
      * {@link android.content.RestrictionsManager#getApplicationRestrictionsPerAdmin} instead.
      *
      * @hide
      */
-    @Deprecated
     @WorkerThread
     public Bundle getApplicationRestrictions(String packageName, UserHandle user) {
         try {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 07d265b..127c7a0 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9541,6 +9541,14 @@
         public static final String SCREENSAVER_COMPLICATIONS_ENABLED =
                 "screensaver_complications_enabled";
 
+        /**
+         * Whether home controls are enabled to be shown over the screensaver by the user.
+         *
+         * @hide
+         */
+        public static final String SCREENSAVER_HOME_CONTROLS_ENABLED =
+                "screensaver_home_controls_enabled";
+
 
         /**
          * Default, indicates that the user has not yet started the dock setup flow.
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index ce8af83..d0f3820 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -1362,6 +1362,11 @@
                 if (!ActivityTaskManager.getService().startDreamActivity(i)) {
                     detach();
                 }
+            } catch (SecurityException e) {
+                Log.w(mTag,
+                        "Received SecurityException trying to start DreamActivity. "
+                        + "Aborting dream start.");
+                detach();
             } catch (RemoteException e) {
                 Log.w(mTag, "Could not connect to activity task manager to start dream activity");
                 e.rethrowFromSystemServer();
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/speech/IRecognitionService.aidl b/core/java/android/speech/IRecognitionService.aidl
index 3134dcd..1148fe3 100644
--- a/core/java/android/speech/IRecognitionService.aidl
+++ b/core/java/android/speech/IRecognitionService.aidl
@@ -78,23 +78,10 @@
      * information see {@link #checkRecognitionSupport},  {@link #startListening} and
      * {@link RecognizerIntent}.
      *
-     * Progress can be monitord by calling {@link #setModelDownloadListener} before a trigger.
+     * Progress updates can be received via {@link #IModelDownloadListener}.
      */
-    void triggerModelDownload(in Intent recognizerIntent, in AttributionSource attributionSource);
-
-    /**
-     * Sets listener to received download progress updates. Clients still have to call
-     * {@link #triggerModelDownload} to trigger a model download.
-     */
-    void setModelDownloadListener(
+    void triggerModelDownload(
         in Intent recognizerIntent,
         in AttributionSource attributionSource,
         in IModelDownloadListener listener);
-
-    /**
-     * Clears the listener for model download events attached to a recognitionIntent if any.
-     */
-    void clearModelDownloadListener(
-        in Intent recognizerIntent,
-        in AttributionSource attributionSource);
 }
diff --git a/core/java/android/speech/ModelDownloadListener.java b/core/java/android/speech/ModelDownloadListener.java
index 6c24399..a58ec90c 100644
--- a/core/java/android/speech/ModelDownloadListener.java
+++ b/core/java/android/speech/ModelDownloadListener.java
@@ -22,20 +22,27 @@
  */
 public interface ModelDownloadListener {
     /**
-     * Called by {@link RecognitionService} when there's an update on the download progress.
+     * Called by {@link RecognitionService} only if the download has started after the request.
      *
-     * <p>RecognitionService will call this zero or more times during the download.</p>
+     * <p> The number of calls to this method varies depending of the {@link RecognitionService}
+     * implementation. If the download finished quickly enough, {@link #onSuccess()} may be called
+     * directly. In other cases, this method may be called any number of times during the download.
+     *
+     * @param completedPercent the percentage of download that is completed
      */
     void onProgress(int completedPercent);
 
     /**
-     * Called when {@link RecognitionService} completed the download and it can now be used to
-     * satisfy recognition requests.
+     * This method is called:
+     * <li> if the model is already available;
+     * <li> if the {@link RecognitionService} has started and completed the download.
+     *
+     * <p> Once this method is called, the model can be safely used to satisfy recognition requests.
      */
     void onSuccess();
 
     /**
-     * Called when {@link RecognitionService} scheduled the download but won't satisfy it
+     * Called when {@link RecognitionService} scheduled the download, but won't satisfy it
      * immediately. There will be no further updates on this listener.
      */
     void onScheduled();
diff --git a/core/java/android/speech/RecognitionService.java b/core/java/android/speech/RecognitionService.java
index 4ecec8f..9656f36 100644
--- a/core/java/android/speech/RecognitionService.java
+++ b/core/java/android/speech/RecognitionService.java
@@ -36,9 +36,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
-import android.text.TextUtils;
 import android.util.Log;
-import android.util.Pair;
 
 import com.android.internal.util.function.pooled.PooledLambda;
 
@@ -93,10 +91,6 @@
 
     private static final int MSG_TRIGGER_MODEL_DOWNLOAD = 6;
 
-    private static final int MSG_SET_MODEL_DOWNLOAD_LISTENER = 7;
-
-    private static final int MSG_CLEAR_MODEL_DOWNLOAD_LISTENER = 8;
-
     private final Handler mHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
@@ -120,21 +114,11 @@
                             checkArgs.mIntent, checkArgs.callback, checkArgs.mAttributionSource);
                     break;
                 case MSG_TRIGGER_MODEL_DOWNLOAD:
-                    Pair<Intent, AttributionSource> params =
-                            (Pair<Intent, AttributionSource>) msg.obj;
-                    dispatchTriggerModelDownload(params.first, params.second);
-                    break;
-                case MSG_SET_MODEL_DOWNLOAD_LISTENER:
-                    ModelDownloadListenerArgs dListenerArgs = (ModelDownloadListenerArgs) msg.obj;
-                    dispatchSetModelDownloadListener(
-                            dListenerArgs.mIntent,
-                            dListenerArgs.mListener,
-                            dListenerArgs.mAttributionSource);
-                    break;
-                case MSG_CLEAR_MODEL_DOWNLOAD_LISTENER:
-                    Pair<Intent, AttributionSource> clearDlPair =
-                            (Pair<Intent, AttributionSource>) msg.obj;
-                    dispatchClearModelDownloadListener(clearDlPair.first, clearDlPair.second);
+                    ModelDownloadArgs modelDownloadArgs = (ModelDownloadArgs) msg.obj;
+                    dispatchTriggerModelDownload(
+                            modelDownloadArgs.mIntent,
+                            modelDownloadArgs.mAttributionSource,
+                            modelDownloadArgs.mListener);
                     break;
             }
         }
@@ -239,59 +223,52 @@
 
     private void dispatchTriggerModelDownload(
             Intent intent,
-            AttributionSource attributionSource) {
-        RecognitionService.this.onTriggerModelDownload(intent, attributionSource);
-    }
-
-    private void dispatchSetModelDownloadListener(
-            Intent intent,
-            IModelDownloadListener listener,
-            AttributionSource attributionSource) {
-        RecognitionService.this.setModelDownloadListener(
-                intent,
-                attributionSource,
-                new ModelDownloadListener() {
-                    @Override
-                    public void onProgress(int completedPercent) {
-                        try {
-                            listener.onProgress(completedPercent);
-                        } catch (RemoteException e) {
-                            throw e.rethrowFromSystemServer();
+            AttributionSource attributionSource,
+            IModelDownloadListener listener) {
+        if (listener == null) {
+            RecognitionService.this.onTriggerModelDownload(intent, attributionSource);
+        } else {
+            RecognitionService.this.onTriggerModelDownload(
+                    intent,
+                    attributionSource,
+                    new ModelDownloadListener() {
+                        @Override
+                        public void onProgress(int completedPercent) {
+                            try {
+                                listener.onProgress(completedPercent);
+                            } catch (RemoteException e) {
+                                throw e.rethrowFromSystemServer();
+                            }
                         }
-                    }
 
-                    @Override
-                    public void onSuccess() {
-                        try {
-                            listener.onSuccess();
-                        } catch (RemoteException e) {
-                            throw e.rethrowFromSystemServer();
+                        @Override
+                        public void onSuccess() {
+                            try {
+                                listener.onSuccess();
+                            } catch (RemoteException e) {
+                                throw e.rethrowFromSystemServer();
+                            }
                         }
-                    }
 
-                    @Override
-                    public void onScheduled() {
-                        try {
-                            listener.onScheduled();
-                        } catch (RemoteException e) {
-                            throw e.rethrowFromSystemServer();
+                        @Override
+                        public void onScheduled() {
+                            try {
+                                listener.onScheduled();
+                            } catch (RemoteException e) {
+                                throw e.rethrowFromSystemServer();
+                            }
                         }
-                    }
 
-                    @Override
-                    public void onError(int error) {
-                        try {
-                            listener.onError(error);
-                        } catch (RemoteException e) {
-                            throw e.rethrowFromSystemServer();
+                        @Override
+                        public void onError(int error) {
+                            try {
+                                listener.onError(error);
+                            } catch (RemoteException e) {
+                                throw e.rethrowFromSystemServer();
+                            }
                         }
-                    }
-                });
-    }
-
-    private void dispatchClearModelDownloadListener(
-            Intent intent, AttributionSource attributionSource) {
-        RecognitionService.this.clearModelDownloadListener(intent, attributionSource);
+                    });
+        }
     }
 
     private static class StartListeningArgs {
@@ -323,17 +300,18 @@
         }
     }
 
-    private static class ModelDownloadListenerArgs {
+    private static class ModelDownloadArgs {
         final Intent mIntent;
-        final IModelDownloadListener mListener;
         final AttributionSource mAttributionSource;
+        @Nullable final IModelDownloadListener mListener;
 
-        private ModelDownloadListenerArgs(Intent intent,
-                IModelDownloadListener listener,
-                AttributionSource attributionSource) {
-            mIntent = intent;
+        private ModelDownloadArgs(
+                Intent intent,
+                AttributionSource attributionSource,
+                @Nullable IModelDownloadListener listener) {
+            this.mIntent = intent;
+            this.mAttributionSource = attributionSource;
             this.mListener = listener;
-            mAttributionSource = attributionSource;
         }
     }
 
@@ -443,38 +421,39 @@
     }
 
     /**
-     * Sets a {@link ModelDownloadListener} to receive progress updates after
-     * {@link #onTriggerModelDownload} calls.
+     * Requests the download of the recognizer support for {@code recognizerIntent}.
      *
-     * @param recognizerIntent the request to monitor model download progress for.
-     * @param modelDownloadListener the listener to keep updated.
+     * <p> Provides the calling {@link AttributionSource} to the service implementation so that
+     * permissions and bandwidth could be correctly blamed.
+     *
+     * <p> Client will receive the progress updates via the given {@link ModelDownloadListener}:
+     *
+     * <li> If the model is already available, {@link ModelDownloadListener#onSuccess()} will be
+     * called directly. The model can be safely used afterwards.
+     *
+     * <li> If the {@link RecognitionService} has started the download,
+     * {@link ModelDownloadListener#onProgress(int)} will be called an unspecified (zero or more)
+     * number of times until the download is complete.
+     * When the download finishes, {@link ModelDownloadListener#onSuccess()} will be called.
+     * The model can be safely used afterwards.
+     *
+     * <li> If the {@link RecognitionService} has only scheduled the download, but won't satisfy it
+     * immediately, {@link ModelDownloadListener#onScheduled()} will be called.
+     * There will be no further updates on this listener.
+     *
+     * <li> If the request fails at any time due to a network or scheduling error,
+     * {@link ModelDownloadListener#onError(int)} will be called.
+     *
+     * @param recognizerIntent contains parameters for the recognition to be performed. The intent
+     *        may also contain optional extras, see {@link RecognizerIntent}.
+     * @param attributionSource the attribution source of the caller.
+     * @param listener on which to receive updates about the model download request.
      */
-    public void setModelDownloadListener(
+    public void onTriggerModelDownload(
             @NonNull Intent recognizerIntent,
             @NonNull AttributionSource attributionSource,
-            @NonNull ModelDownloadListener modelDownloadListener) {
-        if (DBG) {
-            Log.i(TAG, TextUtils.formatSimple(
-                    "#setModelDownloadListener [%s] [%s]",
-                    recognizerIntent,
-                    modelDownloadListener));
-        }
-        modelDownloadListener.onError(SpeechRecognizer.ERROR_CANNOT_LISTEN_TO_DOWNLOAD_EVENTS);
-    }
-
-    /**
-     * Clears the {@link ModelDownloadListener} set to receive progress updates for the given
-     * {@code recognizerIntent}, if any.
-     *
-     * @param recognizerIntent the request to monitor model download progress for.
-     */
-    public void clearModelDownloadListener(
-            @NonNull Intent recognizerIntent,
-            @NonNull AttributionSource attributionSource) {
-        if (DBG) {
-            Log.i(TAG, TextUtils.formatSimple(
-                    "#clearModelDownloadListener [%s]", recognizerIntent));
-        }
+            @NonNull ModelDownloadListener listener) {
+        listener.onError(SpeechRecognizer.ERROR_CANNOT_LISTEN_TO_DOWNLOAD_EVENTS);
     }
 
     @Override
@@ -815,41 +794,18 @@
 
         @Override
         public void triggerModelDownload(
-                Intent recognizerIntent, @NonNull AttributionSource attributionSource) {
+                Intent recognizerIntent,
+                @NonNull AttributionSource attributionSource,
+                IModelDownloadListener listener) {
             final RecognitionService service = mServiceRef.get();
             if (service != null) {
                 service.mHandler.sendMessage(
                         Message.obtain(
                                 service.mHandler, MSG_TRIGGER_MODEL_DOWNLOAD,
-                                Pair.create(recognizerIntent, attributionSource)));
-            }
-        }
-
-        @Override
-        public void setModelDownloadListener(
-                Intent recognizerIntent,
-                AttributionSource attributionSource,
-                IModelDownloadListener listener) throws RemoteException {
-            final RecognitionService service = mServiceRef.get();
-            if (service != null) {
-                service.mHandler.sendMessage(
-                        Message.obtain(service.mHandler, MSG_SET_MODEL_DOWNLOAD_LISTENER,
-                                new ModelDownloadListenerArgs(
+                                new ModelDownloadArgs(
                                         recognizerIntent,
-                                        listener,
-                                        attributionSource)));
-            }
-        }
-
-        @Override
-        public void clearModelDownloadListener(
-                Intent recognizerIntent,
-                AttributionSource attributionSource) throws RemoteException {
-            final RecognitionService service = mServiceRef.get();
-            if (service != null) {
-                service.mHandler.sendMessage(
-                        Message.obtain(service.mHandler, MSG_CLEAR_MODEL_DOWNLOAD_LISTENER,
-                                Pair.create(recognizerIntent, attributionSource)));
+                                        attributionSource,
+                                        listener)));
             }
         }
 
diff --git a/core/java/android/speech/SpeechRecognizer.java b/core/java/android/speech/SpeechRecognizer.java
index 76eb09e..dacb25c 100644
--- a/core/java/android/speech/SpeechRecognizer.java
+++ b/core/java/android/speech/SpeechRecognizer.java
@@ -297,8 +297,6 @@
     private static final int MSG_SET_TEMPORARY_ON_DEVICE_COMPONENT = 5;
     private static final int MSG_CHECK_RECOGNITION_SUPPORT = 6;
     private static final int MSG_TRIGGER_MODEL_DOWNLOAD = 7;
-    private static final int MSG_SET_MODEL_DOWNLOAD_LISTENER = 8;
-    private static final int MSG_CLEAR_MODEL_DOWNLOAD_LISTENER = 9;
 
     /** The actual RecognitionService endpoint */
     private IRecognitionService mService;
@@ -341,19 +339,13 @@
                             args.mIntent, args.mCallbackExecutor, args.mCallback);
                     break;
                 case MSG_TRIGGER_MODEL_DOWNLOAD:
-                    handleTriggerModelDownload((Intent) msg.obj);
-                    break;
-                case MSG_SET_MODEL_DOWNLOAD_LISTENER:
                     ModelDownloadListenerArgs modelDownloadListenerArgs =
                             (ModelDownloadListenerArgs) msg.obj;
-                    handleSetModelDownloadListener(
+                    handleTriggerModelDownload(
                             modelDownloadListenerArgs.mIntent,
                             modelDownloadListenerArgs.mExecutor,
                             modelDownloadListenerArgs.mModelDownloadListener);
                     break;
-                case MSG_CLEAR_MODEL_DOWNLOAD_LISTENER:
-                    handleClearModelDownloadListener((Intent) msg.obj);
-                    break;
             }
         }
     };
@@ -657,17 +649,13 @@
      * user interaction to approve the download. Callers can verify the status of the request via
      * {@link #checkRecognitionSupport(Intent, Executor, RecognitionSupportCallback)}.
      *
-     * <p>Listeners set via
-     * {@link #setModelDownloadListener(Intent, Executor, ModelDownloadListener)} will receive
-     * updates about this download request.</p>
-     *
      * @param recognizerIntent contains parameters for the recognition to be performed. The intent
      *        may also contain optional extras, see {@link RecognizerIntent}.
      */
     public void triggerModelDownload(@NonNull Intent recognizerIntent) {
         Objects.requireNonNull(recognizerIntent, "intent must not be null");
         if (DBG) {
-            Slog.i(TAG, "#triggerModelDownload called");
+            Slog.i(TAG, "#triggerModelDownload without a listener called");
             if (mService == null) {
                 Slog.i(TAG, "Connection is not established yet");
             }
@@ -676,23 +664,47 @@
             // First time connection: first establish a connection, then dispatch.
             connectToSystemService();
         }
-        putMessage(Message.obtain(mHandler, MSG_TRIGGER_MODEL_DOWNLOAD, recognizerIntent));
+        putMessage(Message.obtain(
+                mHandler, MSG_TRIGGER_MODEL_DOWNLOAD,
+                new ModelDownloadListenerArgs(recognizerIntent, null, null)));
     }
 
     /**
-     * Sets a listener to model download updates. Clients will have to call this method before
-     * {@link #triggerModelDownload(Intent)}.
+     * Attempts to download the support for the given {@code recognizerIntent}. This might trigger
+     * user interaction to approve the download. Callers can verify the status of the request via
+     * {@link #checkRecognitionSupport(Intent, Executor, RecognitionSupportCallback)}.
      *
-     * @param recognizerIntent the request to monitor support for.
+     * <p> The updates about the model download request are received via the given
+     * {@link ModelDownloadListener}:
+     *
+     * <li> If the model is already available, {@link ModelDownloadListener#onSuccess()} will be
+     * called directly. The model can be safely used afterwards.
+     *
+     * <li> If the {@link RecognitionService} has started the download,
+     * {@link ModelDownloadListener#onProgress(int)} will be called an unspecified (zero or more)
+     * number of times until the download is complete.
+     * When the download finishes, {@link ModelDownloadListener#onSuccess()} will be called.
+     * The model can be safely used afterwards.
+     *
+     * <li> If the {@link RecognitionService} has only scheduled the download, but won't satisfy it
+     * immediately, {@link ModelDownloadListener#onScheduled()} will be called.
+     * There will be no further updates on this listener.
+     *
+     * <li> If the request fails at any time due to a network or scheduling error,
+     * {@link ModelDownloadListener#onError(int)} will be called.
+     *
+     * @param recognizerIntent contains parameters for the recognition to be performed. The intent
+     *        may also contain optional extras, see {@link RecognizerIntent}.
+     * @param executor for dispatching listener callbacks
      * @param listener on which to receive updates about the model download request.
      */
-    public void setModelDownloadListener(
+    public void triggerModelDownload(
             @NonNull Intent recognizerIntent,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull ModelDownloadListener listener) {
         Objects.requireNonNull(recognizerIntent, "intent must not be null");
         if (DBG) {
-            Slog.i(TAG, "#setModelDownloadListener called");
+            Slog.i(TAG, "#triggerModelDownload with a listener called");
             if (mService == null) {
                 Slog.i(TAG, "Connection is not established yet");
             }
@@ -702,32 +714,11 @@
             connectToSystemService();
         }
         putMessage(Message.obtain(
-                mHandler, MSG_SET_MODEL_DOWNLOAD_LISTENER,
+                mHandler, MSG_TRIGGER_MODEL_DOWNLOAD,
                 new ModelDownloadListenerArgs(recognizerIntent, executor, listener)));
     }
 
     /**
-     * Clears the listener for model download updates if any.
-     *
-     * @param recognizerIntent the request to monitor support for.
-     */
-    public void clearModelDownloadListener(@NonNull Intent recognizerIntent) {
-        Objects.requireNonNull(recognizerIntent, "intent must not be null");
-        if (DBG) {
-            Slog.i(TAG, "#clearModelDownloadListener called");
-            if (mService == null) {
-                Slog.i(TAG, "Connection is not established yet");
-            }
-        }
-        if (mService == null) {
-            // First time connection: first establish a connection, then dispatch.
-            connectToSystemService();
-        }
-        putMessage(Message.obtain(
-                mHandler, MSG_CLEAR_MODEL_DOWNLOAD_LISTENER, recognizerIntent));
-    }
-
-    /**
      * Sets a temporary component to power on-device speech recognizer.
      *
      * <p>This is only expected to be called in tests, system would reject calls from client apps.
@@ -836,51 +827,36 @@
         }
     }
 
-    private void handleTriggerModelDownload(Intent recognizerIntent) {
-        if (!maybeInitializeManagerService()) {
-            return;
-        }
-        try {
-            mService.triggerModelDownload(recognizerIntent, mContext.getAttributionSource());
-        } catch (final RemoteException e) {
-            Log.e(TAG, "downloadModel() failed", e);
-            mListener.onError(ERROR_CLIENT);
-        }
-    }
-
-    private void handleSetModelDownloadListener(
+    private void handleTriggerModelDownload(
             Intent recognizerIntent,
-            Executor callbackExecutor,
+            @Nullable Executor callbackExecutor,
             @Nullable ModelDownloadListener modelDownloadListener) {
         if (!maybeInitializeManagerService()) {
             return;
         }
-        try {
-            InternalModelDownloadListener listener =
-                    modelDownloadListener == null
-                            ? null
-                            : new InternalModelDownloadListener(
-                                    callbackExecutor,
-                                    modelDownloadListener);
-            mService.setModelDownloadListener(
-                    recognizerIntent, mContext.getAttributionSource(), listener);
-            if (DBG) Log.d(TAG, "setModelDownloadListener()");
-        } catch (final RemoteException e) {
-            Log.e(TAG, "setModelDownloadListener() failed", e);
-            callbackExecutor.execute(() -> modelDownloadListener.onError(ERROR_CLIENT));
-        }
-    }
 
-    private void handleClearModelDownloadListener(Intent recognizerIntent) {
-        if (!maybeInitializeManagerService()) {
-            return;
+        // Trigger model download without a listener.
+        if (modelDownloadListener == null) {
+            try {
+                mService.triggerModelDownload(
+                        recognizerIntent, mContext.getAttributionSource(), null);
+                if (DBG) Log.d(TAG, "triggerModelDownload() without a listener");
+            } catch (final RemoteException e) {
+                Log.e(TAG, "triggerModelDownload() without a listener failed", e);
+                mListener.onError(ERROR_CLIENT);
+            }
         }
-        try {
-            mService.clearModelDownloadListener(
-                    recognizerIntent, mContext.getAttributionSource());
-            if (DBG) Log.d(TAG, "clearModelDownloadListener()");
-        } catch (final RemoteException e) {
-            Log.e(TAG, "clearModelDownloadListener() failed", e);
+        // Trigger model download with a listener.
+        else {
+            try {
+                mService.triggerModelDownload(
+                        recognizerIntent, mContext.getAttributionSource(),
+                        new InternalModelDownloadListener(callbackExecutor, modelDownloadListener));
+                if (DBG) Log.d(TAG, "triggerModelDownload() with a listener");
+            } catch (final RemoteException e) {
+                Log.e(TAG, "triggerModelDownload() with a listener failed", e);
+                callbackExecutor.execute(() -> modelDownloadListener.onError(ERROR_CLIENT));
+            }
         }
     }
 
diff --git a/core/java/android/text/TextFlags.java b/core/java/android/text/TextFlags.java
new file mode 100644
index 0000000..9f11e31
--- /dev/null
+++ b/core/java/android/text/TextFlags.java
@@ -0,0 +1,49 @@
+/*
+ * 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.text;
+
+/**
+ * Flags in the "text" namespace.
+ *
+ * @hide
+ */
+public final class TextFlags {
+
+    /**
+     * The name space of the "text" feature.
+     *
+     * This needs to move to DeviceConfig constant.
+     */
+    public static final String NAMESPACE = "text";
+
+    /**
+     * Whether we use the new design of context menu.
+     */
+    public static final String ENABLE_NEW_CONTEXT_MENU =
+            "TextEditing__enable_new_context_menu";
+
+    /**
+     * The key name used in app core settings for {@link #ENABLE_NEW_CONTEXT_MENU}.
+     */
+    public static final String KEY_ENABLE_NEW_CONTEXT_MENU = "text__enable_new_context_menu";
+
+    /**
+     * Default value for the flag {@link #ENABLE_NEW_CONTEXT_MENU}.
+     */
+    public static final boolean ENABLE_NEW_CONTEXT_MENU_DEFAULT = false;
+
+}
diff --git a/core/java/android/text/method/QwertyKeyListener.java b/core/java/android/text/method/QwertyKeyListener.java
index b4a1e8c..c43864d 100644
--- a/core/java/android/text/method/QwertyKeyListener.java
+++ b/core/java/android/text/method/QwertyKeyListener.java
@@ -362,6 +362,15 @@
 
                 return true;
             }
+        } else if (keyCode == KeyEvent.KEYCODE_ESCAPE && event.hasNoModifiers()) {
+            // If user is in the process of composing with a dead key, and
+            // presses Escape, cancel it. We need special handling because
+            // the Escape key will not produce a Unicode character
+            if (activeStart == selStart && activeEnd == selEnd) {
+                Selection.setSelection(content, selEnd);
+                content.removeSpan(TextKeyListener.ACTIVE);
+                return true;
+            }
         }
 
         return super.onKeyDown(view, content, keyCode, event);
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 2ae882c..6201b3a 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -220,9 +220,9 @@
         DEFAULT_FLAGS.put(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME, "true");
         DEFAULT_FLAGS.put(SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, "true");
         DEFAULT_FLAGS.put(SETTINGS_AUTO_TEXT_WRAPPING, "false");
-        DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_UI, "false");
-        DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY, "false");
-        DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD, "false");
+        DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_UI, "true");
+        DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY, "true");
+        DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD, "true");
         DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE, "false");
         DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA, "true");
         DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA_PHASE2, "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/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index a47783c..6b60442 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -205,6 +205,16 @@
     }
 
     /**
+     * Notify HandwritingInitiator that a delegate view (see {@link View#isHandwritingDelegate})
+     * gained focus.
+     */
+    public void onDelegateViewFocused(@NonNull View view) {
+        if (view == getConnectedView()) {
+            tryAcceptStylusHandwritingDelegation(view);
+        }
+    }
+
+    /**
      * Notify HandwritingInitiator that a new InputConnection is created.
      * The caller of this method should guarantee that each onInputConnectionCreated call
      * is paired with a onInputConnectionClosed call.
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 720f569..9225cd9 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -29,6 +29,7 @@
 import android.hardware.input.HostUsiVersion;
 import android.hardware.input.InputDeviceIdentifier;
 import android.hardware.input.InputManager;
+import android.hardware.input.InputManagerGlobal;
 import android.hardware.lights.LightsManager;
 import android.icu.util.ULocale;
 import android.os.Build;
@@ -742,7 +743,7 @@
      */
     @Nullable
     public static InputDevice getDevice(int id) {
-        return InputManager.getInstance().getInputDevice(id);
+        return InputManagerGlobal.getInstance().getInputDevice(id);
     }
 
     /**
@@ -750,7 +751,7 @@
      * @return The input device ids.
      */
     public static int[] getDeviceIds() {
-        return InputManager.getInstance().getInputDeviceIds();
+        return InputManagerGlobal.getInstance().getInputDeviceIds();
     }
 
     /**
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 800fc97..aec3487 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -8324,6 +8324,13 @@
             onFocusLost();
         } else if (hasWindowFocus()) {
             notifyFocusChangeToImeFocusController(true /* hasFocus */);
+
+            if (mIsHandwritingDelegate) {
+                ViewRootImpl viewRoot = getViewRootImpl();
+                if (viewRoot != null) {
+                    viewRoot.getHandwritingInitiator().onDelegateViewFocused(this);
+                }
+            }
         }
 
         invalidate(true);
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index c96d298..d80819f 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -28,6 +28,7 @@
 import android.content.res.Resources;
 import android.graphics.Rect;
 import android.hardware.input.InputManager;
+import android.hardware.input.InputManagerGlobal;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.RemoteException;
@@ -1188,7 +1189,7 @@
     }
 
     private static boolean isInputDeviceInfoValid(int id, int axis, int source) {
-        InputDevice device = InputManager.getInstance().getInputDevice(id);
+        InputDevice device = InputManagerGlobal.getInstance().getInputDevice(id);
         return device != null && device.getMotionRange(axis, source) != null;
     }
 
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 24dcb69..fb25e7a6 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -11626,7 +11626,8 @@
 
         mNumPausedForSync++;
         mHandler.removeMessages(MSG_PAUSED_FOR_SYNC_TIMEOUT);
-        mHandler.sendEmptyMessageDelayed(MSG_PAUSED_FOR_SYNC_TIMEOUT, 1000);
+        mHandler.sendEmptyMessageDelayed(MSG_PAUSED_FOR_SYNC_TIMEOUT,
+                1000 * Build.HW_TIMEOUT_MULTIPLIER);
         return mActiveSurfaceSyncGroup;
     };
 
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 9f9a781..dce5432 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -74,6 +74,7 @@
 import android.text.Spanned;
 import android.text.SpannedString;
 import android.text.StaticLayout;
+import android.text.TextFlags;
 import android.text.TextUtils;
 import android.text.method.InsertModeTransformationMethod;
 import android.text.method.KeyListener;
@@ -169,9 +170,6 @@
     private static final String TAG = "Editor";
     private static final boolean DEBUG_UNDO = false;
 
-    // TODO(nona): Make this configurable.
-    private static final boolean FLAG_USE_NEW_CONTEXT_MENU = false;
-
     // Specifies whether to use the magnifier when pressing the insertion or selection handles.
     private static final boolean FLAG_USE_MAGNIFIER = true;
 
@@ -470,6 +468,7 @@
     private static final int LINE_CHANGE_SLOP_MIN_DP = 8;
     private int mLineChangeSlopMax;
     private int mLineChangeSlopMin;
+    private boolean mUseNewContextMenu;
 
     private final AccessibilitySmartActions mA11ySmartActions;
     private InsertModeController mInsertModeController;
@@ -500,6 +499,9 @@
         mLineSlopRatio = AppGlobals.getFloatCoreSetting(
                 WidgetFlags.KEY_LINE_SLOP_RATIO,
                 WidgetFlags.LINE_SLOP_RATIO_DEFAULT);
+        mUseNewContextMenu = AppGlobals.getIntCoreSetting(
+                TextFlags.KEY_ENABLE_NEW_CONTEXT_MENU,
+                TextFlags.ENABLE_NEW_CONTEXT_MENU_DEFAULT ? 1 : 0) != 0;
         if (TextView.DEBUG_CURSOR) {
             logCursor("Editor", "Cursor drag from anywhere is %s.",
                     mFlagCursorDragFromAnywhereEnabled ? "enabled" : "disabled");
@@ -3171,7 +3173,7 @@
         final int menuItemOrderSelectAll;
         final int menuItemOrderShare;
         final int menuItemOrderAutofill;
-        if (FLAG_USE_NEW_CONTEXT_MENU) {
+        if (mUseNewContextMenu) {
             menuItemOrderPasteAsPlainText = 7;
             menuItemOrderSelectAll = 8;
             menuItemOrderShare = 9;
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 1600a16..fd80981 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -462,12 +462,12 @@
     private static final int CHANGE_WATCHER_PRIORITY = 100;
 
     /**
-     * The span priority of the {@link TransformationMethod} that is set on the text. It must be
+     * The span priority of the {@link OffsetMapping} that is set on the text. It must be
      * higher than the {@link DynamicLayout}'s {@link TextWatcher}, so that the transformed text is
      * updated before {@link DynamicLayout#reflow(CharSequence, int, int, int)} being triggered
      * by {@link TextWatcher#onTextChanged(CharSequence, int, int, int)}.
      */
-    private static final int TRANSFORMATION_SPAN_PRIORITY = 200;
+    private static final int OFFSET_MAPPING_SPAN_PRIORITY = 200;
 
     // New state used to change background based on whether this TextView is multiline.
     private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline };
@@ -7033,9 +7033,9 @@
         }
 
         final int textLength = text.length();
+        final boolean isOffsetMapping = mTransformed instanceof OffsetMapping;
 
-        if (text instanceof Spannable && (!mAllowTransformationLengthChange
-                || text instanceof OffsetMapping)) {
+        if (text instanceof Spannable && (!mAllowTransformationLengthChange || isOffsetMapping)) {
             Spannable sp = (Spannable) text;
 
             // Remove any ChangeWatchers that might have come from other TextViews.
@@ -7053,8 +7053,9 @@
             if (mEditor != null) mEditor.addSpanWatchers(sp);
 
             if (mTransformation != null) {
+                final int priority = isOffsetMapping ? OFFSET_MAPPING_SPAN_PRIORITY : 0;
                 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE
-                        | (TRANSFORMATION_SPAN_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
+                        | (priority << Spanned.SPAN_PRIORITY_SHIFT));
             }
 
             if (mMovement != null) {
diff --git a/core/java/android/window/SnapshotDrawerUtils.java b/core/java/android/window/SnapshotDrawerUtils.java
index 1e6c1ff..52e17ca 100644
--- a/core/java/android/window/SnapshotDrawerUtils.java
+++ b/core/java/android/window/SnapshotDrawerUtils.java
@@ -34,6 +34,7 @@
 import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
 import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
 import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST;
@@ -415,6 +416,7 @@
         layoutParams.setFitInsetsIgnoringVisibility(attrs.isFitInsetsIgnoringVisibility());
 
         layoutParams.setTitle(title);
+        layoutParams.inputFeatures |= INPUT_FEATURE_NO_INPUT_CHANNEL;
         return layoutParams;
     }
 
diff --git a/core/java/android/window/SurfaceSyncGroup.java b/core/java/android/window/SurfaceSyncGroup.java
index 0672d63..7f99fb7 100644
--- a/core/java/android/window/SurfaceSyncGroup.java
+++ b/core/java/android/window/SurfaceSyncGroup.java
@@ -21,6 +21,7 @@
 import android.annotation.UiThread;
 import android.os.Binder;
 import android.os.BinderProxy;
+import android.os.Build;
 import android.os.Debug;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -62,7 +63,7 @@
     private static final int MAX_COUNT = 100;
 
     private static final AtomicInteger sCounter = new AtomicInteger(0);
-    private static final int TRANSACTION_READY_TIMEOUT = 1000;
+    private static final int TRANSACTION_READY_TIMEOUT = 1000 * Build.HW_TIMEOUT_MULTIPLIER;
 
     private static Supplier<Transaction> sTransactionFactory = Transaction::new;
 
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index a3209f6..fabb089 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -131,6 +131,19 @@
     }
 
     /**
+     * Sets the densityDpi value in the configuration for the given container.
+     * @hide
+     */
+    @NonNull
+    public WindowContainerTransaction setDensityDpi(@NonNull WindowContainerToken container,
+            int densityDpi) {
+        Change chg = getOrCreateChange(container.asBinder());
+        chg.mConfiguration.densityDpi = densityDpi;
+        chg.mConfigSetMask |= ActivityInfo.CONFIG_DENSITY;
+        return this;
+    }
+
+    /**
      * Notify {@link com.android.server.wm.PinnedTaskController} that the picture-in-picture task
      * has finished the enter animation with the given bounds.
      */
diff --git a/core/java/com/android/internal/app/LocaleHelper.java b/core/java/com/android/internal/app/LocaleHelper.java
index 57bd3f9..d521866 100644
--- a/core/java/com/android/internal/app/LocaleHelper.java
+++ b/core/java/com/android/internal/app/LocaleHelper.java
@@ -20,6 +20,7 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.icu.text.CaseMap;
 import android.icu.text.ListFormatter;
+import android.icu.text.NumberingSystem;
 import android.icu.util.ULocale;
 import android.os.LocaleList;
 import android.text.TextUtils;
@@ -173,6 +174,21 @@
     }
 
     /**
+     * Returns numbering system value of a locale for display in the provided locale.
+     *
+     * @param locale The locale whose key value is displayed.
+     * @param displayLocale The locale in which to display the key value.
+     * @return The string of numbering system.
+     */
+    public static String getDisplayNumberingSystemKeyValue(
+            Locale locale, Locale displayLocale) {
+        ULocale uLocale = new ULocale.Builder()
+                .setUnicodeLocaleKeyword("nu", NumberingSystem.getInstance(locale).getName())
+                .build();
+        return uLocale.getDisplayKeywordValue("numbers", ULocale.forLocale(displayLocale));
+    }
+
+    /**
      * Adds the likely subtags for a provided locale ID.
      *
      * @param locale the locale to maximize.
diff --git a/core/java/com/android/internal/app/LocalePickerWithRegion.java b/core/java/com/android/internal/app/LocalePickerWithRegion.java
index 685bd9a..5dfc0ea 100644
--- a/core/java/com/android/internal/app/LocalePickerWithRegion.java
+++ b/core/java/com/android/internal/app/LocalePickerWithRegion.java
@@ -61,6 +61,7 @@
     private int mTopDistance = 0;
     private CharSequence mTitle = null;
     private OnActionExpandListener mOnActionExpandListener;
+    private boolean mIsNumberingSystem = false;
 
     /**
      * Other classes can register to be notified when a locale was selected.
@@ -90,6 +91,18 @@
         boolean hasSpecificPackageName();
     }
 
+    private static LocalePickerWithRegion createNumberingSystemPicker(
+            LocaleSelectedListener listener, LocaleStore.LocaleInfo parent,
+            boolean translatedOnly, OnActionExpandListener onActionExpandListener,
+            LocaleCollectorBase localePickerCollector) {
+        LocalePickerWithRegion localePicker = new LocalePickerWithRegion();
+        localePicker.setOnActionExpandListener(onActionExpandListener);
+        localePicker.setIsNumberingSystem(true);
+        boolean shouldShowTheList = localePicker.setListener(listener, parent,
+                translatedOnly, localePickerCollector);
+        return shouldShowTheList ? localePicker : null;
+    }
+
     private static LocalePickerWithRegion createCountryPicker(
             LocaleSelectedListener listener, LocaleStore.LocaleInfo parent,
             boolean translatedOnly, OnActionExpandListener onActionExpandListener,
@@ -128,6 +141,10 @@
         return localePicker;
     }
 
+    private void setIsNumberingSystem(boolean isNumberingSystem) {
+        mIsNumberingSystem = isNumberingSystem;
+    }
+
     /**
      * Sets the listener and initializes the locale list.
      *
@@ -184,6 +201,7 @@
         final boolean hasSpecificPackageName =
                 mLocalePickerCollector != null && mLocalePickerCollector.hasSpecificPackageName();
         mAdapter = new SuggestedLocaleAdapter(mLocaleList, countryMode, hasSpecificPackageName);
+        mAdapter.setNumberingSystemMode(mIsNumberingSystem);
         final LocaleHelper.LocaleInfoComparator comp =
                 new LocaleHelper.LocaleInfoComparator(sortingLocale, countryMode);
         mAdapter.sort(comp);
@@ -213,7 +231,6 @@
     @Override
     public void onResume() {
         super.onResume();
-
         if (mParentLocale != null) {
             getActivity().setTitle(mParentLocale.getFullNameNative());
         } else {
@@ -250,16 +267,28 @@
         // Special case for resetting the app locale to equal the system locale.
         boolean isSystemLocale = locale.isSystemLocale();
         boolean isRegionLocale = locale.getParent() != null;
+        boolean mayHaveDifferentNumberingSystem = locale.hasNumberingSystems();
 
-        if (isSystemLocale || isRegionLocale) {
+        if (isSystemLocale
+                || (isRegionLocale && !mayHaveDifferentNumberingSystem)
+                || mIsNumberingSystem) {
             if (mListener != null) {
                 mListener.onLocaleSelected(locale);
             }
             returnToParentFrame();
         } else {
-            LocalePickerWithRegion selector = LocalePickerWithRegion.createCountryPicker(
-                    mListener, locale, mTranslatedOnly /* translate only */,
-                    mOnActionExpandListener, this.mLocalePickerCollector);
+            LocalePickerWithRegion selector;
+            if (mayHaveDifferentNumberingSystem) {
+                selector =
+                        LocalePickerWithRegion.createNumberingSystemPicker(
+                        mListener, locale, mTranslatedOnly /* translate only */,
+                        mOnActionExpandListener, this.mLocalePickerCollector);
+            } else {
+                selector = LocalePickerWithRegion.createCountryPicker(
+                        mListener, locale, mTranslatedOnly /* translate only */,
+                        mOnActionExpandListener, this.mLocalePickerCollector);
+            }
+
             if (selector != null) {
                 getFragmentManager().beginTransaction()
                         .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
diff --git a/core/java/com/android/internal/app/LocaleStore.java b/core/java/com/android/internal/app/LocaleStore.java
index 8b41829..bcbfdc9 100644
--- a/core/java/com/android/internal/app/LocaleStore.java
+++ b/core/java/com/android/internal/app/LocaleStore.java
@@ -39,6 +39,9 @@
 import java.util.Set;
 
 public class LocaleStore {
+    private static final int TIER_LANGUAGE = 1;
+    private static final int TIER_REGION = 2;
+    private static final int TIER_NUMBERING = 3;
     private static final HashMap<String, LocaleInfo> sLocaleCache = new HashMap<>();
     private static final String TAG = LocaleStore.class.getSimpleName();
     private static boolean sFullyInitialized = false;
@@ -68,10 +71,13 @@
         private String mFullCountryNameNative;
         private String mLangScriptKey;
 
+        private boolean mHasNumberingSystems;
+
         private LocaleInfo(Locale locale) {
             this.mLocale = locale;
             this.mId = locale.toLanguageTag();
             this.mParent = getParent(locale);
+            this.mHasNumberingSystems = false;
             this.mIsChecked = false;
             this.mSuggestionFlags = SUGGESTION_TYPE_NONE;
             this.mIsTranslated = false;
@@ -93,6 +99,11 @@
                     .build();
         }
 
+        /** Return true if there are any same locales with different numbering system. */
+        public boolean hasNumberingSystems() {
+            return mHasNumberingSystems;
+        }
+
         @Override
         public String toString() {
             return mId;
@@ -195,6 +206,10 @@
             }
         }
 
+        String getNumberingSystem() {
+            return LocaleHelper.getDisplayNumberingSystemKeyValue(mLocale, mLocale);
+        }
+
         String getContentDescription(boolean countryMode) {
             if (countryMode) {
                 return getFullCountryNameInUiLanguage();
@@ -383,6 +398,12 @@
 
         final boolean isInDeveloperMode = Settings.Global.getInt(context.getContentResolver(),
                 Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
+        Set<Locale> numberSystemLocaleList = new HashSet<>();
+        for (String localeId : LocalePicker.getSupportedLocales(context)) {
+            if (Locale.forLanguageTag(localeId).getUnicodeLocaleType("nu") != null) {
+                numberSystemLocaleList.add(Locale.forLanguageTag(localeId));
+            }
+        }
         for (String localeId : LocalePicker.getSupportedLocales(context)) {
             if (localeId.isEmpty()) {
                 throw new IllformedLocaleException("Bad locale entry in locale_config.xml");
@@ -403,6 +424,12 @@
             if (simCountries.contains(li.getLocale().getCountry())) {
                 li.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_SIM;
             }
+            numberSystemLocaleList.forEach(l -> {
+                if (li.getLocale().stripExtensions().equals(l.stripExtensions())) {
+                    li.mHasNumberingSystems = true;
+                }
+            });
+
             sLocaleCache.put(li.getId(), li);
             final Locale parent = li.getParent();
             if (parent != null) {
@@ -445,20 +472,43 @@
         sFullyInitialized = true;
     }
 
-    private static int getLevel(Set<String> ignorables, LocaleInfo li, boolean translatedOnly) {
-        if (ignorables.contains(li.getId())) return 0;
-        if (li.mIsPseudo) return 2;
-        if (translatedOnly && !li.isTranslated()) return 0;
-        if (li.getParent() != null) return 2;
-        return 0;
+    private static boolean isShallIgnore(
+            Set<String> ignorables, LocaleInfo li, boolean translatedOnly) {
+        if (ignorables.stream().anyMatch(tag ->
+                Locale.forLanguageTag(tag).stripExtensions()
+                        .equals(li.getLocale().stripExtensions()))) {
+            return true;
+        }
+        if (li.mIsPseudo) return false;
+        if (translatedOnly && !li.isTranslated()) return true;
+        if (li.getParent() != null) return false;
+        return true;
+    }
+
+    private static int getLocaleTier(LocaleInfo parent) {
+        if (parent == null) {
+            return TIER_LANGUAGE;
+        } else if (parent.getLocale().getCountry().isEmpty()) {
+            return TIER_REGION;
+        } else {
+            return TIER_NUMBERING;
+        }
     }
 
     /**
      * Returns a list of locales for language or region selection.
+     *
      * If the parent is null, then it is the language list.
+     *
      * If it is not null, then the list will contain all the locales that belong to that parent.
      * Example: if the parent is "ar", then the region list will contain all Arabic locales.
-     * (this is not language based, but language-script, so that it works for zh-Hant and so on.
+     * (this is not language based, but language-script, so that it works for zh-Hant and so on.)
+     *
+     * If it is not null and has country, then the list will contain all locales with that parent's
+     * language and country, i.e. containing alternate numbering systems.
+     *
+     * Example: if the parent is "ff-Adlm-BF", then the numbering list will contain all
+     * Fula (Adlam, Burkina Faso) i.e. "ff-Adlm-BF" and "ff-Adlm-BF-u-nu-latn"
      */
     @UnsupportedAppUsage
     public static Set<LocaleInfo> getLevelLocales(Context context, Set<String> ignorables,
@@ -478,28 +528,49 @@
      */
     public static Set<LocaleInfo> getLevelLocales(Context context, Set<String> ignorables,
             LocaleInfo parent, boolean translatedOnly, LocaleList explicitLocales) {
-        fillCache(context);
-        String parentId = parent == null ? null : parent.getId();
-        HashSet<LocaleInfo> result = new HashSet<>();
+        if (context != null) {
+            fillCache(context);
+        }
         HashMap<String, LocaleInfo> supportedLcoaleInfos =
                 explicitLocales == null
                         ? sLocaleCache
                         : convertExplicitLocales(explicitLocales, sLocaleCache.values());
+        return getTierLocales(ignorables, parent, translatedOnly, supportedLcoaleInfos);
+    }
 
+    private static Set<LocaleInfo> getTierLocales(
+            Set<String> ignorables,
+            LocaleInfo parent,
+            boolean translatedOnly,
+            HashMap<String, LocaleInfo> supportedLcoaleInfos) {
+
+        boolean hasTargetParent = parent != null;
+        String parentId = hasTargetParent ? parent.getId() : null;
+        HashSet<LocaleInfo> result = new HashSet<>();
         for (LocaleStore.LocaleInfo li : supportedLcoaleInfos.values()) {
-            int level = getLevel(ignorables, li, translatedOnly);
-            if (level == 2) {
-                if (parent != null) { // region selection
-                    if (parentId.equals(li.getParent().toLanguageTag())) {
-                        result.add(li);
-                    }
-                } else { // language selection
+            if (isShallIgnore(ignorables, li, translatedOnly)) {
+                continue;
+            }
+            switch(getLocaleTier(parent)) {
+                case TIER_LANGUAGE:
                     if (li.isSuggestionOfType(LocaleInfo.SUGGESTION_TYPE_SIM)) {
                         result.add(li);
                     } else {
-                        result.add(getLocaleInfo(li.getParent()));
+                        result.add(getLocaleInfo(li.getParent(), supportedLcoaleInfos));
                     }
-                }
+                    break;
+                case TIER_REGION:
+                    if (parentId.equals(li.getParent().toLanguageTag())) {
+                        result.add(getLocaleInfo(
+                                li.getLocale().stripExtensions(), supportedLcoaleInfos));
+                    }
+                    break;
+                case TIER_NUMBERING:
+                    if (parent.getLocale().stripExtensions()
+                            .equals(li.getLocale().stripExtensions())) {
+                        result.add(li);
+                    }
+                    break;
             }
         }
         return result;
@@ -538,18 +609,21 @@
     }
 
     private static LocaleList matchLocaleFromSupportedLocaleList(
-            LocaleList explicitLocales, Collection<LocaleInfo> localeinfo) {
+            LocaleList explicitLocales, Collection<LocaleInfo> localeInfos) {
+        if (localeInfos == null) {
+            return explicitLocales;
+        }
         //TODO: Adds a function for unicode extension if needed.
         Locale[] resultLocales = new Locale[explicitLocales.size()];
         for (int i = 0; i < explicitLocales.size(); i++) {
-            Locale locale = explicitLocales.get(i).stripExtensions();
+            Locale locale = explicitLocales.get(i);
             if (!TextUtils.isEmpty(locale.getCountry())) {
-                for (LocaleInfo localeInfo :localeinfo) {
+                for (LocaleInfo localeInfo :localeInfos) {
                     if (LocaleList.matchesLanguageAndScript(locale, localeInfo.getLocale())
                             && TextUtils.equals(locale.getCountry(),
                             localeInfo.getLocale().getCountry())) {
                         resultLocales[i] = localeInfo.getLocale();
-                        continue;
+                        break;
                     }
                 }
             }
@@ -562,18 +636,23 @@
 
     @UnsupportedAppUsage
     public static LocaleInfo getLocaleInfo(Locale locale) {
+        return getLocaleInfo(locale, sLocaleCache);
+    }
+
+    private static LocaleInfo getLocaleInfo(
+            Locale locale, HashMap<String, LocaleInfo> localeInfos) {
         String id = locale.toLanguageTag();
         LocaleInfo result;
-        if (!sLocaleCache.containsKey(id)) {
+        if (!localeInfos.containsKey(id)) {
             // Locale preferences can modify the language tag to current system languages, so we
             // need to check the input locale without extra u extension except numbering system.
             Locale filteredLocale = new Locale.Builder()
                     .setLocale(locale.stripExtensions())
                     .setUnicodeLocaleKeyword("nu", locale.getUnicodeLocaleType("nu"))
                     .build();
-            if (sLocaleCache.containsKey(filteredLocale.toLanguageTag())) {
+            if (localeInfos.containsKey(filteredLocale.toLanguageTag())) {
                 result = new LocaleInfo(locale);
-                LocaleInfo localeInfo = sLocaleCache.get(filteredLocale.toLanguageTag());
+                LocaleInfo localeInfo = localeInfos.get(filteredLocale.toLanguageTag());
                 // This locale is included in supported locales, so follow the settings
                 // of supported locales.
                 result.mIsPseudo = localeInfo.mIsPseudo;
@@ -582,9 +661,9 @@
                 return result;
             }
             result = new LocaleInfo(locale);
-            sLocaleCache.put(id, result);
+            localeInfos.put(id, result);
         } else {
-            result = sLocaleCache.get(id);
+            result = localeInfos.get(id);
         }
         return result;
     }
diff --git a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
index a61a6d7..08de4dfb 100644
--- a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
+++ b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
@@ -64,6 +64,7 @@
     protected ArrayList<LocaleStore.LocaleInfo> mOriginalLocaleOptions;
     protected int mSuggestionCount;
     protected final boolean mCountryMode;
+    protected boolean mIsNumberingMode;
     protected LayoutInflater mInflater;
 
     protected Locale mDisplayLocale = null;
@@ -89,6 +90,14 @@
         }
     }
 
+    public void setNumberingSystemMode(boolean isNumberSystemMode) {
+        mIsNumberingMode = isNumberSystemMode;
+    }
+
+    public boolean getIsForNumberingSystem() {
+        return mIsNumberingMode;
+    }
+
     @Override
     public boolean areAllItemsEnabled() {
         return false;
@@ -209,7 +218,6 @@
         if (convertView == null && mInflater == null) {
             mInflater = LayoutInflater.from(parent.getContext());
         }
-
         int itemType = getItemViewType(position);
         View itemView = getNewViewIfNeeded(convertView, parent, itemType, position);
         switch (itemType) {
@@ -217,13 +225,13 @@
             case TYPE_HEADER_ALL_OTHERS:
                 TextView textView = (TextView) itemView;
                 if (itemType == TYPE_HEADER_SUGGESTED) {
-                   if (mCountryMode) {
+                    if (mCountryMode && !mIsNumberingMode) {
                         setTextTo(textView, R.string.language_picker_regions_section_suggested);
                     } else {
                         setTextTo(textView, R.string.language_picker_section_suggested);
                     }
                 } else {
-                    if (mCountryMode) {
+                    if (mCountryMode && !mIsNumberingMode) {
                         setTextTo(textView, R.string.region_picker_section_all);
                     } else {
                         setTextTo(textView, R.string.language_picker_section_all);
@@ -419,9 +427,11 @@
 
     private void updateTextView(View convertView, TextView text, int position) {
         LocaleStore.LocaleInfo item = (LocaleStore.LocaleInfo) getItem(position);
-        text.setText(item.getLabel(mCountryMode));
+        text.setText(mIsNumberingMode
+                ? item.getNumberingSystem() : item.getLabel(mCountryMode));
         text.setTextLocale(item.getLocale());
-        text.setContentDescription(item.getContentDescription(mCountryMode));
+        text.setContentDescription(mIsNumberingMode
+                        ? item.getNumberingSystem() : item.getContentDescription(mCountryMode));
         if (mCountryMode) {
             int layoutDir = TextUtils.getLayoutDirectionFromLocale(item.getParent());
             //noinspection ResourceType
diff --git a/core/java/com/android/internal/app/procstats/DumpUtils.java b/core/java/com/android/internal/app/procstats/DumpUtils.java
index bce0d60..f6bcc46 100644
--- a/core/java/com/android/internal/app/procstats/DumpUtils.java
+++ b/core/java/com/android/internal/app/procstats/DumpUtils.java
@@ -27,12 +27,12 @@
 import static com.android.internal.app.procstats.ProcessStats.ADJ_SCREEN_OFF;
 import static com.android.internal.app.procstats.ProcessStats.ADJ_SCREEN_ON;
 import static com.android.internal.app.procstats.ProcessStats.STATE_BACKUP;
-import static com.android.internal.app.procstats.ProcessStats.STATE_BOUND_TOP_OR_FGS;
-import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED_ACTIVITY;
-import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED_ACTIVITY_CLIENT;
-import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED_EMPTY;
+import static com.android.internal.app.procstats.ProcessStats.STATE_BOUND_FGS;
+import static com.android.internal.app.procstats.ProcessStats.STATE_BOUND_TOP;
+import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED;
 import static com.android.internal.app.procstats.ProcessStats.STATE_COUNT;
 import static com.android.internal.app.procstats.ProcessStats.STATE_FGS;
+import static com.android.internal.app.procstats.ProcessStats.STATE_FROZEN;
 import static com.android.internal.app.procstats.ProcessStats.STATE_HEAVY_WEIGHT;
 import static com.android.internal.app.procstats.ProcessStats.STATE_HOME;
 import static com.android.internal.app.procstats.ProcessStats.STATE_IMPORTANT_BACKGROUND;
@@ -72,7 +72,8 @@
         STATE_NAMES = new String[STATE_COUNT];
         STATE_NAMES[STATE_PERSISTENT]               = "Persist";
         STATE_NAMES[STATE_TOP]                      = "Top";
-        STATE_NAMES[STATE_BOUND_TOP_OR_FGS]         = "BTopFgs";
+        STATE_NAMES[STATE_BOUND_FGS]                = "BFgs";
+        STATE_NAMES[STATE_BOUND_TOP]                = "BTop";
         STATE_NAMES[STATE_FGS]                      = "Fgs";
         STATE_NAMES[STATE_IMPORTANT_FOREGROUND]     = "ImpFg";
         STATE_NAMES[STATE_IMPORTANT_BACKGROUND]     = "ImpBg";
@@ -83,14 +84,14 @@
         STATE_NAMES[STATE_HEAVY_WEIGHT]             = "HeavyWt";
         STATE_NAMES[STATE_HOME]                     = "Home";
         STATE_NAMES[STATE_LAST_ACTIVITY]            = "LastAct";
-        STATE_NAMES[STATE_CACHED_ACTIVITY]          = "CchAct";
-        STATE_NAMES[STATE_CACHED_ACTIVITY_CLIENT]   = "CchCAct";
-        STATE_NAMES[STATE_CACHED_EMPTY]             = "CchEmty";
+        STATE_NAMES[STATE_CACHED]                   = "Cached";
+        STATE_NAMES[STATE_FROZEN]                   = "Frozen";
 
         STATE_LABELS = new String[STATE_COUNT];
         STATE_LABELS[STATE_PERSISTENT]              = "Persistent";
         STATE_LABELS[STATE_TOP]                     = "       Top";
-        STATE_LABELS[STATE_BOUND_TOP_OR_FGS]        = "Bnd TopFgs";
+        STATE_LABELS[STATE_BOUND_FGS]               = "   Bnd Fgs";
+        STATE_LABELS[STATE_BOUND_TOP]               = "   Bnd Top";
         STATE_LABELS[STATE_FGS]                     = "       Fgs";
         STATE_LABELS[STATE_IMPORTANT_FOREGROUND]    = "    Imp Fg";
         STATE_LABELS[STATE_IMPORTANT_BACKGROUND]    = "    Imp Bg";
@@ -101,16 +102,16 @@
         STATE_LABELS[STATE_HEAVY_WEIGHT]            = " Heavy Wgt";
         STATE_LABELS[STATE_HOME]                    = "    (Home)";
         STATE_LABELS[STATE_LAST_ACTIVITY]           = "(Last Act)";
-        STATE_LABELS[STATE_CACHED_ACTIVITY]         = " (Cch Act)";
-        STATE_LABELS[STATE_CACHED_ACTIVITY_CLIENT]  = "(Cch CAct)";
-        STATE_LABELS[STATE_CACHED_EMPTY]            = "(Cch Emty)";
+        STATE_LABELS[STATE_CACHED]                  = "  (Cached)";
+        STATE_LABELS[STATE_FROZEN]                  = "    Frozen";
         STATE_LABEL_CACHED                          = "  (Cached)";
         STATE_LABEL_TOTAL                           = "     TOTAL";
 
         STATE_NAMES_CSV = new String[STATE_COUNT];
         STATE_NAMES_CSV[STATE_PERSISTENT]               = "pers";
         STATE_NAMES_CSV[STATE_TOP]                      = "top";
-        STATE_NAMES_CSV[STATE_BOUND_TOP_OR_FGS]         = "btopfgs";
+        STATE_NAMES_CSV[STATE_BOUND_FGS]                = "bfgs";
+        STATE_NAMES_CSV[STATE_BOUND_TOP]                = "btop";
         STATE_NAMES_CSV[STATE_FGS]                      = "fgs";
         STATE_NAMES_CSV[STATE_IMPORTANT_FOREGROUND]     = "impfg";
         STATE_NAMES_CSV[STATE_IMPORTANT_BACKGROUND]     = "impbg";
@@ -121,14 +122,14 @@
         STATE_NAMES_CSV[STATE_HEAVY_WEIGHT]             = "heavy";
         STATE_NAMES_CSV[STATE_HOME]                     = "home";
         STATE_NAMES_CSV[STATE_LAST_ACTIVITY]            = "lastact";
-        STATE_NAMES_CSV[STATE_CACHED_ACTIVITY]          = "cch-activity";
-        STATE_NAMES_CSV[STATE_CACHED_ACTIVITY_CLIENT]   = "cch-aclient";
-        STATE_NAMES_CSV[STATE_CACHED_EMPTY]             = "cch-empty";
+        STATE_NAMES_CSV[STATE_CACHED]                   = "cached";
+        STATE_NAMES_CSV[STATE_FROZEN]                   = "frzn";
 
         STATE_TAGS = new String[STATE_COUNT];
         STATE_TAGS[STATE_PERSISTENT]                = "p";
         STATE_TAGS[STATE_TOP]                       = "t";
-        STATE_TAGS[STATE_BOUND_TOP_OR_FGS]          = "d";
+        STATE_TAGS[STATE_BOUND_FGS]                 = "y";
+        STATE_TAGS[STATE_BOUND_TOP]                 = "z";
         STATE_TAGS[STATE_FGS]                       = "g";
         STATE_TAGS[STATE_IMPORTANT_FOREGROUND]      = "f";
         STATE_TAGS[STATE_IMPORTANT_BACKGROUND]      = "b";
@@ -139,15 +140,14 @@
         STATE_TAGS[STATE_HEAVY_WEIGHT]              = "w";
         STATE_TAGS[STATE_HOME]                      = "h";
         STATE_TAGS[STATE_LAST_ACTIVITY]             = "l";
-        STATE_TAGS[STATE_CACHED_ACTIVITY]           = "a";
-        STATE_TAGS[STATE_CACHED_ACTIVITY_CLIENT]    = "c";
-        STATE_TAGS[STATE_CACHED_EMPTY]              = "e";
+        STATE_TAGS[STATE_CACHED]                    = "a";
+        STATE_TAGS[STATE_FROZEN]                    = "e";
 
         STATE_PROTO_ENUMS = new int[STATE_COUNT];
         STATE_PROTO_ENUMS[STATE_PERSISTENT] = ProcessStatsEnums.PROCESS_STATE_PERSISTENT;
         STATE_PROTO_ENUMS[STATE_TOP] = ProcessStatsEnums.PROCESS_STATE_TOP;
-        STATE_PROTO_ENUMS[STATE_BOUND_TOP_OR_FGS] =
-                ProcessStatsEnums.PROCESS_STATE_BOUND_TOP_OR_FGS;
+        STATE_PROTO_ENUMS[STATE_BOUND_FGS] = ProcessStatsEnums.PROCESS_STATE_BOUND_FGS;
+        STATE_PROTO_ENUMS[STATE_BOUND_TOP] = ProcessStatsEnums.PROCESS_STATE_BOUND_TOP;
         STATE_PROTO_ENUMS[STATE_FGS] = ProcessStatsEnums.PROCESS_STATE_FGS;
         STATE_PROTO_ENUMS[STATE_IMPORTANT_FOREGROUND] =
                 ProcessStatsEnums.PROCESS_STATE_IMPORTANT_FOREGROUND;
@@ -161,10 +161,8 @@
         STATE_PROTO_ENUMS[STATE_HEAVY_WEIGHT] = ProcessStatsEnums.PROCESS_STATE_HEAVY_WEIGHT;
         STATE_PROTO_ENUMS[STATE_HOME] = ProcessStatsEnums.PROCESS_STATE_HOME;
         STATE_PROTO_ENUMS[STATE_LAST_ACTIVITY] = ProcessStatsEnums.PROCESS_STATE_LAST_ACTIVITY;
-        STATE_PROTO_ENUMS[STATE_CACHED_ACTIVITY] = ProcessStatsEnums.PROCESS_STATE_CACHED_ACTIVITY;
-        STATE_PROTO_ENUMS[STATE_CACHED_ACTIVITY_CLIENT] =
-                ProcessStatsEnums.PROCESS_STATE_CACHED_ACTIVITY_CLIENT;
-        STATE_PROTO_ENUMS[STATE_CACHED_EMPTY] = ProcessStatsEnums.PROCESS_STATE_CACHED_EMPTY;
+        STATE_PROTO_ENUMS[STATE_CACHED] = ProcessStatsEnums.PROCESS_STATE_CACHED_ACTIVITY;
+        STATE_PROTO_ENUMS[STATE_FROZEN] = ProcessStatsEnums.PROCESS_STATE_FROZEN;
 
         // Remap states, as defined by ProcessStats.java, to a reduced subset of states for data
         // aggregation / size reduction purposes.
@@ -173,7 +171,9 @@
                 ProcessStatsEnums.AGGREGATED_PROCESS_STATE_PERSISTENT;
         PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_TOP] =
                 ProcessStatsEnums.AGGREGATED_PROCESS_STATE_TOP;
-        PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_BOUND_TOP_OR_FGS] =
+        PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_BOUND_FGS] =
+                ProcessStatsEnums.AGGREGATED_PROCESS_STATE_BOUND_TOP_OR_FGS;
+        PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_BOUND_TOP] =
                 ProcessStatsEnums.AGGREGATED_PROCESS_STATE_BOUND_TOP_OR_FGS;
         PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_FGS] =
                 ProcessStatsEnums.AGGREGATED_PROCESS_STATE_FGS;
@@ -196,11 +196,9 @@
                 ProcessStatsEnums.AGGREGATED_PROCESS_STATE_CACHED;
         PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_LAST_ACTIVITY] =
                 ProcessStatsEnums.AGGREGATED_PROCESS_STATE_CACHED;
-        PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_CACHED_ACTIVITY] =
+        PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_CACHED] =
                 ProcessStatsEnums.AGGREGATED_PROCESS_STATE_CACHED;
-        PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_CACHED_ACTIVITY_CLIENT] =
-                ProcessStatsEnums.AGGREGATED_PROCESS_STATE_CACHED;
-        PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_CACHED_EMPTY] =
+        PROCESS_STATS_STATE_TO_AGGREGATED_STATE[STATE_FROZEN] =
                 ProcessStatsEnums.AGGREGATED_PROCESS_STATE_CACHED;
     }
 
diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java
index 818a503..fff778c 100644
--- a/core/java/com/android/internal/app/procstats/ProcessState.java
+++ b/core/java/com/android/internal/app/procstats/ProcessState.java
@@ -28,10 +28,9 @@
 import static com.android.internal.app.procstats.ProcessStats.PSS_USS_MAXIMUM;
 import static com.android.internal.app.procstats.ProcessStats.PSS_USS_MINIMUM;
 import static com.android.internal.app.procstats.ProcessStats.STATE_BACKUP;
-import static com.android.internal.app.procstats.ProcessStats.STATE_BOUND_TOP_OR_FGS;
-import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED_ACTIVITY;
-import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED_ACTIVITY_CLIENT;
-import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED_EMPTY;
+import static com.android.internal.app.procstats.ProcessStats.STATE_BOUND_FGS;
+import static com.android.internal.app.procstats.ProcessStats.STATE_BOUND_TOP;
+import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED;
 import static com.android.internal.app.procstats.ProcessStats.STATE_COUNT;
 import static com.android.internal.app.procstats.ProcessStats.STATE_FGS;
 import static com.android.internal.app.procstats.ProcessStats.STATE_HEAVY_WEIGHT;
@@ -85,9 +84,9 @@
         STATE_PERSISTENT,               // ActivityManager.PROCESS_STATE_PERSISTENT
         STATE_PERSISTENT,               // ActivityManager.PROCESS_STATE_PERSISTENT_UI
         STATE_TOP,                      // ActivityManager.PROCESS_STATE_TOP
-        STATE_BOUND_TOP_OR_FGS,         // ActivityManager.PROCESS_STATE_BOUND_TOP
+        STATE_BOUND_TOP,                // ActivityManager.PROCESS_STATE_BOUND_TOP
         STATE_FGS,                      // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
-        STATE_BOUND_TOP_OR_FGS,         // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
+        STATE_BOUND_FGS,                // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
         STATE_IMPORTANT_FOREGROUND,     // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
         STATE_IMPORTANT_BACKGROUND,     // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
         STATE_IMPORTANT_BACKGROUND,     // ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND
@@ -98,10 +97,10 @@
         STATE_HEAVY_WEIGHT,             // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
         STATE_HOME,                     // ActivityManager.PROCESS_STATE_HOME
         STATE_LAST_ACTIVITY,            // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
-        STATE_CACHED_ACTIVITY,          // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
-        STATE_CACHED_ACTIVITY_CLIENT,   // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
-        STATE_CACHED_ACTIVITY,          // ActivityManager.PROCESS_STATE_CACHED_RECENT
-        STATE_CACHED_EMPTY,             // ActivityManager.PROCESS_STATE_CACHED_EMPTY
+        STATE_CACHED,                   // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
+        STATE_CACHED,                   // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+        STATE_CACHED,                   // ActivityManager.PROCESS_STATE_CACHED_RECENT
+        STATE_CACHED,                   // ActivityManager.PROCESS_STATE_CACHED_EMPTY
     };
 
     public static final Comparator<ProcessState> COMPARATOR = new Comparator<ProcessState>() {
@@ -926,8 +925,11 @@
                 screenStates, memStates, new int[] { STATE_PERSISTENT }, now, totalTime, true);
         dumpProcessSummaryDetails(pw, prefix, DumpUtils.STATE_LABELS[STATE_TOP],
                 screenStates, memStates, new int[] {STATE_TOP}, now, totalTime, true);
-        dumpProcessSummaryDetails(pw, prefix, DumpUtils.STATE_LABELS[STATE_BOUND_TOP_OR_FGS],
-                screenStates, memStates, new int[] { STATE_BOUND_TOP_OR_FGS}, now, totalTime,
+        dumpProcessSummaryDetails(pw, prefix, DumpUtils.STATE_LABELS[STATE_BOUND_TOP],
+                screenStates, memStates, new int[] { STATE_BOUND_TOP }, now, totalTime,
+                true);
+        dumpProcessSummaryDetails(pw, prefix, DumpUtils.STATE_LABELS[STATE_BOUND_FGS],
+                screenStates, memStates, new int[] { STATE_BOUND_FGS }, now, totalTime,
                 true);
         dumpProcessSummaryDetails(pw, prefix, DumpUtils.STATE_LABELS[STATE_FGS],
                 screenStates, memStates, new int[] { STATE_FGS}, now, totalTime,
@@ -953,9 +955,6 @@
                 screenStates, memStates, new int[] {STATE_HOME}, now, totalTime, true);
         dumpProcessSummaryDetails(pw, prefix, DumpUtils.STATE_LABELS[STATE_LAST_ACTIVITY],
                 screenStates, memStates, new int[] {STATE_LAST_ACTIVITY}, now, totalTime, true);
-        dumpProcessSummaryDetails(pw, prefix, DumpUtils.STATE_LABEL_CACHED,
-                screenStates, memStates, new int[] {STATE_CACHED_ACTIVITY,
-                        STATE_CACHED_ACTIVITY_CLIENT, STATE_CACHED_EMPTY}, now, totalTime, true);
     }
 
     public void dumpProcessState(PrintWriter pw, String prefix,
@@ -1563,7 +1562,10 @@
                 case STATE_TOP:
                     topMs += duration;
                     break;
-                case STATE_BOUND_TOP_OR_FGS:
+                case STATE_BOUND_FGS:
+                    boundFgsMs += duration;
+                    break;
+                case STATE_BOUND_TOP:
                     boundTopMs += duration;
                     break;
                 case STATE_FGS:
@@ -1583,13 +1585,10 @@
                 case STATE_PERSISTENT:
                     otherMs += duration;
                     break;
-                case STATE_CACHED_ACTIVITY:
-                case STATE_CACHED_ACTIVITY_CLIENT:
-                case STATE_CACHED_EMPTY:
+                case STATE_CACHED:
                     cachedMs += duration;
                     break;
-                    // TODO (b/261910877) Add support for tracking boundFgsMs and
-                    // frozenMs.
+                    // TODO (b/261910877) Add support for tracking frozenMs.
             }
         }
         statsEventOutput.write(
diff --git a/core/java/com/android/internal/app/procstats/ProcessStats.java b/core/java/com/android/internal/app/procstats/ProcessStats.java
index f3ed09a..3ce234b 100644
--- a/core/java/com/android/internal/app/procstats/ProcessStats.java
+++ b/core/java/com/android/internal/app/procstats/ProcessStats.java
@@ -81,21 +81,21 @@
     public static final int STATE_NOTHING = -1;
     public static final int STATE_PERSISTENT = 0;
     public static final int STATE_TOP = 1;
-    public static final int STATE_BOUND_TOP_OR_FGS = 2;
+    public static final int STATE_BOUND_TOP = 2;
     public static final int STATE_FGS = 3;
-    public static final int STATE_IMPORTANT_FOREGROUND = 4;
-    public static final int STATE_IMPORTANT_BACKGROUND = 5;
-    public static final int STATE_BACKUP = 6;
-    public static final int STATE_SERVICE = 7;
-    public static final int STATE_SERVICE_RESTARTING = 8;
-    public static final int STATE_RECEIVER = 9;
-    public static final int STATE_HEAVY_WEIGHT = 10;
-    public static final int STATE_HOME = 11;
-    public static final int STATE_LAST_ACTIVITY = 12;
-    public static final int STATE_CACHED_ACTIVITY = 13;
-    public static final int STATE_CACHED_ACTIVITY_CLIENT = 14;
-    public static final int STATE_CACHED_EMPTY = 15;
-    public static final int STATE_COUNT = STATE_CACHED_EMPTY+1;
+    public static final int STATE_BOUND_FGS = 4;
+    public static final int STATE_IMPORTANT_FOREGROUND = 5;
+    public static final int STATE_IMPORTANT_BACKGROUND = 6;
+    public static final int STATE_BACKUP = 7;
+    public static final int STATE_SERVICE = 8;
+    public static final int STATE_SERVICE_RESTARTING = 9;
+    public static final int STATE_RECEIVER = 10;
+    public static final int STATE_HEAVY_WEIGHT = 11;
+    public static final int STATE_HOME = 12;
+    public static final int STATE_LAST_ACTIVITY = 13;
+    public static final int STATE_CACHED = 14;
+    public static final int STATE_FROZEN = 15;
+    public static final int STATE_COUNT = STATE_FROZEN + 1;
 
     public static final int PSS_SAMPLE_COUNT = 0;
     public static final int PSS_MINIMUM = 1;
@@ -154,9 +154,10 @@
     public static final int[] ALL_SCREEN_ADJ = new int[] { ADJ_SCREEN_OFF, ADJ_SCREEN_ON };
 
     public static final int[] NON_CACHED_PROC_STATES = new int[] {
-            STATE_PERSISTENT, STATE_TOP, STATE_BOUND_TOP_OR_FGS, STATE_FGS,
+            STATE_PERSISTENT, STATE_TOP, STATE_FGS,
             STATE_IMPORTANT_FOREGROUND, STATE_IMPORTANT_BACKGROUND, STATE_BACKUP,
-            STATE_SERVICE, STATE_SERVICE_RESTARTING, STATE_RECEIVER, STATE_HEAVY_WEIGHT
+            STATE_SERVICE, STATE_SERVICE_RESTARTING, STATE_RECEIVER, STATE_HEAVY_WEIGHT,
+            STATE_BOUND_TOP, STATE_BOUND_FGS
     };
 
     public static final int[] BACKGROUND_PROC_STATES = new int[] {
@@ -165,11 +166,11 @@
     };
 
     public static final int[] ALL_PROC_STATES = new int[] { STATE_PERSISTENT,
-            STATE_TOP, STATE_BOUND_TOP_OR_FGS, STATE_FGS, STATE_IMPORTANT_FOREGROUND,
+            STATE_TOP, STATE_FGS, STATE_IMPORTANT_FOREGROUND,
             STATE_IMPORTANT_BACKGROUND, STATE_BACKUP,
             STATE_SERVICE, STATE_SERVICE_RESTARTING, STATE_RECEIVER,
-            STATE_HEAVY_WEIGHT, STATE_HOME, STATE_LAST_ACTIVITY, STATE_CACHED_ACTIVITY,
-            STATE_CACHED_ACTIVITY_CLIENT, STATE_CACHED_EMPTY
+            STATE_HEAVY_WEIGHT, STATE_HOME, STATE_LAST_ACTIVITY, STATE_CACHED,
+            STATE_BOUND_TOP, STATE_BOUND_FGS, STATE_FROZEN
     };
 
     // Should report process stats.
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 7ae63b1..928a097 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -18,6 +18,7 @@
 
 import static android.Manifest.permission.READ_DEVICE_CONFIG;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.provider.DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR;
 
 import static com.android.internal.jank.FrameTracker.REASON_CANCEL_NORMAL;
 import static com.android.internal.jank.FrameTracker.REASON_CANCEL_TIMEOUT;
@@ -448,17 +449,7 @@
         mEnabled = DEFAULT_ENABLED;
 
         final Context context = ActivityThread.currentApplication();
-        if (context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) == PERMISSION_GRANTED) {
-            // Post initialization to the background in case we're running on the main thread.
-            mWorker.getThreadHandler().post(
-                    () -> mPropertiesChangedListener.onPropertiesChanged(
-                            DeviceConfig.getProperties(
-                                    DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR)));
-            DeviceConfig.addOnPropertiesChangedListener(
-                    DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR,
-                    new HandlerExecutor(mWorker.getThreadHandler()),
-                    mPropertiesChangedListener);
-        } else {
+        if (context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) != PERMISSION_GRANTED) {
             if (DEBUG) {
                 Log.d(TAG, "Initialized the InteractionJankMonitor."
                         + " (No READ_DEVICE_CONFIG permission to change configs)"
@@ -467,7 +458,25 @@
                         + ", frameTimeThreshold=" + mTraceThresholdFrameTimeMillis
                         + ", package=" + context.getPackageName());
             }
+            return;
         }
+
+        // Post initialization to the background in case we're running on the main thread.
+        mWorker.getThreadHandler().post(
+                () -> {
+                    try {
+                        mPropertiesChangedListener.onPropertiesChanged(
+                                DeviceConfig.getProperties(NAMESPACE_INTERACTION_JANK_MONITOR));
+                        DeviceConfig.addOnPropertiesChangedListener(
+                                NAMESPACE_INTERACTION_JANK_MONITOR,
+                                new HandlerExecutor(mWorker.getThreadHandler()),
+                                mPropertiesChangedListener);
+                    } catch (SecurityException ex) {
+                        Log.d(TAG, "Can't get properties: READ_DEVICE_CONFIG granted="
+                                + context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG)
+                                + ", package=" + context.getPackageName());
+                    }
+                });
     }
 
     /**
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/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java
index 3a393b6..69d3d6a 100644
--- a/core/java/com/android/internal/util/ScreenshotHelper.java
+++ b/core/java/com/android/internal/util/ScreenshotHelper.java
@@ -195,6 +195,8 @@
                         UserHandle.CURRENT)) {
                     mScreenshotConnection = conn;
                     handler.postDelayed(mScreenshotTimeout, timeoutMs);
+                } else {
+                    mContext.unbindService(conn);
                 }
             } else {
                 Messenger messenger = new Messenger(mScreenshotService);
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 611035e..70a1354 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4492,7 +4492,7 @@
 
     <!-- Allows an application to be able to store and retrieve credentials from a remote
          device.
-         @hide @SystemApi -->
+    <p>Protection level: signature|privileged|role -->
     <permission android:name="android.permission.PROVIDE_REMOTE_CREDENTIALS"
                 android:protectionLevel="signature|privileged|role" />
 
@@ -5303,12 +5303,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/miniresolver.xml b/core/res/res/layout/miniresolver.xml
index d07ad89..1ad3acd 100644
--- a/core/res/res/layout/miniresolver.xml
+++ b/core/res/res/layout/miniresolver.xml
@@ -56,6 +56,7 @@
             android:paddingTop="16dp"
             android:layout_below="@id/icon"
             android:layout_centerHorizontal="true"
+            android:fontFamily="@string/config_headlineFontFamily"
             android:textSize="24sp"
             android:lineHeight="32sp"
             android:gravity="center"
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 3074906..6afdae5 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -327,6 +327,7 @@
         <item>@string/wfcSpnFormat_wifi_calling_wo_hyphen</item>
         <item>@string/wfcSpnFormat_vowifi</item>
         <item>@string/wfcSpnFormat_spn_wifi_calling_vo_hyphen</item>
+        <item>@string/wfcSpnFormat_wifi_call</item>
     </string-array>
 
     <!-- Spn during Wi-Fi Calling: "<operator>" -->
@@ -353,6 +354,8 @@
     <string name="wfcSpnFormat_wifi_calling_wo_hyphen">WiFi Calling</string>
     <!-- Spn during Wi-Fi Calling: "VoWifi" -->
     <string name="wfcSpnFormat_vowifi">VoWifi</string>
+    <!-- Spn during Wi_Fi Calling: "WiFi Call" (without hyphen).  This string is shown in the call banner for calls which take place over a WiFi network. -->
+    <string name="wfcSpnFormat_wifi_call">WiFi Call</string>
 
     <!-- WFC, summary for Disabled -->
     <string name="wifi_calling_off_summary">Off</string>
@@ -6264,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/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
index 1ec2613..fccb177 100644
--- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
+++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
@@ -24,6 +24,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -224,7 +225,7 @@
     }
 
     @Test
-    public void onTouchEvent_startHandwriting_delegate() {
+    public void onTouchEvent_tryAcceptDelegation_delegatorCallbackCreatesInputConnection() {
         View delegateView = new View(mContext);
         delegateView.setIsHandwritingDelegate(true);
 
@@ -245,6 +246,29 @@
     }
 
     @Test
+    public void onTouchEvent_tryAcceptDelegation_delegatorCallbackFocusesDelegate() {
+        View delegateView = new View(mContext);
+        delegateView.setIsHandwritingDelegate(true);
+        mHandwritingInitiator.onInputConnectionCreated(delegateView);
+        reset(mHandwritingInitiator);
+
+        mTestView1.setHandwritingDelegatorCallback(
+                () -> mHandwritingInitiator.onDelegateViewFocused(delegateView));
+
+        final int x1 = (sHwArea1.left + sHwArea1.right) / 2;
+        final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2;
+        MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
+        mHandwritingInitiator.onTouchEvent(stylusEvent1);
+
+        final int x2 = x1 + mHandwritingSlop * 2;
+        final int y2 = y1;
+        MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0);
+        mHandwritingInitiator.onTouchEvent(stylusEvent2);
+
+        verify(mHandwritingInitiator, times(1)).tryAcceptStylusHandwritingDelegation(delegateView);
+    }
+
+    @Test
     public void onTouchEvent_notStartHandwriting_whenHandwritingNotAvailable() {
         final Rect rect = new Rect(600, 600, 900, 900);
         final View testView = createView(rect, true /* autoHandwritingEnabled */,
diff --git a/core/tests/coretests/src/com/android/internal/app/procstats/ProcessStatsTest.java b/core/tests/coretests/src/com/android/internal/app/procstats/ProcessStatsTest.java
index 35b3267..6189914 100644
--- a/core/tests/coretests/src/com/android/internal/app/procstats/ProcessStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/procstats/ProcessStatsTest.java
@@ -23,6 +23,8 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.MockitoAnnotations.initMocks;
 
+import android.app.ActivityManager;
+
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.util.FrameworkStatsLog;
@@ -128,6 +130,34 @@
     }
 
     @SmallTest
+    public void testDumpBoundFgsDuration() throws Exception {
+        ProcessStats processStats = new ProcessStats();
+        ProcessState processState =
+                processStats.getProcessStateLocked(
+                        APP_1_PACKAGE_NAME, APP_1_UID, APP_1_VERSION, APP_1_PROCESS_NAME);
+        processState.setState(ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE,
+                ProcessStats.ADJ_MEM_FACTOR_NORMAL, NOW_MS, /* pkgList */ null);
+        processState.commitStateTime(NOW_MS + TimeUnit.SECONDS.toMillis(DURATION_SECS));
+        processStats.dumpProcessState(FrameworkStatsLog.PROCESS_STATE, mStatsEventOutput);
+        verify(mStatsEventOutput)
+                .write(
+                        eq(FrameworkStatsLog.PROCESS_STATE),
+                        eq(APP_1_UID),
+                        eq(APP_1_PROCESS_NAME),
+                        anyInt(),
+                        anyInt(),
+                        eq(0),
+                        eq(0),
+                        eq(0),
+                        eq(0),
+                        eq(DURATION_SECS),
+                        eq(0),
+                        eq(0),
+                        eq(0),
+                        eq(0));
+    }
+
+    @SmallTest
     public void testDumpProcessAssociation() throws Exception {
         ProcessStats processStats = new ProcessStats();
         AssociationState associationState =
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/fonts/fonts.xml b/data/fonts/fonts.xml
index 8e2a59c..e7ddc88 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -255,9 +255,12 @@
     </family>
 
     <family name="cursive">
-        <font weight="400" style="normal" postScriptName="DancingScript">DancingScript-Regular.ttf
-        </font>
-        <font weight="700" style="normal">DancingScript-Bold.ttf</font>
+      <font weight="400" style="normal">DancingScript-Regular.ttf
+        <axis tag="wght" stylevalue="400" />
+      </font>
+      <font weight="700" style="normal">DancingScript-Regular.ttf
+        <axis tag="wght" stylevalue="700" />
+      </font>
     </family>
 
     <family name="sans-serif-smallcaps">
diff --git a/data/keyboards/Generic.kcm b/data/keyboards/Generic.kcm
index fe6eeeb..1048742 100644
--- a/data/keyboards/Generic.kcm
+++ b/data/keyboards/Generic.kcm
@@ -42,9 +42,10 @@
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
-    alt:                                '\u00e7'
-    shift+alt:                          '\u00c7'
     shift+capslock:                     'c'
+    alt:                                '\u00e7'
+    shift+alt, capslock+alt:            '\u00c7'
+    shift+capslock+alt:                 '\u00e7'
 }
 
 key D {
@@ -58,8 +59,8 @@
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
-    alt:                                '\u0301'
     shift+capslock:                     'e'
+    alt:                                '\u0301'
 }
 
 key F {
@@ -87,8 +88,8 @@
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
-    alt:                                '\u0302'
     shift+capslock:                     'i'
+    alt:                                '\u0302'
 }
 
 key J {
@@ -123,8 +124,8 @@
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
-    alt:                                '\u0303'
     shift+capslock:                     'n'
+    alt:                                '\u0303'
 }
 
 key O {
@@ -159,8 +160,8 @@
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
-    alt:                                '\u00df'
     shift+capslock:                     's'
+    alt:                                '\u00df'
 }
 
 key T {
@@ -174,8 +175,8 @@
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
-    alt:                                '\u0308'
     shift+capslock:                     'u'
+    alt:                                '\u0308'
 }
 
 key V {
diff --git a/data/keyboards/Vendor_004c_Product_0265.idc b/data/keyboards/Vendor_004c_Product_0265.idc
deleted file mode 120000
index 707dfcf..0000000
--- a/data/keyboards/Vendor_004c_Product_0265.idc
+++ /dev/null
@@ -1 +0,0 @@
-Vendor_05ac_Product_0265.idc
\ No newline at end of file
diff --git a/data/keyboards/Vendor_004c_Product_0265.idc b/data/keyboards/Vendor_004c_Product_0265.idc
new file mode 100644
index 0000000..bfea4db
--- /dev/null
+++ b/data/keyboards/Vendor_004c_Product_0265.idc
@@ -0,0 +1,34 @@
+# Copyright 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.
+
+#
+# Apple Magic Trackpad 2 (Bluetooth) configuration file
+#
+# WHEN MODIFYING, also change the USB file (Vendor_05ac_Product_0265.idc)
+#
+
+gestureProp.Pressure_Calibration_Offset = 30
+gestureProp.Palm_Pressure = 250.0
+gestureProp.Palm_Width = 20.0
+gestureProp.Multiple_Palm_Width = 20.0
+
+# Enable Stationary Wiggle Filter
+gestureProp.Stationary_Wiggle_Filter_Enabled = 1
+gestureProp.Finger_Moving_Energy = 0.0008
+gestureProp.Finger_Moving_Hysteresis = 0.0004
+
+# Avoid accidental scroll/move on finger lift
+gestureProp.Max_Stationary_Move_Speed = 47
+gestureProp.Max_Stationary_Move_Speed_Hysteresis = 1
+gestureProp.Max_Stationary_Move_Suppress_Distance = 0.2
diff --git a/data/keyboards/Vendor_05ac_Product_0265.idc b/data/keyboards/Vendor_05ac_Product_0265.idc
index d72de64..520d188 100644
--- a/data/keyboards/Vendor_05ac_Product_0265.idc
+++ b/data/keyboards/Vendor_05ac_Product_0265.idc
@@ -13,9 +13,9 @@
 # limitations under the License.
 
 #
-# Apple Magic Trackpad 2 configuration file
-#   Bluetooth vendor ID 004c
-#   USB vendor ID 05ac
+# Apple Magic Trackpad 2 (USB) configuration file
+#
+# WHEN MODIFYING, also change the Bluetooth file (Vendor_004c_Product_0265.idc)
 #
 
 gestureProp.Pressure_Calibration_Offset = 30
diff --git a/data/keyboards/Virtual.kcm b/data/keyboards/Virtual.kcm
index 53308e3..06b8237 100644
--- a/data/keyboards/Virtual.kcm
+++ b/data/keyboards/Virtual.kcm
@@ -39,9 +39,10 @@
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
-    alt:                                '\u00e7'
-    shift+alt:                          '\u00c7'
     shift+capslock:                     'c'
+    alt:                                '\u00e7'
+    shift+alt, capslock+alt:            '\u00c7'
+    shift+capslock+alt:                 '\u00e7'
 }
 
 key D {
@@ -55,8 +56,8 @@
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
-    alt:                                '\u0301'
     shift+capslock:                     'e'
+    alt:                                '\u0301'
 }
 
 key F {
@@ -84,8 +85,8 @@
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
-    alt:                                '\u0302'
     shift+capslock:                     'i'
+    alt:                                 '\u0302'
 }
 
 key J {
@@ -120,8 +121,8 @@
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
-    alt:                                '\u0303'
     shift+capslock:                     'n'
+    alt:                                '\u0303'
 }
 
 key O {
@@ -156,8 +157,8 @@
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
-    alt:                                '\u00df'
     shift+capslock:                     's'
+    alt:                                '\u00df'
 }
 
 key T {
@@ -171,8 +172,8 @@
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
-    alt:                                '\u0308'
     shift+capslock:                     'u'
+    alt:                                '\u0308'
 }
 
 key V {
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index c1f6c29..c3b0f9b 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -808,9 +808,12 @@
                         KeymasterDefs.KM_TAG_ATTESTATION_ID_BRAND,
                         platformReportedBrand.getBytes(StandardCharsets.UTF_8)
                 ));
+                final String platformReportedDevice =
+                        isPropertyEmptyOrUnknown(Build.DEVICE_FOR_ATTESTATION)
+                                ? Build.DEVICE : Build.DEVICE_FOR_ATTESTATION;
                 params.add(KeyStore2ParameterUtils.makeBytes(
                         KeymasterDefs.KM_TAG_ATTESTATION_ID_DEVICE,
-                        Build.DEVICE.getBytes(StandardCharsets.UTF_8)
+                        platformReportedDevice.getBytes(StandardCharsets.UTF_8)
                 ));
                 final String platformReportedProduct =
                         isPropertyEmptyOrUnknown(Build.PRODUCT_FOR_ATTESTATION)
@@ -819,9 +822,12 @@
                         KeymasterDefs.KM_TAG_ATTESTATION_ID_PRODUCT,
                         platformReportedProduct.getBytes(StandardCharsets.UTF_8)
                 ));
+                final String platformReportedManufacturer =
+                        isPropertyEmptyOrUnknown(Build.MANUFACTURER_FOR_ATTESTATION)
+                                ? Build.MANUFACTURER : Build.MANUFACTURER_FOR_ATTESTATION;
                 params.add(KeyStore2ParameterUtils.makeBytes(
                         KeymasterDefs.KM_TAG_ATTESTATION_ID_MANUFACTURER,
-                        Build.MANUFACTURER.getBytes(StandardCharsets.UTF_8)
+                        platformReportedManufacturer.getBytes(StandardCharsets.UTF_8)
                 ));
                 final String platformReportedModel =
                         isPropertyEmptyOrUnknown(Build.MODEL_FOR_ATTESTATION)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index ef53839..48fe65d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -1028,19 +1028,21 @@
      * the bubble or bubble stack.
      *
      * Some notes:
-     *    - Only one app bubble is supported at a time
+     *    - Only one app bubble is supported at a time, regardless of users. Multi-users support is
+     *      tracked in b/273533235.
      *    - Calling this method with a different intent than the existing app bubble will do nothing
      *
      * @param intent the intent to display in the bubble expanded view.
+     * @param user the {@link UserHandle} of the user to start this activity for.
      */
-    public void showOrHideAppBubble(Intent intent) {
+    public void showOrHideAppBubble(Intent intent, UserHandle user) {
         if (intent == null || intent.getPackage() == null) {
             Log.w(TAG, "App bubble failed to show, invalid intent: " + intent
                     + ((intent != null) ? " with package: " + intent.getPackage() : " "));
             return;
         }
 
-        PackageManager packageManager = getPackageManagerForUser(mContext, mCurrentUserId);
+        PackageManager packageManager = getPackageManagerForUser(mContext, user.getIdentifier());
         if (!isResizableActivity(intent, packageManager, KEY_APP_BUBBLE)) return;
 
         Bubble existingAppBubble = mBubbleData.getBubbleInStackWithKey(KEY_APP_BUBBLE);
@@ -1061,7 +1063,7 @@
             }
         } else {
             // App bubble does not exist, lets add and expand it
-            Bubble b = new Bubble(intent, UserHandle.of(mCurrentUserId), mMainExecutor);
+            Bubble b = new Bubble(intent, user, mMainExecutor);
             b.setShouldAutoExpand(true);
             inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false);
         }
@@ -1869,10 +1871,9 @@
         }
 
         @Override
-        public void showOrHideAppBubble(Intent intent) {
-            mMainExecutor.execute(() -> {
-                BubbleController.this.showOrHideAppBubble(intent);
-            });
+        public void showOrHideAppBubble(Intent intent, UserHandle user) {
+            mMainExecutor.execute(
+                    () -> BubbleController.this.showOrHideAppBubble(intent, user));
         }
 
         @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index ecddbda..e5a4362 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -236,12 +236,17 @@
                     fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
 
                     if (mBubble.isAppBubble()) {
-                        PendingIntent pi = PendingIntent.getActivity(mContext, 0,
+                        Context context =
+                                mContext.createContextAsUser(
+                                        mBubble.getUser(), Context.CONTEXT_RESTRICTED);
+                        PendingIntent pi = PendingIntent.getActivity(
+                                context,
+                                /* requestCode= */ 0,
                                 mBubble.getAppBubbleIntent()
                                         .addFlags(FLAG_ACTIVITY_NEW_DOCUMENT)
                                         .addFlags(FLAG_ACTIVITY_MULTIPLE_TASK),
                                 PendingIntent.FLAG_IMMUTABLE,
-                                null);
+                                /* options= */ null);
                         mTaskView.startActivity(pi, /* fillInIntent= */ null, options,
                                 launchBounds);
                     } else if (!mIsOverflow && mBubble.hasMetadataShortcutId()) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 4c0a93f..5555bec 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -129,12 +129,14 @@
      * the bubble or bubble stack.
      *
      * Some notes:
-     *    - Only one app bubble is supported at a time
+     *    - Only one app bubble is supported at a time, regardless of users. Multi-users support is
+     *      tracked in b/273533235.
      *    - Calling this method with a different intent than the existing app bubble will do nothing
      *
      * @param intent the intent to display in the bubble expanded view.
+     * @param user the {@link UserHandle} of the user to start this activity for.
      */
-    void showOrHideAppBubble(Intent intent);
+    void showOrHideAppBubble(Intent intent, UserHandle user);
 
     /** @return true if the specified {@code taskId} corresponds to app bubble's taskId. */
     boolean isAppBubbleTaskId(int taskId);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
index bf226283..cb1a6e7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
@@ -22,10 +22,12 @@
 import static com.android.wm.shell.common.DevicePostureController.DEVICE_POSTURE_UNKNOWN;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_FOLDABLE;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.app.WindowConfiguration;
 import android.content.Context;
 import android.content.res.Configuration;
+import android.os.SystemProperties;
 import android.util.ArraySet;
 import android.view.Surface;
 
@@ -34,6 +36,8 @@
 import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.sysui.ShellInit;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
@@ -49,8 +53,34 @@
 public class TabletopModeController implements
         DevicePostureController.OnDevicePostureChangedListener,
         DisplayController.OnDisplaysChangedListener {
+    /**
+     * When {@code true}, floating windows like PiP would auto move to the position
+     * specified by {@link #PREFER_TOP_HALF_IN_TABLETOP} when in tabletop mode.
+     */
+    private static final boolean ENABLE_MOVE_FLOATING_WINDOW_IN_TABLETOP =
+            SystemProperties.getBoolean(
+                    "persist.wm.debug.enable_move_floating_window_in_tabletop", false);
+
+    /**
+     * Prefer the {@link #PREFERRED_TABLETOP_HALF_TOP} if this flag is enabled,
+     * {@link #PREFERRED_TABLETOP_HALF_BOTTOM} otherwise.
+     * See also {@link #getPreferredHalfInTabletopMode()}.
+     */
+    private static final boolean PREFER_TOP_HALF_IN_TABLETOP =
+            SystemProperties.getBoolean("persist.wm.debug.prefer_top_half_in_tabletop", true);
+
     private static final long TABLETOP_MODE_DELAY_MILLIS = 1_000;
 
+    @IntDef(prefix = {"PREFERRED_TABLETOP_HALF_"}, value = {
+            PREFERRED_TABLETOP_HALF_TOP,
+            PREFERRED_TABLETOP_HALF_BOTTOM
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PreferredTabletopHalf {}
+
+    public static final int PREFERRED_TABLETOP_HALF_TOP = 0;
+    public static final int PREFERRED_TABLETOP_HALF_BOTTOM = 1;
+
     private final Context mContext;
 
     private final DevicePostureController mDevicePostureController;
@@ -132,6 +162,22 @@
         }
     }
 
+    /**
+     * @return {@code true} if floating windows like PiP would auto move to the position
+     * specified by {@link #getPreferredHalfInTabletopMode()} when in tabletop mode.
+     */
+    public boolean enableMoveFloatingWindowInTabletop() {
+        return ENABLE_MOVE_FLOATING_WINDOW_IN_TABLETOP;
+    }
+
+    /** @return Preferred half for floating windows like PiP when in tabletop mode. */
+    @PreferredTabletopHalf
+    public int getPreferredHalfInTabletopMode() {
+        return PREFER_TOP_HALF_IN_TABLETOP
+                ? PREFERRED_TABLETOP_HALF_TOP
+                : PREFERRED_TABLETOP_HALF_BOTTOM;
+    }
+
     /** Register {@link OnTabletopModeChangedListener} to listen for tabletop mode change. */
     public void registerOnTabletopModeChangedListener(
             @NonNull OnTabletopModeChangedListener listener) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index ba0f073..7a83d10 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -45,6 +45,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.SystemWindows;
+import com.android.wm.shell.common.TabletopModeController;
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.common.annotations.ShellBackgroundThread;
@@ -357,6 +358,7 @@
             TaskStackListenerImpl taskStackListener,
             PipParamsChangedForwarder pipParamsChangedForwarder,
             DisplayInsetsController displayInsetsController,
+            TabletopModeController pipTabletopController,
             Optional<OneHandedController> oneHandedController,
             @ShellMainThread ShellExecutor mainExecutor) {
         return Optional.ofNullable(PipController.create(
@@ -366,7 +368,7 @@
                 pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer,
                 pipTransitionState, pipTouchHandler, pipTransitionController,
                 windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
-                displayInsetsController, oneHandedController, mainExecutor));
+                displayInsetsController, pipTabletopController, oneHandedController, mainExecutor));
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 73a7403..31c5e33 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -25,6 +25,7 @@
 import android.app.WindowConfiguration.WindowingMode
 import android.content.Context
 import android.os.IBinder
+import android.os.SystemProperties
 import android.view.SurfaceControl
 import android.view.WindowManager.TRANSIT_CHANGE
 import android.view.WindowManager.TRANSIT_NONE
@@ -32,6 +33,7 @@
 import android.view.WindowManager.TRANSIT_TO_FRONT
 import android.window.TransitionInfo
 import android.window.TransitionRequestInfo
+import android.window.WindowContainerToken
 import android.window.WindowContainerTransaction
 import androidx.annotation.BinderThread
 import com.android.internal.protolog.common.ProtoLog
@@ -115,10 +117,7 @@
         val wct = WindowContainerTransaction()
         // Bring other apps to front first
         bringDesktopAppsToFront(wct)
-
-        wct.setWindowingMode(task.getToken(), WINDOWING_MODE_FREEFORM)
-        wct.reorder(task.getToken(), true /* onTop */)
-
+        addMoveToDesktopChanges(wct, task.token)
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
         } else {
@@ -136,8 +135,7 @@
         ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToFullscreen: %d", task.taskId)
 
         val wct = WindowContainerTransaction()
-        wct.setWindowingMode(task.getToken(), WINDOWING_MODE_FULLSCREEN)
-        wct.setBounds(task.getToken(), null)
+        addMoveToFullscreenChanges(wct, task.token)
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
         } else {
@@ -234,8 +232,8 @@
                         " taskId=%d",
                     task.taskId
                 )
-                return WindowContainerTransaction().apply {
-                    setWindowingMode(task.token, WINDOWING_MODE_FREEFORM)
+                return WindowContainerTransaction().also { wct ->
+                    addMoveToDesktopChanges(wct, task.token)
                 }
             }
         }
@@ -251,15 +249,44 @@
                         " taskId=%d",
                     task.taskId
                 )
-                return WindowContainerTransaction().apply {
-                    setWindowingMode(task.token, WINDOWING_MODE_FULLSCREEN)
-                    setBounds(task.token, null)
+                return WindowContainerTransaction().also { wct ->
+                    addMoveToFullscreenChanges(wct, task.token)
                 }
             }
         }
         return null
     }
 
+    private fun addMoveToDesktopChanges(
+        wct: WindowContainerTransaction,
+        token: WindowContainerToken
+    ) {
+        wct.setWindowingMode(token, WINDOWING_MODE_FREEFORM)
+        wct.reorder(token, true /* onTop */)
+        if (isDesktopDensityOverrideSet()) {
+            wct.setDensityDpi(token, getDesktopDensityDpi())
+        }
+    }
+
+    private fun addMoveToFullscreenChanges(
+        wct: WindowContainerTransaction,
+        token: WindowContainerToken
+    ) {
+        wct.setWindowingMode(token, WINDOWING_MODE_FULLSCREEN)
+        wct.setBounds(token, null)
+        if (isDesktopDensityOverrideSet()) {
+            wct.setDensityDpi(token, getFullscreenDensityDpi())
+        }
+    }
+
+    private fun getFullscreenDensityDpi(): Int {
+        return context.resources.displayMetrics.densityDpi
+    }
+
+    private fun getDesktopDensityDpi(): Int {
+        return DESKTOP_DENSITY_OVERRIDE
+    }
+
     /** Creates a new instance of the external interface to pass to another process. */
     private fun createExternalInterface(): ExternalInterfaceBinder {
         return IDesktopModeImpl(this)
@@ -318,4 +345,18 @@
             return result[0]
         }
     }
+
+    companion object {
+        private val DESKTOP_DENSITY_OVERRIDE =
+            SystemProperties.getInt("persist.wm.debug.desktop_mode_density", 0)
+        private val DESKTOP_DENSITY_ALLOWED_RANGE = (100..1000)
+
+        /**
+         * Check if desktop density override is enabled
+         */
+        @JvmStatic
+        fun isDesktopDensityOverrideSet(): Boolean {
+            return DESKTOP_DENSITY_OVERRIDE in DESKTOP_DENSITY_ALLOWED_RANGE
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
index f664808..f08742d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
@@ -43,7 +43,9 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.function.Consumer;
@@ -113,6 +115,12 @@
      * @see android.view.View#setPreferKeepClearRects
      */
     private final Set<Rect> mUnrestrictedKeepClearAreas = new ArraySet<>();
+    /**
+     * Additional to {@link #mUnrestrictedKeepClearAreas}, allow the caller to append named bounds
+     * as unrestricted keep clear area. Values in this map would be appended to
+     * {@link #getUnrestrictedKeepClearAreas()} and this is meant for internal usage only.
+     */
+    private final Map<String, Rect> mNamedUnrestrictedKeepClearAreas = new HashMap<>();
 
     private @Nullable Runnable mOnMinimalSizeChangeCallback;
     private @Nullable TriConsumer<Boolean, Integer, Boolean> mOnShelfVisibilityChangeCallback;
@@ -378,6 +386,16 @@
         mUnrestrictedKeepClearAreas.addAll(unrestrictedAreas);
     }
 
+    /** Add a named unrestricted keep clear area. */
+    public void addNamedUnrestrictedKeepClearArea(@NonNull String name, Rect unrestrictedArea) {
+        mNamedUnrestrictedKeepClearAreas.put(name, unrestrictedArea);
+    }
+
+    /** Remove a named unrestricted keep clear area. */
+    public void removeNamedUnrestrictedKeepClearArea(@NonNull String name) {
+        mNamedUnrestrictedKeepClearAreas.remove(name);
+    }
+
     @NonNull
     public Set<Rect> getRestrictedKeepClearAreas() {
         return mRestrictedKeepClearAreas;
@@ -385,7 +403,10 @@
 
     @NonNull
     public Set<Rect> getUnrestrictedKeepClearAreas() {
-        return mUnrestrictedKeepClearAreas;
+        if (mNamedUnrestrictedKeepClearAreas.isEmpty()) return mUnrestrictedKeepClearAreas;
+        final Set<Rect> unrestrictedAreas = new ArraySet<>(mUnrestrictedKeepClearAreas);
+        unrestrictedAreas.addAll(mNamedUnrestrictedKeepClearAreas.values());
+        return unrestrictedAreas;
     }
 
     /**
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 db6ef1d..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
@@ -43,6 +43,7 @@
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.RemoteException;
 import android.os.SystemProperties;
@@ -71,6 +72,7 @@
 import com.android.wm.shell.common.RemoteCallable;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SingleInstanceRemoteListener;
+import com.android.wm.shell.common.TabletopModeController;
 import com.android.wm.shell.common.TaskStackListenerCallback;
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.onehanded.OneHandedController;
@@ -102,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;
@@ -117,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);
 
@@ -147,6 +150,7 @@
     private TaskStackListenerImpl mTaskStackListener;
     private PipParamsChangedForwarder mPipParamsChangedForwarder;
     private DisplayInsetsController mDisplayInsetsController;
+    private TabletopModeController mTabletopModeController;
     private Optional<OneHandedController> mOneHandedController;
     private final ShellCommandHandler mShellCommandHandler;
     private final ShellController mShellController;
@@ -403,6 +407,7 @@
             TaskStackListenerImpl taskStackListener,
             PipParamsChangedForwarder pipParamsChangedForwarder,
             DisplayInsetsController displayInsetsController,
+            TabletopModeController pipTabletopController,
             Optional<OneHandedController> oneHandedController,
             ShellExecutor mainExecutor) {
         if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
@@ -417,7 +422,7 @@
                 pipDisplayLayoutState, pipMotionHelper, pipMediaController, phonePipMenuController,
                 pipTaskOrganizer, pipTransitionState, pipTouchHandler, pipTransitionController,
                 windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
-                displayInsetsController, oneHandedController, mainExecutor)
+                displayInsetsController, pipTabletopController, oneHandedController, mainExecutor)
                 .mImpl;
     }
 
@@ -444,6 +449,7 @@
             TaskStackListenerImpl taskStackListener,
             PipParamsChangedForwarder pipParamsChangedForwarder,
             DisplayInsetsController displayInsetsController,
+            TabletopModeController tabletopModeController,
             Optional<OneHandedController> oneHandedController,
             ShellExecutor mainExecutor
     ) {
@@ -477,6 +483,7 @@
                 .getInteger(R.integer.config_pipEnterAnimationDuration);
         mPipParamsChangedForwarder = pipParamsChangedForwarder;
         mDisplayInsetsController = displayInsetsController;
+        mTabletopModeController = tabletopModeController;
 
         shellInit.addInitCallback(this::onInit, this);
     }
@@ -659,6 +666,42 @@
                     }
                 });
 
+        mTabletopModeController.registerOnTabletopModeChangedListener((isInTabletopMode) -> {
+            if (!mTabletopModeController.enableMoveFloatingWindowInTabletop()) return;
+            final String tag = "tabletop-mode";
+            if (!isInTabletopMode) {
+                mPipBoundsState.removeNamedUnrestrictedKeepClearArea(tag);
+                return;
+            }
+
+            // To prepare for the entry bounds.
+            final Rect displayBounds = mPipBoundsState.getDisplayBounds();
+            if (mTabletopModeController.getPreferredHalfInTabletopMode()
+                    == TabletopModeController.PREFERRED_TABLETOP_HALF_TOP) {
+                // Prefer top, avoid the bottom half of the display.
+                mPipBoundsState.addNamedUnrestrictedKeepClearArea(tag, new Rect(
+                        displayBounds.left, displayBounds.centerY(),
+                        displayBounds.right, displayBounds.bottom));
+            } else {
+                // Prefer bottom, avoid the top half of the display.
+                mPipBoundsState.addNamedUnrestrictedKeepClearArea(tag, new Rect(
+                        displayBounds.left, displayBounds.top,
+                        displayBounds.right, displayBounds.centerY()));
+            }
+
+            // Try to move the PiP window if we have entered PiP mode.
+            if (mPipTransitionState.hasEnteredPip()) {
+                final Rect pipBounds = mPipBoundsState.getBounds();
+                final Point edgeInsets = mPipSizeSpecHandler.getScreenEdgeInsets();
+                if ((pipBounds.height() + 2 * edgeInsets.y) > (displayBounds.height() / 2)) {
+                    // PiP bounds is too big to fit either half, bail early.
+                    return;
+                }
+                mMainExecutor.removeCallbacks(mMovePipInResponseToKeepClearAreasChangeCallback);
+                mMainExecutor.execute(mMovePipInResponseToKeepClearAreasChangeCallback);
+            }
+        });
+
         mOneHandedController.ifPresent(controller -> {
             controller.registerTransitionCallback(
                     new OneHandedTransitionCallback() {
@@ -892,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/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 72da108..3c0ef96 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -23,6 +23,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.res.ColorStateList;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Color;
 import android.graphics.Point;
@@ -46,6 +47,7 @@
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.desktopmode.DesktopTasksController;
 
 /**
  * Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with
@@ -95,6 +97,17 @@
         mDesktopActive = DesktopModeStatus.isActive(mContext);
     }
 
+    @Override
+    protected Configuration getConfigurationWithOverrides(
+            ActivityManager.RunningTaskInfo taskInfo) {
+        Configuration configuration = taskInfo.getConfiguration();
+        if (DesktopTasksController.isDesktopDensityOverrideSet()) {
+            // Density is overridden for desktop tasks. Keep system density for window decoration.
+            configuration.densityDpi = mContext.getResources().getConfiguration().densityDpi;
+        }
+        return configuration;
+    }
+
     void setCaptionListeners(
             View.OnClickListener onCaptionButtonClickListener,
             View.OnTouchListener onCaptionTouchListener) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 7a7ac47..ddd3b44 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -131,7 +131,17 @@
         mSurfaceControlViewHostFactory = surfaceControlViewHostFactory;
 
         mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId);
-        mDecorWindowContext = mContext.createConfigurationContext(mTaskInfo.getConfiguration());
+        mDecorWindowContext = mContext.createConfigurationContext(
+                getConfigurationWithOverrides(mTaskInfo));
+    }
+
+    /**
+     * Get {@link Configuration} from supplied {@link RunningTaskInfo}.
+     *
+     * Allows values to be overridden before returning the configuration.
+     */
+    protected Configuration getConfigurationWithOverrides(RunningTaskInfo taskInfo) {
+        return taskInfo.getConfiguration();
     }
 
     /**
@@ -165,7 +175,7 @@
 
         outResult.mRootView = rootView;
         rootView = null; // Clear it just in case we use it accidentally
-        final Configuration taskConfig = mTaskInfo.getConfiguration();
+        final Configuration taskConfig = getConfigurationWithOverrides(mTaskInfo);
         if (oldTaskConfig.densityDpi != taskConfig.densityDpi
                 || mDisplay == null
                 || mDisplay.getDisplayId() != mTaskInfo.displayId) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 0e14c69..108e273 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -53,6 +53,7 @@
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.TabletopModeController;
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.onehanded.OneHandedController;
 import com.android.wm.shell.pip.PipAnimationController;
@@ -115,6 +116,7 @@
     @Mock private Optional<OneHandedController> mMockOneHandedController;
     @Mock private PipParamsChangedForwarder mMockPipParamsChangedForwarder;
     @Mock private DisplayInsetsController mMockDisplayInsetsController;
+    @Mock private TabletopModeController mMockTabletopModeController;
 
     @Mock private DisplayLayout mMockDisplayLayout1;
     @Mock private DisplayLayout mMockDisplayLayout2;
@@ -137,7 +139,8 @@
                 mMockPipTaskOrganizer, mMockPipTransitionState, mMockPipTouchHandler,
                 mMockPipTransitionController, mMockWindowManagerShellWrapper,
                 mMockTaskStackListener, mMockPipParamsChangedForwarder,
-                mMockDisplayInsetsController, mMockOneHandedController, mMockExecutor);
+                mMockDisplayInsetsController, mMockTabletopModeController,
+                mMockOneHandedController, mMockExecutor);
         mShellInit.init();
         when(mMockPipBoundsAlgorithm.getSnapAlgorithm()).thenReturn(mMockPipSnapAlgorithm);
         when(mMockPipTouchHandler.getMotionHelper()).thenReturn(mMockPipMotionHelper);
@@ -228,7 +231,8 @@
                 mMockPipTaskOrganizer, mMockPipTransitionState, mMockPipTouchHandler,
                 mMockPipTransitionController, mMockWindowManagerShellWrapper,
                 mMockTaskStackListener, mMockPipParamsChangedForwarder,
-                mMockDisplayInsetsController, mMockOneHandedController, mMockExecutor));
+                mMockDisplayInsetsController, mMockTabletopModeController,
+                mMockOneHandedController, mMockExecutor));
     }
 
     @Test
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/effects/GainmapRenderer.cpp b/libs/hwui/effects/GainmapRenderer.cpp
index 8977d3c..bfe4eaf 100644
--- a/libs/hwui/effects/GainmapRenderer.cpp
+++ b/libs/hwui/effects/GainmapRenderer.cpp
@@ -23,21 +23,55 @@
 #include "utils/Trace.h"
 
 #ifdef __ANDROID__
+#include "include/core/SkColorSpace.h"
+#include "include/core/SkImage.h"
+#include "include/core/SkShader.h"
+#include "include/effects/SkRuntimeEffect.h"
+#include "include/private/SkGainmapInfo.h"
 #include "renderthread/CanvasContext.h"
+#include "src/core/SkColorFilterPriv.h"
+#include "src/core/SkImageInfoPriv.h"
+#include "src/core/SkRuntimeEffectPriv.h"
 #endif
 
 namespace android::uirenderer {
 
 using namespace renderthread;
 
+static float getTargetHdrSdrRatio(const SkColorSpace* destColorspace) {
+    // We should always have a known destination colorspace. If we don't we must be in some
+    // legacy mode where we're lost and also definitely not going to HDR
+    if (destColorspace == nullptr) {
+        return 1.f;
+    }
+
+    constexpr float GenericSdrWhiteNits = 203.f;
+    constexpr float maxPQLux = 10000.f;
+    constexpr float maxHLGLux = 1000.f;
+    skcms_TransferFunction destTF;
+    destColorspace->transferFn(&destTF);
+    if (skcms_TransferFunction_isPQish(&destTF)) {
+        return maxPQLux / GenericSdrWhiteNits;
+    } else if (skcms_TransferFunction_isHLGish(&destTF)) {
+        return maxHLGLux / GenericSdrWhiteNits;
+    } else {
+#ifdef __ANDROID__
+        CanvasContext* context = CanvasContext::getActiveContext();
+        return context ? context->targetSdrHdrRatio() : 1.f;
+#else
+        return 1.f;
+#endif
+    }
+}
+
 void DrawGainmapBitmap(SkCanvas* c, const sk_sp<const SkImage>& image, const SkRect& src,
                        const SkRect& dst, const SkSamplingOptions& sampling, const SkPaint* paint,
                        SkCanvas::SrcRectConstraint constraint,
                        const sk_sp<const SkImage>& gainmapImage, const SkGainmapInfo& gainmapInfo) {
     ATRACE_CALL();
 #ifdef __ANDROID__
-    CanvasContext* context = CanvasContext::getActiveContext();
-    float targetSdrHdrRatio = context ? context->targetSdrHdrRatio() : 1.f;
+    auto destColorspace = c->imageInfo().refColorSpace();
+    float targetSdrHdrRatio = getTargetHdrSdrRatio(destColorspace.get());
     if (targetSdrHdrRatio > 1.f && gainmapImage) {
         SkPaint gainmapPaint = *paint;
         float sX = gainmapImage->width() / (float)image->width();
@@ -48,9 +82,9 @@
         gainmapSrc.fRight *= sX;
         gainmapSrc.fTop *= sY;
         gainmapSrc.fBottom *= sY;
-        auto shader = SkGainmapShader::Make(image, src, sampling, gainmapImage, gainmapSrc,
-                                            sampling, gainmapInfo, dst, targetSdrHdrRatio,
-                                            c->imageInfo().refColorSpace());
+        auto shader =
+                SkGainmapShader::Make(image, src, sampling, gainmapImage, gainmapSrc, sampling,
+                                      gainmapInfo, dst, targetSdrHdrRatio, destColorspace);
         gainmapPaint.setShader(shader);
         c->drawRect(dst, gainmapPaint);
     } else
@@ -58,4 +92,213 @@
         c->drawImageRect(image.get(), src, dst, sampling, paint, constraint);
 }
 
+#ifdef __ANDROID__
+
+static constexpr char gGainmapSKSL[] = R"SKSL(
+    uniform shader base;
+    uniform shader gainmap;
+    uniform colorFilter workingSpaceToLinearSrgb;
+    uniform half4 logRatioMin;
+    uniform half4 logRatioMax;
+    uniform half4 gainmapGamma;
+    uniform half4 epsilonSdr;
+    uniform half4 epsilonHdr;
+    uniform half W;
+    uniform int gainmapIsAlpha;
+    uniform int gainmapIsRed;
+    uniform int singleChannel;
+    uniform int noGamma;
+
+    half4 toDest(half4 working) {
+        half4 ls = workingSpaceToLinearSrgb.eval(working);
+        vec3 dest = fromLinearSrgb(ls.rgb);
+        return half4(dest.r, dest.g, dest.b, ls.a);
+    }
+
+    half4 main(float2 coord) {
+        half4 S = base.eval(coord);
+        half4 G = gainmap.eval(coord);
+        if (gainmapIsAlpha == 1) {
+            G = half4(G.a, G.a, G.a, 1.0);
+        }
+        if (gainmapIsRed == 1) {
+            G = half4(G.r, G.r, G.r, 1.0);
+        }
+        if (singleChannel == 1) {
+            half L;
+            if (noGamma == 1) {
+                L = mix(logRatioMin.r, logRatioMax.r, G.r);
+            } else {
+                L = mix(logRatioMin.r, logRatioMax.r, pow(G.r, gainmapGamma.r));
+            }
+            half3 H = (S.rgb + epsilonSdr.rgb) * exp(L * W) - epsilonHdr.rgb;
+            return toDest(half4(H.r, H.g, H.b, S.a));
+        } else {
+            half3 L;
+            if (noGamma == 1) {
+                L = mix(logRatioMin.rgb, logRatioMax.rgb, G.rgb);
+            } else {
+                L = mix(logRatioMin.rgb, logRatioMax.rgb, pow(G.rgb, gainmapGamma.rgb));
+            }
+            half3 H = (S.rgb + epsilonSdr.rgb) * exp(L * W) - epsilonHdr.rgb;
+            return toDest(half4(H.r, H.g, H.b, S.a));
+        }
+    }
+)SKSL";
+
+static sk_sp<SkRuntimeEffect> gainmap_apply_effect() {
+    static const SkRuntimeEffect* effect = []() -> SkRuntimeEffect* {
+        auto buildResult = SkRuntimeEffect::MakeForShader(SkString(gGainmapSKSL), {});
+        if (buildResult.effect) {
+            return buildResult.effect.release();
+        } else {
+            LOG_ALWAYS_FATAL("Failed to build gainmap shader: %s", buildResult.errorText.c_str());
+        }
+    }();
+    SkASSERT(effect);
+    return sk_ref_sp(effect);
+}
+
+static bool all_channels_equal(const SkColor4f& c) {
+    return c.fR == c.fG && c.fR == c.fB;
+}
+
+class DeferredGainmapShader {
+private:
+    sk_sp<SkRuntimeEffect> mShader{gainmap_apply_effect()};
+    SkRuntimeShaderBuilder mBuilder{mShader};
+    SkGainmapInfo mGainmapInfo;
+    std::mutex mUniformGuard;
+
+    void setupChildren(const sk_sp<const SkImage>& baseImage,
+                       const sk_sp<const SkImage>& gainmapImage, SkTileMode tileModeX,
+                       SkTileMode tileModeY, const SkSamplingOptions& samplingOptions) {
+        sk_sp<SkColorSpace> baseColorSpace =
+                baseImage->colorSpace() ? baseImage->refColorSpace() : SkColorSpace::MakeSRGB();
+
+        // Determine the color space in which the gainmap math is to be applied.
+        sk_sp<SkColorSpace> gainmapMathColorSpace = baseColorSpace->makeLinearGamma();
+
+        // Create a color filter to transform from the base image's color space to the color space
+        // in which the gainmap is to be applied.
+        auto colorXformSdrToGainmap =
+                SkColorFilterPriv::MakeColorSpaceXform(baseColorSpace, gainmapMathColorSpace);
+
+        // The base image shader will convert into the color space in which the gainmap is applied.
+        auto baseImageShader = baseImage->makeRawShader(tileModeX, tileModeY, samplingOptions)
+                                       ->makeWithColorFilter(colorXformSdrToGainmap);
+
+        // The gainmap image shader will ignore any color space that the gainmap has.
+        const SkMatrix gainmapRectToDstRect =
+                SkMatrix::RectToRect(SkRect::MakeWH(gainmapImage->width(), gainmapImage->height()),
+                                     SkRect::MakeWH(baseImage->width(), baseImage->height()));
+        auto gainmapImageShader = gainmapImage->makeRawShader(tileModeX, tileModeY, samplingOptions,
+                                                              &gainmapRectToDstRect);
+
+        // Create a color filter to transform from the color space in which the gainmap is applied
+        // to the intermediate destination color space.
+        auto colorXformGainmapToDst = SkColorFilterPriv::MakeColorSpaceXform(
+                gainmapMathColorSpace, SkColorSpace::MakeSRGBLinear());
+
+        mBuilder.child("base") = std::move(baseImageShader);
+        mBuilder.child("gainmap") = std::move(gainmapImageShader);
+        mBuilder.child("workingSpaceToLinearSrgb") = std::move(colorXformGainmapToDst);
+    }
+
+    void setupGenericUniforms(const sk_sp<const SkImage>& gainmapImage,
+                              const SkGainmapInfo& gainmapInfo) {
+        const SkColor4f logRatioMin({sk_float_log(gainmapInfo.fGainmapRatioMin.fR),
+                                     sk_float_log(gainmapInfo.fGainmapRatioMin.fG),
+                                     sk_float_log(gainmapInfo.fGainmapRatioMin.fB), 1.f});
+        const SkColor4f logRatioMax({sk_float_log(gainmapInfo.fGainmapRatioMax.fR),
+                                     sk_float_log(gainmapInfo.fGainmapRatioMax.fG),
+                                     sk_float_log(gainmapInfo.fGainmapRatioMax.fB), 1.f});
+        const int noGamma = gainmapInfo.fGainmapGamma.fR == 1.f &&
+                            gainmapInfo.fGainmapGamma.fG == 1.f &&
+                            gainmapInfo.fGainmapGamma.fB == 1.f;
+        const uint32_t colorTypeFlags = SkColorTypeChannelFlags(gainmapImage->colorType());
+        const int gainmapIsAlpha = colorTypeFlags == kAlpha_SkColorChannelFlag;
+        const int gainmapIsRed = colorTypeFlags == kRed_SkColorChannelFlag;
+        const int singleChannel = all_channels_equal(gainmapInfo.fGainmapGamma) &&
+                                  all_channels_equal(gainmapInfo.fGainmapRatioMin) &&
+                                  all_channels_equal(gainmapInfo.fGainmapRatioMax) &&
+                                  (colorTypeFlags == kGray_SkColorChannelFlag ||
+                                   colorTypeFlags == kAlpha_SkColorChannelFlag ||
+                                   colorTypeFlags == kRed_SkColorChannelFlag);
+        mBuilder.uniform("logRatioMin") = logRatioMin;
+        mBuilder.uniform("logRatioMax") = logRatioMax;
+        mBuilder.uniform("gainmapGamma") = gainmapInfo.fGainmapGamma;
+        mBuilder.uniform("epsilonSdr") = gainmapInfo.fEpsilonSdr;
+        mBuilder.uniform("epsilonHdr") = gainmapInfo.fEpsilonHdr;
+        mBuilder.uniform("noGamma") = noGamma;
+        mBuilder.uniform("singleChannel") = singleChannel;
+        mBuilder.uniform("gainmapIsAlpha") = gainmapIsAlpha;
+        mBuilder.uniform("gainmapIsRed") = gainmapIsRed;
+    }
+
+    sk_sp<const SkData> build(float targetHdrSdrRatio) {
+        sk_sp<const SkData> uniforms;
+        {
+            // If we are called concurrently from multiple threads, we need to guard the call
+            // to writableUniforms() which mutates mUniform. This is otherwise safe because
+            // writeableUniforms() will make a copy if it's not unique before mutating
+            // This can happen if a BitmapShader is used on multiple canvas', such as a
+            // software + hardware canvas, which is otherwise valid as SkShader is "immutable"
+            std::lock_guard _lock(mUniformGuard);
+            const float Wunclamped = (sk_float_log(targetHdrSdrRatio) -
+                                      sk_float_log(mGainmapInfo.fDisplayRatioSdr)) /
+                                     (sk_float_log(mGainmapInfo.fDisplayRatioHdr) -
+                                      sk_float_log(mGainmapInfo.fDisplayRatioSdr));
+            const float W = std::max(std::min(Wunclamped, 1.f), 0.f);
+            mBuilder.uniform("W") = W;
+            uniforms = mBuilder.uniforms();
+        }
+        return uniforms;
+    }
+
+public:
+    explicit DeferredGainmapShader(const sk_sp<const SkImage>& image,
+                                   const sk_sp<const SkImage>& gainmapImage,
+                                   const SkGainmapInfo& gainmapInfo, SkTileMode tileModeX,
+                                   SkTileMode tileModeY, const SkSamplingOptions& sampling) {
+        mGainmapInfo = gainmapInfo;
+        setupChildren(image, gainmapImage, tileModeX, tileModeY, sampling);
+        setupGenericUniforms(gainmapImage, gainmapInfo);
+    }
+
+    static sk_sp<SkShader> Make(const sk_sp<const SkImage>& image,
+                                const sk_sp<const SkImage>& gainmapImage,
+                                const SkGainmapInfo& gainmapInfo, SkTileMode tileModeX,
+                                SkTileMode tileModeY, const SkSamplingOptions& sampling) {
+        auto deferredHandler = std::make_shared<DeferredGainmapShader>(
+                image, gainmapImage, gainmapInfo, tileModeX, tileModeY, sampling);
+        auto callback =
+                [deferredHandler](const SkRuntimeEffectPriv::UniformsCallbackContext& renderContext)
+                -> sk_sp<const SkData> {
+            return deferredHandler->build(getTargetHdrSdrRatio(renderContext.fDstColorSpace));
+        };
+        return SkRuntimeEffectPriv::MakeDeferredShader(deferredHandler->mShader.get(), callback,
+                                                       deferredHandler->mBuilder.children());
+    }
+};
+
+sk_sp<SkShader> MakeGainmapShader(const sk_sp<const SkImage>& image,
+                                  const sk_sp<const SkImage>& gainmapImage,
+                                  const SkGainmapInfo& gainmapInfo, SkTileMode tileModeX,
+                                  SkTileMode tileModeY, const SkSamplingOptions& sampling) {
+    return DeferredGainmapShader::Make(image, gainmapImage, gainmapInfo, tileModeX, tileModeY,
+                                       sampling);
+}
+
+#else  // __ANDROID__
+
+sk_sp<SkShader> MakeGainmapShader(const sk_sp<const SkImage>& image,
+                                  const sk_sp<const SkImage>& gainmapImage,
+                                  const SkGainmapInfo& gainmapInfo, SkTileMode tileModeX,
+                                  SkTileMode tileModeY, const SkSamplingOptions& sampling) {
+        return nullptr;
+}
+
+#endif  // __ANDROID__
+
 }  // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/effects/GainmapRenderer.h b/libs/hwui/effects/GainmapRenderer.h
index 7c56d94..4ed2445 100644
--- a/libs/hwui/effects/GainmapRenderer.h
+++ b/libs/hwui/effects/GainmapRenderer.h
@@ -30,4 +30,9 @@
                        SkCanvas::SrcRectConstraint constraint,
                        const sk_sp<const SkImage>& gainmapImage, const SkGainmapInfo& gainmapInfo);
 
+sk_sp<SkShader> MakeGainmapShader(const sk_sp<const SkImage>& image,
+                                  const sk_sp<const SkImage>& gainmapImage,
+                                  const SkGainmapInfo& gainmapInfo, SkTileMode tileModeX,
+                                  SkTileMode tileModeY, const SkSamplingOptions& sampling);
+
 }  // namespace android::uirenderer
diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp
index 75d45e5..7eb79be 100644
--- a/libs/hwui/jni/Shader.cpp
+++ b/libs/hwui/jni/Shader.cpp
@@ -1,6 +1,9 @@
 #undef LOG_TAG
 #define LOG_TAG "ShaderJNI"
 
+#include <vector>
+
+#include "Gainmap.h"
 #include "GraphicsJNI.h"
 #include "SkBitmap.h"
 #include "SkBlendMode.h"
@@ -17,10 +20,9 @@
 #include "SkShader.h"
 #include "SkString.h"
 #include "SkTileMode.h"
+#include "effects/GainmapRenderer.h"
 #include "include/effects/SkRuntimeEffect.h"
 
-#include <vector>
-
 using namespace android::uirenderer;
 
 /**
@@ -74,7 +76,20 @@
     if (bitmapHandle) {
         // Only pass a valid SkBitmap object to the constructor if the Bitmap exists. Otherwise,
         // we'll pass an empty SkBitmap to avoid crashing/excepting for compatibility.
-        image = android::bitmap::toBitmap(bitmapHandle).makeImage();
+        auto& bitmap = android::bitmap::toBitmap(bitmapHandle);
+        image = bitmap.makeImage();
+
+        if (!isDirectSampled && bitmap.hasGainmap()) {
+            sk_sp<SkShader> gainmapShader = MakeGainmapShader(
+                    image, bitmap.gainmap()->bitmap->makeImage(), bitmap.gainmap()->info,
+                    (SkTileMode)tileModeX, (SkTileMode)tileModeY, sampling);
+            if (gainmapShader) {
+                if (matrix) {
+                    gainmapShader = gainmapShader->makeWithLocalMatrix(*matrix);
+                }
+                return reinterpret_cast<jlong>(gainmapShader.release());
+            }
+        }
     }
 
     if (!image.get()) {
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 24cfc9d..c398405 100644
--- a/libs/input/MouseCursorController.cpp
+++ b/libs/input/MouseCursorController.cpp
@@ -63,24 +63,23 @@
     mLocked.pointerSprite.clear();
 }
 
-bool MouseCursorController::getBounds(float* outMinX, float* outMinY, float* outMaxX,
-                                      float* outMaxY) const {
+std::optional<FloatRect> MouseCursorController::getBounds() const {
     std::scoped_lock lock(mLock);
 
-    return getBoundsLocked(outMinX, outMinY, outMaxX, outMaxY);
+    return getBoundsLocked();
 }
 
-bool MouseCursorController::getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX,
-                                            float* outMaxY) const REQUIRES(mLock) {
+std::optional<FloatRect> MouseCursorController::getBoundsLocked() const REQUIRES(mLock) {
     if (!mLocked.viewport.isValid()) {
-        return false;
+        return {};
     }
 
-    *outMinX = mLocked.viewport.logicalLeft;
-    *outMinY = mLocked.viewport.logicalTop;
-    *outMaxX = mLocked.viewport.logicalRight - 1;
-    *outMaxY = mLocked.viewport.logicalBottom - 1;
-    return true;
+    return FloatRect{
+            static_cast<float>(mLocked.viewport.logicalLeft),
+            static_cast<float>(mLocked.viewport.logicalTop),
+            static_cast<float>(mLocked.viewport.logicalRight - 1),
+            static_cast<float>(mLocked.viewport.logicalBottom - 1),
+    };
 }
 
 void MouseCursorController::move(float deltaX, float deltaY) {
@@ -121,31 +120,19 @@
 }
 
 void MouseCursorController::setPositionLocked(float x, float y) REQUIRES(mLock) {
-    float minX, minY, maxX, maxY;
-    if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) {
-        if (x <= minX) {
-            mLocked.pointerX = minX;
-        } else if (x >= maxX) {
-            mLocked.pointerX = maxX;
-        } else {
-            mLocked.pointerX = x;
-        }
-        if (y <= minY) {
-            mLocked.pointerY = minY;
-        } else if (y >= maxY) {
-            mLocked.pointerY = maxY;
-        } else {
-            mLocked.pointerY = y;
-        }
-        updatePointerLocked();
-    }
+    const auto bounds = getBoundsLocked();
+    if (!bounds) return;
+
+    mLocked.pointerX = std::max(bounds->left, std::min(bounds->right, x));
+    mLocked.pointerY = std::max(bounds->top, std::min(bounds->bottom, y));
+
+    updatePointerLocked();
 }
 
-void MouseCursorController::getPosition(float* outX, float* outY) const {
+FloatPoint MouseCursorController::getPosition() const {
     std::scoped_lock lock(mLock);
 
-    *outX = mLocked.pointerX;
-    *outY = mLocked.pointerY;
+    return {mLocked.pointerX, mLocked.pointerY};
 }
 
 int32_t MouseCursorController::getDisplayId() const {
@@ -235,10 +222,9 @@
     // Reset cursor position to center if size or display changed.
     if (oldViewport.displayId != viewport.displayId || oldDisplayWidth != newDisplayWidth ||
         oldDisplayHeight != newDisplayHeight) {
-        float minX, minY, maxX, maxY;
-        if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) {
-            mLocked.pointerX = (minX + maxX) * 0.5f;
-            mLocked.pointerY = (minY + maxY) * 0.5f;
+        if (const auto bounds = getBoundsLocked(); bounds) {
+            mLocked.pointerX = (bounds->left + bounds->right) * 0.5f;
+            mLocked.pointerY = (bounds->top + bounds->bottom) * 0.5f;
             // Reload icon resources for density may be changed.
             loadResourcesLocked(getAdditionalMouseResources);
         } else {
diff --git a/libs/input/MouseCursorController.h b/libs/input/MouseCursorController.h
index db0ab56..26be2a8 100644
--- a/libs/input/MouseCursorController.h
+++ b/libs/input/MouseCursorController.h
@@ -43,12 +43,12 @@
     MouseCursorController(PointerControllerContext& context);
     ~MouseCursorController();
 
-    bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
+    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);
-    void getPosition(float* outX, float* outY) const;
+    FloatPoint getPosition() const;
     int32_t getDisplayId() const;
     void fade(PointerControllerInterface::Transition transition);
     void unfade(PointerControllerInterface::Transition transition);
@@ -102,7 +102,7 @@
 
     } mLocked GUARDED_BY(mLock);
 
-    bool getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
+    std::optional<FloatRect> getBoundsLocked() const;
     void setPositionLocked(float x, float y);
 
     void updatePointerLocked();
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index fedf58d..544edc2 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -114,16 +114,15 @@
 PointerController::~PointerController() {
     mDisplayInfoListener->onPointerControllerDestroyed();
     mUnregisterWindowInfosListener(mDisplayInfoListener);
-    mContext.getPolicy()->onPointerDisplayIdChanged(ADISPLAY_ID_NONE, 0, 0);
+    mContext.getPolicy()->onPointerDisplayIdChanged(ADISPLAY_ID_NONE, FloatPoint{0, 0});
 }
 
 std::mutex& PointerController::getLock() const {
     return mDisplayInfoListener->mLock;
 }
 
-bool PointerController::getBounds(float* outMinX, float* outMinY, float* outMaxX,
-                                  float* outMaxY) const {
-    return mCursorController.getBounds(outMinX, outMinY, outMaxX, outMaxY);
+std::optional<FloatRect> PointerController::getBounds() const {
+    return mCursorController.getBounds();
 }
 
 void PointerController::move(float deltaX, float deltaY) {
@@ -156,15 +155,13 @@
     mCursorController.setPosition(transformed.x, transformed.y);
 }
 
-void PointerController::getPosition(float* outX, float* outY) const {
+FloatPoint PointerController::getPosition() const {
     const int32_t displayId = mCursorController.getDisplayId();
-    mCursorController.getPosition(outX, outY);
+    const auto p = mCursorController.getPosition();
     {
         std::scoped_lock lock(getLock());
         const auto& transform = getTransformForDisplayLocked(displayId);
-        const auto xy = transform.inverse().transform(*outX, *outY);
-        *outX = xy.x;
-        *outY = xy.y;
+        return FloatPoint{transform.inverse().transform(p.x, p.y)};
     }
 }
 
@@ -262,19 +259,31 @@
 }
 
 void PointerController::setDisplayViewport(const DisplayViewport& viewport) {
-    std::scoped_lock lock(getLock());
+    struct PointerDisplayChangeArgs {
+        int32_t displayId;
+        FloatPoint cursorPosition;
+    };
+    std::optional<PointerDisplayChangeArgs> pointerDisplayChanged;
 
-    bool getAdditionalMouseResources = false;
-    if (mLocked.presentation == PointerController::Presentation::POINTER ||
-        mLocked.presentation == PointerController::Presentation::STYLUS_HOVER) {
-        getAdditionalMouseResources = true;
-    }
-    mCursorController.setDisplayViewport(viewport, getAdditionalMouseResources);
-    if (viewport.displayId != mLocked.pointerDisplayId) {
-        float xPos, yPos;
-        mCursorController.getPosition(&xPos, &yPos);
-        mContext.getPolicy()->onPointerDisplayIdChanged(viewport.displayId, xPos, yPos);
-        mLocked.pointerDisplayId = viewport.displayId;
+    { // acquire lock
+        std::scoped_lock lock(getLock());
+
+        bool getAdditionalMouseResources = false;
+        if (mLocked.presentation == PointerController::Presentation::POINTER ||
+            mLocked.presentation == PointerController::Presentation::STYLUS_HOVER) {
+            getAdditionalMouseResources = true;
+        }
+        mCursorController.setDisplayViewport(viewport, getAdditionalMouseResources);
+        if (viewport.displayId != mLocked.pointerDisplayId) {
+            mLocked.pointerDisplayId = viewport.displayId;
+            pointerDisplayChanged = {viewport.displayId, mCursorController.getPosition()};
+        }
+    } // release lock
+
+    if (pointerDisplayChanged) {
+        // Notify the policy without holding the pointer controller lock.
+        mContext.getPolicy()->onPointerDisplayIdChanged(pointerDisplayChanged->displayId,
+                                                        pointerDisplayChanged->cursorPosition);
     }
 }
 
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index 48d5a57..6d3557c 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -50,12 +50,12 @@
 
     ~PointerController() override;
 
-    virtual bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
+    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 void getPosition(float* outX, float* outY) const;
+    virtual FloatPoint getPosition() const;
     virtual int32_t getDisplayId() const;
     virtual void fade(Transition transition);
     virtual void unfade(Transition transition);
diff --git a/libs/input/PointerControllerContext.h b/libs/input/PointerControllerContext.h
index 96d83a5..f6f5d3b 100644
--- a/libs/input/PointerControllerContext.h
+++ b/libs/input/PointerControllerContext.h
@@ -81,7 +81,7 @@
     virtual PointerIconStyle getDefaultPointerIconId() = 0;
     virtual PointerIconStyle getDefaultStylusIconId() = 0;
     virtual PointerIconStyle getCustomPointerIconId() = 0;
-    virtual void onPointerDisplayIdChanged(int32_t displayId, float xPos, float yPos) = 0;
+    virtual void onPointerDisplayIdChanged(int32_t displayId, const FloatPoint& position) = 0;
 };
 
 /*
diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp
index c820d00..2378d42 100644
--- a/libs/input/tests/PointerController_test.cpp
+++ b/libs/input/tests/PointerController_test.cpp
@@ -60,7 +60,7 @@
     virtual PointerIconStyle getDefaultPointerIconId() override;
     virtual PointerIconStyle getDefaultStylusIconId() override;
     virtual PointerIconStyle getCustomPointerIconId() override;
-    virtual void onPointerDisplayIdChanged(int32_t displayId, float xPos, float yPos) override;
+    virtual void onPointerDisplayIdChanged(int32_t displayId, const FloatPoint& position) override;
 
     bool allResourcesAreLoaded();
     bool noResourcesAreLoaded();
@@ -143,8 +143,8 @@
 }
 
 void MockPointerControllerPolicyInterface::onPointerDisplayIdChanged(int32_t displayId,
-                                                                     float /*xPos*/,
-                                                                     float /*yPos*/) {
+                                                                     const FloatPoint& /*position*/
+) {
     latestPointerDisplayId = displayId;
 }
 
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index 23f87ab..f86b9af 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -1566,7 +1566,7 @@
         FileInputStream in = null;
         try {
             in = new FileInputStream(fileDescriptor);
-            loadAttributes(in);
+            loadAttributes(in, fileDescriptor);
         } finally {
             closeQuietly(in);
             if (isFdDuped) {
@@ -1637,7 +1637,7 @@
                 mSeekableFileDescriptor = null;
             }
         }
-        loadAttributes(inputStream);
+        loadAttributes(inputStream, null);
     }
 
     /**
@@ -1963,7 +1963,7 @@
      * This function decides which parser to read the image data according to the given input stream
      * type and the content of the input stream.
      */
-    private void loadAttributes(@NonNull InputStream in) {
+    private void loadAttributes(@NonNull InputStream in, @Nullable FileDescriptor fd) {
         if (in == null) {
             throw new NullPointerException("inputstream shouldn't be null");
         }
@@ -1993,7 +1993,7 @@
                         break;
                     }
                     case IMAGE_TYPE_HEIF: {
-                        getHeifAttributes(inputStream);
+                        getHeifAttributes(inputStream, fd);
                         break;
                     }
                     case IMAGE_TYPE_ORF: {
@@ -2580,7 +2580,7 @@
             } else if (isSeekableFD(in.getFD())) {
                 mSeekableFileDescriptor = in.getFD();
             }
-            loadAttributes(in);
+            loadAttributes(in, null);
         } finally {
             closeQuietly(in);
             if (modernFd != null) {
@@ -3068,59 +3068,66 @@
         }
     }
 
-    private void getHeifAttributes(ByteOrderedDataInputStream in) throws IOException {
+    private void getHeifAttributes(ByteOrderedDataInputStream in, @Nullable FileDescriptor fd)
+            throws IOException {
         MediaMetadataRetriever retriever = new MediaMetadataRetriever();
         try {
-            retriever.setDataSource(new MediaDataSource() {
-                long mPosition;
+            if (fd != null) {
+                retriever.setDataSource(fd);
+            } else {
+                retriever.setDataSource(new MediaDataSource() {
+                    long mPosition;
 
-                @Override
-                public void close() throws IOException {}
+                    @Override
+                    public void close() throws IOException {}
 
-                @Override
-                public int readAt(long position, byte[] buffer, int offset, int size)
-                        throws IOException {
-                    if (size == 0) {
-                        return 0;
-                    }
-                    if (position < 0) {
+                    @Override
+                    public int readAt(long position, byte[] buffer, int offset, int size)
+                            throws IOException {
+                        if (size == 0) {
+                            return 0;
+                        }
+                        if (position < 0) {
+                            return -1;
+                        }
+                        try {
+                            if (mPosition != position) {
+                                // We don't allow seek to positions after the available bytes,
+                                // the input stream won't be able to seek back then.
+                                // However, if we hit an exception before (mPosition set to -1),
+                                // let it try the seek in hope it might recover.
+                                if (mPosition >= 0 && position >= mPosition + in.available()) {
+                                    return -1;
+                                }
+                                in.seek(position);
+                                mPosition = position;
+                            }
+
+                            // If the read will cause us to go over the available bytes,
+                            // reduce the size so that we stay in the available range.
+                            // Otherwise the input stream may not be able to seek back.
+                            if (size > in.available()) {
+                                size = in.available();
+                            }
+
+                            int bytesRead = in.read(buffer, offset, size);
+                            if (bytesRead >= 0) {
+                                mPosition += bytesRead;
+                                return bytesRead;
+                            }
+                        } catch (IOException e) {
+                            // absorb the exception and fall through to the 'failed read' path below
+                        }
+                        mPosition = -1; // need to seek on next read
                         return -1;
                     }
-                    try {
-                        if (mPosition != position) {
-                            // We don't allow seek to positions after the available bytes,
-                            // the input stream won't be able to seek back then.
-                            // However, if we hit an exception before (mPosition set to -1),
-                            // let it try the seek in hope it might recover.
-                            if (mPosition >= 0 && position >= mPosition + in.available()) {
-                                return -1;
-                            }
-                            in.seek(position);
-                            mPosition = position;
-                        }
 
-                        // If the read will cause us to go over the available bytes,
-                        // reduce the size so that we stay in the available range.
-                        // Otherwise the input stream may not be able to seek back.
-                        if (size > in.available()) {
-                            size = in.available();
-                        }
-
-                        int bytesRead = in.read(buffer, offset, size);
-                        if (bytesRead >= 0) {
-                            mPosition += bytesRead;
-                            return bytesRead;
-                        }
-                    } catch (IOException e) {}
-                    mPosition = -1; // need to seek on next read
-                    return -1;
-                }
-
-                @Override
-                public long getSize() throws IOException {
-                    return -1;
-                }
-            });
+                    @Override
+                    public long getSize() throws IOException {
+                        return -1;
+                    }
+                });
+            }
 
             String exifOffsetStr = retriever.extractMetadata(
                     MediaMetadataRetriever.METADATA_KEY_EXIF_OFFSET);
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 7d6a8b4..ef2b5a5 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -149,10 +149,45 @@
     /**
      * This constant is used as a {@link Bundle} key for TV messages. The value of the key
      * identifies the stream on the TV input source for which the watermark event is relevant to.
+     *
+     * <p> Type: String
      */
     public static final String TV_MESSAGE_KEY_STREAM_ID =
             "android.media.tv.TvInputManager.stream_id";
 
+    /**
+     * This constant is used as a {@link Bundle} key for TV messages. The value of the key
+     * identifies the subtype of the data, such as the format of the CC data. The format
+     * found at this key can then be used to identify how to parse the data at
+     * {@link #TV_MESSAGE_KEY_RAW_DATA}.
+     *
+     * To parse the raw data bsed on the subtype, please refer to the official documentation of the
+     * concerning subtype. For example, for the subtype "ATSC A/335" for watermarking, the
+     * document for A/335 from the ATSC standard details how this data is formatted.
+     *
+     * Some other examples of common formats include:
+     * <ul>
+     *     <li>Watermarking - ATSC A/336</li>
+     *     <li>Closed Captioning - CTA 608-E</li>
+     * </ul>
+     *
+     * <p> Type: String
+     */
+    public static final String TV_MESSAGE_KEY_SUBTYPE =
+            "android.media.tv.TvInputManager.subtype";
+
+    /**
+     * This constant is used as a {@link Bundle} key for TV messages. The value of the key
+     * stores the raw data contained in this TV Message. The format of this data is determined
+     * by the format defined by the subtype, found using the key at
+     * {@link #TV_MESSAGE_KEY_SUBTYPE}. See {@link #TV_MESSAGE_KEY_SUBTYPE} for more
+     * information on how to parse this data.
+     *
+     * <p> Type: byte[]
+     */
+    public static final String TV_MESSAGE_KEY_RAW_DATA =
+            "android.media.tv.TvInputManager.raw_data";
+
     static final int VIDEO_UNAVAILABLE_REASON_START = 0;
     static final int VIDEO_UNAVAILABLE_REASON_END = 18;
 
@@ -802,7 +837,13 @@
          *
          * @param session A {@link TvInputManager.Session} associated with this callback.
          * @param type The type of message received, such as {@link #TV_MESSAGE_TYPE_WATERMARK}
-         * @param data The raw data of the message
+         * @param data The raw data of the message. The bundle keys are:
+         *             {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
+         *             {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE},
+         *             {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
+         *             See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
+         *             how to parse this data.
+         *
          */
         public void onTvMessage(Session session, @TvInputManager.TvMessageType int type,
                 Bundle data) {
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 3c6ed91..4e380c4 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -1030,7 +1030,12 @@
          *
          * @param type The of message that was sent, such as
          * {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK}
-         * @param data The data sent with the message.
+         * @param data The raw data of the message. The bundle keys are:
+         *             {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
+         *             {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE},
+         *             {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
+         *             See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
+         *             how to parse this data.
          */
         public void notifyTvMessage(@TvInputManager.TvMessageType int type,
                 @NonNull Bundle data) {
@@ -1500,7 +1505,12 @@
          *
          * @param type The type of message received, such as
          * {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK}
-         * @param data The raw data of the message
+         * @param data The raw data of the message. The bundle keys are:
+         *             {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
+         *             {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE},
+         *             {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
+         *             See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
+         *             how to parse this data.
          */
         public void onTvMessage(@NonNull @TvInputManager.TvMessageType String type,
                 @NonNull Bundle data) {
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index 19a2e5d..8a886832 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -1254,7 +1254,12 @@
          * @param inputId The ID of the TV input bound to this view.
          * @param type The type of message received, such as
          *             {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK}
-         * @param data The raw data of the message
+         * @param data The raw data of the message. The bundle keys are:
+         *             {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
+         *             {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE},
+         *             {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
+         *             See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
+         *             how to parse this data.
          */
         public void onTvMessage(@NonNull String inputId,
                 @TvInputManager.TvMessageType int type, @NonNull Bundle data) {
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index 7dfe16a..69ff9c6 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -919,7 +919,12 @@
          *
          * @param type The type of message received, such as
          * {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK}
-         * @param data The raw data of the message
+         * @param data The raw data of the message. The bundle keys are:
+         *             {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
+         *             {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE},
+         *             {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
+         *             See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
+         *             how to parse this data.
          */
         public void onTvMessage(@TvInputManager.TvMessageType int type,
                 @NonNull Bundle data) {
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index 1a0319b..80a1435 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -944,7 +944,12 @@
      *
      * @param type The type of message received, such as
      * {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK}
-     * @param data The raw data of the message
+     * @param data The raw data of the message. The bundle keys are:
+     *             {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
+     *             {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE},
+     *             {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
+     *             See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
+     *             how to parse this data.
      */
     public void notifyTvMessage(@NonNull @TvInputManager.TvMessageType int type,
             @NonNull Bundle 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/CarrierDefaultApp/res/values/strings.xml b/packages/CarrierDefaultApp/res/values/strings.xml
index e91d35b..f4e89a14 100644
--- a/packages/CarrierDefaultApp/res/values/strings.xml
+++ b/packages/CarrierDefaultApp/res/values/strings.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
-    <string name="app_name">CarrierDefaultApp</string>
+    <string name="app_name">Carrier Communications</string>
     <string name="android_system_label">Mobile Carrier</string>
     <string name="portal_notification_id">Mobile data has run out</string>
     <string name="no_data_notification_id">Your mobile data has been deactivated</string>
@@ -17,9 +17,9 @@
     <!-- Telephony notification channel name for performance boost notifications. -->
     <string name="performance_boost_notification_channel">Performance boost</string>
     <!-- Notification title text for the performance boost notification. -->
-    <string name="performance_boost_notification_title">Improve your app experience</string>
+    <string name="performance_boost_notification_title">5G options from your carrier</string>
     <!-- Notification detail text for the performance boost notification. -->
-    <string name="performance_boost_notification_detail">Tap to visit %s\'s website and learn more</string>
+    <string name="performance_boost_notification_detail">Visit %s\'s website to see options for your app experience</string>
     <!-- Notification button text to cancel the performance boost notification. -->
     <string name="performance_boost_notification_button_not_now">Not now</string>
     <!-- Notification button text to manage the performance boost notification. -->
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/InputDevices/res/raw/keyboard_layout_arabic.kcm b/packages/InputDevices/res/raw/keyboard_layout_arabic.kcm
index 1614188..9c2064c 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_arabic.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_arabic.kcm
@@ -22,89 +22,89 @@
 
 key GRAVE {
     label:                              '`'
-    base, capslock:                     '\u0630'
+    base:                               '\u0630'
     shift:                              '\u0651'
 }
 
 key 1 {
     label:                              '1'
     base:                               '\u0661'
-    shift:                              '!'
     capslock:                           '1'
+    shift:                              '!'
 }
 
 key 2 {
     label:                              '2'
     base:                               '\u0662'
-    shift:                              '@'
     capslock:                           '2'
+    shift:                              '@'
 }
 
 key 3 {
     label:                              '3'
     base:                               '\u0663'
-    shift:                              '#'
     capslock:                           '3'
+    shift:                              '#'
 }
 
 key 4 {
     label:                              '4'
     base:                               '\u0664'
-    shift:                              '$'
     capslock:                           '4'
+    shift:                              '$'
 }
 
 key 5 {
     label:                              '5'
     base:                               '\u0665'
-    shift:                              '%'
     capslock:                           '5'
+    shift:                              '%'
 }
 
 key 6 {
     label:                              '6'
     base:                               '\u0666'
-    shift:                              '^'
     capslock:                           '6'
+    shift:                              '^'
 }
 
 key 7 {
     label:                              '7'
     base:                               '\u0667'
-    shift:                              '&'
     capslock:                           '7'
+    shift:                              '&'
 }
 
 key 8 {
     label:                              '8'
     base:                               '\u0668'
-    shift:                              '*'
     capslock:                           '8'
+    shift:                              '*'
 }
 
 key 9 {
     label:                              '9'
     base:                               '\u0669'
-    shift:                              ')'
     capslock:                           '9'
+    shift:                              ')'
 }
 
 key 0 {
     label:                              '0'
     base:                               '\u0660'
-    shift:                              '('
     capslock:                           '0'
+    shift:                              '('
 }
 
 key MINUS {
     label:                              '-'
-    base, capslock:                     '-'
+    base:                               '-'
     shift:                              '_'
 }
 
 key EQUALS {
     label:                              '='
-    base, capslock:                     '='
+    base:                               '='
     shift:                              '+'
 }
 
@@ -112,79 +112,79 @@
 
 key Q {
     label:                              'Q'
-    base, capslock:                     '\u0636'
+    base:                               '\u0636'
     shift:                              '\u064e'
 }
 
 key W {
     label:                              'W'
-    base, capslock:                     '\u0635'
+    base:                               '\u0635'
     shift:                              '\u064b'
 }
 
 key E {
     label:                              'E'
-    base, capslock:                     '\u062b'
+    base:                               '\u062b'
     shift:                              '\u064f'
 }
 
 key R {
     label:                              'R'
-    base, capslock:                     '\u0642'
+    base:                               '\u0642'
     shift:                              '\u064c'
 }
 
 key T {
     label:                              'T'
-    base, capslock:                     '\u0641'
+    base:                               '\u0641'
     shift:                              '\ufef9'
 }
 
 key Y {
     label:                              'Y'
-    base, capslock:                     '\u063a'
+    base:                               '\u063a'
     shift:                              '\u0625'
 }
 
 key U {
     label:                              'U'
-    base, capslock:                     '\u0639'
+    base:                               '\u0639'
     shift:                              '\u2018'
 }
 
 key I {
     label:                              'I'
-    base, capslock:                     '\u0647'
+    base:                               '\u0647'
     shift:                              '\u00f7'
 }
 
 key O {
     label:                              'O'
-    base, capslock:                     '\u062e'
+    base:                               '\u062e'
     shift:                              '\u00d7'
 }
 
 key P {
     label:                              'P'
-    base, capslock:                     '\u062d'
+    base:                               '\u062d'
     shift:                              '\u061b'
 }
 
 key LEFT_BRACKET {
     label:                              ']'
-    base, capslock:                     '\u062c'
+    base:                               '\u062c'
     shift:                              '>'
 }
 
 key RIGHT_BRACKET {
     label:                              '['
-    base, capslock:                     '\u062f'
+    base:                               '\u062f'
     shift:                              '<'
 }
 
 key BACKSLASH {
     label:                              '\\'
-    base, capslock:                     '\\'
+    base:                               '\\'
     shift:                              '|'
 }
 
@@ -192,67 +192,67 @@
 
 key A {
     label:                              'A'
-    base, capslock:                     '\u0634'
+    base:                               '\u0634'
     shift:                              '\u0650'
 }
 
 key S {
     label:                              'S'
-    base, capslock:                     '\u0633'
+    base:                               '\u0633'
     shift:                              '\u064d'
 }
 
 key D {
     label:                              'D'
-    base, capslock:                     '\u064a'
+    base:                               '\u064a'
     shift:                              ']'
 }
 
 key F {
     label:                              'F'
-    base, capslock:                     '\u0628'
+    base:                               '\u0628'
     shift:                              '['
 }
 
 key G {
     label:                              'G'
-    base, capslock:                     '\u0644'
+    base:                               '\u0644'
     shift:                              '\ufef7'
 }
 
 key H {
     label:                              'H'
-    base, capslock:                     '\u0627'
+    base:                               '\u0627'
     shift:                              '\u0623'
 }
 
 key J {
     label:                              'J'
-    base, capslock:                     '\u062a'
+    base:                               '\u062a'
     shift:                              '\u0640'
 }
 
 key K {
     label:                              'K'
-    base, capslock:                     '\u0646'
+    base:                               '\u0646'
     shift:                              '\u060c'
 }
 
 key L {
     label:                              'L'
-    base, capslock:                     '\u0645'
+    base:                               '\u0645'
     shift:                              '/'
 }
 
 key SEMICOLON {
     label:                              ';'
-    base, capslock:                     '\u0643'
+    base:                               '\u0643'
     shift:                              ':'
 }
 
 key APOSTROPHE {
     label:                              '\''
-    base, capslock:                     '\u0637'
+    base:                               '\u0637'
     shift:                              '"'
 }
 
@@ -260,60 +260,60 @@
 
 key Z {
     label:                              'Z'
-    base, capslock:                     '\u0626'
+    base:                               '\u0626'
     shift:                              '~'
 }
 
 key X {
     label:                              'X'
-    base, capslock:                     '\u0621'
+    base:                               '\u0621'
     shift:                              '\u0652'
 }
 
 key C {
     label:                              'C'
-    base, capslock:                     '\u0624'
+    base:                               '\u0624'
     shift:                              '}'
 }
 
 key V {
     label:                              'V'
-    base, capslock:                     '\u0631'
+    base:                               '\u0631'
     shift:                              '{'
 }
 
 key B {
     label:                              'B'
-    base, capslock:                     '\ufefb'
+    base:                               '\ufefb'
     shift:                              '\ufef5'
 }
 
 key N {
     label:                              'N'
-    base, capslock:                     '\u0649'
+    base:                               '\u0649'
     shift:                              '\u0622'
 }
 
 key M {
     label:                              'M'
-    base, capslock:                     '\u0629'
+    base:                               '\u0629'
     shift:                              '\u2019'
 }
 
 key COMMA {
     label:                              ','
-    base, capslock:                     '\u0648'
+    base:                               '\u0648'
     shift:                              ','
 }
 
 key PERIOD {
     label:                              '.'
-    base, capslock:                     '\u0632'
+    base:                               '\u0632'
     shift:                              '.'
 }
 
 key SLASH {
     label:                              '/'
-    base, capslock:                     '\u0638'
+    base:                               '\u0638'
     shift:                              '\u061f'
 }
diff --git a/packages/InputDevices/res/raw/keyboard_layout_azerbaijani.kcm b/packages/InputDevices/res/raw/keyboard_layout_azerbaijani.kcm
index 69490cc..3f5e894 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_azerbaijani.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_azerbaijani.kcm
@@ -107,72 +107,84 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
 }
 
 key W {
     label:                              '\u00dc'
     base:                               '\u00fc'
     shift, capslock:                    '\u00dc'
+    shift+capslock:                     '\u00fc'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
 }
 
 key R {
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              '\u0130'
     base:                               'i'
     shift, capslock:                    '\u0130'
+    shift+capslock:                     'i'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
     label:                              '\u00d6'
     base:                               '\u00f6'
     shift:                              '\u00d6'
+    shift+capslock:                     '\u00f6'
 }
 
 key RIGHT_BRACKET {
     label:                              '\u011e'
     base:                               '\u011f'
     shift:                              '\u011e'
+    shift+capslock:                     '\u011f'
 }
 
 key BACKSLASH {
@@ -187,66 +199,77 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
     label:                              'I'
     base:                               '\u0131'
     shift:                              'I'
+    shift+capslock:                     '\u0131'
 }
 
 key APOSTROPHE {
     label:                              '\u018f'
     base:                               '\u0259'
     shift:                              '\u018f'
+    shift+capslock:                     '\u0259'
 }
 
 ### ROW 4
@@ -255,54 +278,63 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key COMMA {
     label:                              '\u00c7'
     base:                               '\u00e7'
     shift:                              '\u00c7'
+    shift+capslock:                     '\u00e7'
 }
 
 key PERIOD {
     label:                              '\u015e'
     base:                               '\u015f'
     shift:                              '\u015e'
+    shift+capslock:                     '\u015f'
 }
 
 key SLASH {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_belarusian.kcm b/packages/InputDevices/res/raw/keyboard_layout_belarusian.kcm
index 3deb9dd..6751e1d 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_belarusian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_belarusian.kcm
@@ -24,6 +24,7 @@
     label:                              '\u0401'
     base:                               '\u0451'
     shift, capslock:                    '\u0401'
+    shift+capslock:                     '\u0451'
     ralt:                               '`'
     ralt+shift:                         '~'
 }
@@ -106,163 +107,203 @@
     label:                              '\u0419'
     base:                               '\u0439'
     shift, capslock:                    '\u0419'
+    shift+capslock:                     '\u0439'
     ralt:                               'q'
-    ralt+shift, ralt+capslock:          'Q'
+    shift+ralt, capslock+ralt:          'Q'
+    shift+capslock+ralt:                'q'
 }
 key W {
     label:                              '\u0426'
     base:                               '\u0446'
     shift, capslock:                    '\u0426'
+    shift+capslock:                     '\u0446'
     ralt:                               'w'
-    ralt+shift, ralt+capslock:          'W'
+    shift+ralt, capslock+ralt:          'W'
+    shift+capslock+ralt:                'w'
 }
 key E {
     label:                              '\u0423'
     base:                               '\u0443'
     shift, capslock:                    '\u0423'
+    shift+capslock:                     '\u0443'
     ralt:                               'e'
-    ralt+shift, ralt+capslock:          'E'
+    shift+ralt, capslock+ralt:          'E'
+    shift+capslock+ralt:                'e'
 }
 key R {
     label:                              '\u041a'
     base:                               '\u043a'
     shift, capslock:                    '\u041a'
+    shift+capslock:                     '\u043a'
     ralt:                               'r'
-    ralt+shift, ralt+capslock:          'R'
+    shift+ralt, capslock+ralt:          'R'
+    shift+capslock+ralt:                'r'
 }
 key T {
     label:                              '\u0415'
     base:                               '\u0435'
     shift, capslock:                    '\u0415'
+    shift+capslock:                     '\u0435'
     ralt:                               't'
-    ralt+shift, ralt+capslock:          'T'
+    shift+ralt, capslock+ralt:          'T'
+    shift+capslock+ralt:                't'
 }
 key Y {
     label:                              '\u041d'
     base:                               '\u043d'
     shift, capslock:                    '\u041d'
+    shift+capslock:                     '\u043d'
     ralt:                               'y'
-    ralt+shift, ralt+capslock:          'Y'
+    shift+ralt, capslock+ralt:          'Y'
+    shift+capslock+ralt:                'y'
 }
 key U {
     label:                              '\u0413'
     base:                               '\u0433'
     shift, capslock:                    '\u0413'
+    shift+capslock:                     '\u0433'
     ralt:                               'u'
-    ralt+shift, ralt+capslock:          'U'
+    shift+ralt, capslock+ralt:          'U'
+    shift+capslock+ralt:                'u'
 }
 key I {
     label:                              '\u0428'
     base:                               '\u0448'
     shift, capslock:                    '\u0428'
+    shift+capslock:                     '\u0448'
     ralt:                               'i'
-    ralt+shift, ralt+capslock:          'I'
+    shift+ralt, capslock+ralt:          'I'
+    shift+capslock+ralt:                'i'
 }
 key O {
     label:                              '\u040E'
     base:                               '\u045E'
     shift, capslock:                    '\u040E'
+    shift+capslock:                     '\u045E'
     ralt:                               'o'
-    ralt+shift, ralt+capslock:          'O'
+    shift+ralt, capslock+ralt:          'O'
+    shift+capslock+ralt:                'o'
 }
 key P {
     label:                              '\u0417'
     base:                               '\u0437'
     shift, capslock:                    '\u0417'
+    shift+capslock:                     '\u0437'
     ralt:                               'p'
-    ralt+shift, ralt+capslock:          'P'
+    shift+ralt, capslock+ralt:          'P'
+    shift+capslock+ralt:                'p'
 }
 key LEFT_BRACKET {
     label:                              '\u0425'
     base:                               '\u0445'
     shift, capslock:                    '\u0425'
+    shift+capslock:                     '\u0445'
     ralt:                               '['
-    ralt+shift:                         '{'
+    shift+ralt:                         '{'
 }
 key RIGHT_BRACKET {
     label:                              '\u0027'
     base:                               '\u0027'
-    shift, capslock:                    '\u0027'
     ralt:                               ']'
-    ralt+shift:                         '}'
+    shift+ralt:                         '}'
 }
 ### ROW 3
 key A {
     label:                              '\u0424'
     base:                               '\u0444'
     shift, capslock:                    '\u0424'
+    shift+capslock:                     '\u0444'
     ralt:                               'a'
-    ralt+shift, ralt+capslock:          'A'
+    shift+ralt, capslock+ralt:          'A'
+    shift+capslock+ralt:                'a'
 }
 key S {
     label:                              '\u042b'
     base:                               '\u044b'
     shift, capslock:                    '\u042b'
+    shift+capslock:                     '\u044b'
     ralt:                               's'
-    ralt+shift, ralt+capslock:          'S'
+    shift+ralt, capslock+ralt:          'S'
+    shift+capslock+ralt:                's'
 }
 key D {
     label:                              '\u0412'
     base:                               '\u0432'
     shift, capslock:                    '\u0412'
+    shift+capslock:                     '\u0432'
     ralt:                               'd'
-    ralt+shift, ralt+capslock:          'D'
+    shift+ralt, capslock+ralt:          'D'
+    shift+capslock+ralt:                'd'
 }
 key F {
     label:                              '\u0410'
     base:                               '\u0430'
     shift, capslock:                    '\u0410'
+    shift+capslock:                     '\u0430'
     ralt:                               'f'
-    ralt+shift, ralt+capslock:          'F'
+    shift+ralt, capslock+ralt:          'F'
+    shift+capslock+ralt:                'f'
 }
 key G {
     label:                              '\u041f'
     base:                               '\u043f'
     shift, capslock:                    '\u041f'
+    shift+capslock:                     '\u043f'
     ralt:                               'g'
-    ralt+shift, ralt+capslock:          'G'
+    shift+ralt, capslock+ralt:          'G'
+    shift+capslock+ralt:                'g'
 }
 key H {
     label:                              '\u0420'
     base:                               '\u0440'
     shift, capslock:                    '\u0420'
+    shift+capslock:                     '\u0440'
     ralt:                               'h'
-    ralt+shift, ralt+capslock:          'H'
+    shift+ralt, capslock+ralt:          'H'
+    shift+capslock+ralt:                'h'
 }
 key J {
     label:                              '\u041e'
     base:                               '\u043e'
     shift, capslock:                    '\u041e'
+    shift+capslock:                     '\u043e'
     ralt:                               'j'
-    ralt+shift, ralt+capslock:          'J'
+    shift+ralt, capslock+ralt:          'J'
+    shift+capslock+ralt:                'j'
 }
 key K {
     label:                              '\u041b'
     base:                               '\u043b'
     shift, capslock:                    '\u041b'
+    shift+capslock:                     '\u043b'
     ralt:                               'k'
-    ralt+shift, ralt+capslock:          'K'
+    shift+ralt, capslock+ralt:          'K'
+    shift+capslock+ralt:                'k'
 }
 key L {
     label:                              '\u0414'
     base:                               '\u0434'
     shift, capslock:                    '\u0414'
+    shift+capslock:                     '\u0434'
     ralt:                               'l'
-    ralt+shift, ralt+capslock:          'L'
+    shift+ralt, capslock+ralt:          'L'
+    shift+capslock+ralt:                'l'
 }
 key SEMICOLON {
     label:                              '\u0416'
     base:                               '\u0436'
     shift, capslock:                    '\u0416'
+    shift+capslock:                     '\u0436'
     ralt:                               ';'
-    ralt+shift:                         ':'
+    shift+ralt:                         ':'
 }
 key APOSTROPHE {
     label:                              '\u042d'
     base:                               '\u044d'
     shift, capslock:                    '\u042d'
+    shift+capslock:                     '\u044d'
     ralt:                               '\''
-    ralt+shift:                         '"'
+    shift+ralt:                         '"'
 }
 key BACKSLASH {
     label:                              '\\'
@@ -275,69 +316,85 @@
     label:                              '\u042f'
     base:                               '\u044f'
     shift, capslock:                    '\u042f'
+    shift+capslock:                     '\u044f'
     ralt:                               'z'
-    ralt+shift, ralt+capslock:          'Z'
+    shift+ralt, capslock+ralt:          'Z'
+    shift+capslock+ralt:                'z'
 }
 key X {
     label:                              '\u0427'
     base:                               '\u0447'
     shift, capslock:                    '\u0427'
+    shift+capslock:                     '\u0447'
     ralt:                               'x'
-    ralt+shift, ralt+capslock:          'X'
+    shift+ralt, capslock+ralt:          'X'
+    shift+capslock+ralt:                'x'
 }
 key C {
     label:                              '\u0421'
     base:                               '\u0441'
     shift, capslock:                    '\u0421'
+    shift+capslock:                      '\u0441'
     ralt:                               'c'
-    ralt+shift, ralt+capslock:          'C'
+    shift+ralt, capslock+ralt:          'C'
+    shift+capslock+ralt:                'c'
 }
 key V {
     label:                              '\u041c'
     base:                               '\u043c'
     shift, capslock:                    '\u041c'
+    shift+capslock:                     '\u043c'
     ralt:                               'v'
-    ralt+shift, ralt+capslock:          'V'
+    shift+ralt, capslock+ralt:          'V'
+    shift+capslock+ralt:                'v'
 }
 key B {
     label:                              '\u0406'
     base:                               '\u0456'
     shift, capslock:                    '\u0406'
+    shift+capslock:                     '\u0456'
     ralt:                               'b'
-    ralt+shift, ralt+capslock:          'B'
+    shift+ralt, capslock+ralt:          'B'
+    shift+capslock+ralt:                'b'
 }
 key N {
     label:                              '\u0422'
     base:                               '\u0442'
     shift, capslock:                    '\u0422'
+    shift+capslock:                     '\u0442'
     ralt:                               'n'
-    ralt+shift, ralt+capslock:          'N'
+    shift+ralt, capslock+ralt:          'N'
+    shift+capslock+ralt:                'n'
 }
 key M {
     label:                              '\u042c'
     base:                               '\u044c'
     shift, capslock:                    '\u042c'
+    shift+capslock:                     '\u044c'
     ralt:                               'm'
-    ralt+shift, ralt+capslock:          'M'
+    shift+ralt, capslock+ralt:          'M'
+    shift+capslock+ralt:                'm'
 }
 key COMMA {
     label:                              '\u0411'
     base:                               '\u0431'
     shift, capslock:                    '\u0411'
+    shift+capslock:                     '\u0431'
     ralt:                               ','
-    ralt+shift:                         '<'
+    shift+ralt:                         '<'
 }
 key PERIOD {
     label:                              '\u042e'
     base:                               '\u044e'
     shift, capslock:                    '\u042e'
+    shift+capslock:                     '\u044e'
     ralt:                               '.'
-    ralt+shift:                         '>'
+    shift+ralt:                         '>'
 }
 key SLASH {
     label:                              '.'
     base:                               '.'
     shift:                              ','
     ralt:                               '/'
-    ralt+shift:                         '?'
+    shift+ralt:                         '?'
 }
diff --git a/packages/InputDevices/res/raw/keyboard_layout_belgian.kcm b/packages/InputDevices/res/raw/keyboard_layout_belgian.kcm
index f2c39ce..d529311 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_belgian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_belgian.kcm
@@ -122,18 +122,21 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key Z {
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
 }
 
@@ -141,42 +144,49 @@
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
@@ -199,60 +209,70 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key APOSTROPHE {
@@ -282,36 +302,42 @@
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_brazilian.kcm b/packages/InputDevices/res/raw/keyboard_layout_brazilian.kcm
index 140c7ac..ad3199f 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_brazilian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_brazilian.kcm
@@ -115,6 +115,7 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
     ralt:                               '/'
 }
 
@@ -122,6 +123,7 @@
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
     ralt:                               '?'
 }
 
@@ -129,6 +131,7 @@
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
 }
 
@@ -136,42 +139,49 @@
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
@@ -193,60 +203,70 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
     label:                              '\u00c7'
     base:                               '\u00e7'
     shift, capslock:                    '\u00c7'
+    shift+capslock:                     '\u00e7'
 }
 
 key APOSTROPHE {
@@ -258,7 +278,7 @@
 key BACKSLASH {
     label:                              ']'
     base:                               ']'
-    shift, capslock:                    '}'
+    shift:                              '}'
     ralt:                               '\u00ba'
 }
 
@@ -274,18 +294,21 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
     ralt:                               '\u20a2'
 }
 
@@ -293,24 +316,28 @@
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'n'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_bulgarian.kcm b/packages/InputDevices/res/raw/keyboard_layout_bulgarian.kcm
index c56367e..94ffbd0 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_bulgarian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_bulgarian.kcm
@@ -27,7 +27,7 @@
 key GRAVE {
     label:                              '`'
     base:                               '`'
-    shift, capslock:                    '~'
+    shift:                              '~'
     ralt:                               '`'
     ralt+shift:                         '~'
 }
@@ -123,89 +123,109 @@
     label:                              ','
     base:                               ','
     shift:                              '\u044b'
-    capslock:                           '\u042b'
+    shift+capslock:                     '\u042b'
     ralt:                               'q'
-    ralt+shift, ralt+capslock:          'Q'
+    shift+ralt, capslock+ralt:          'Q'
+    shift+capslock+ralt:                'q'
 }
 
 key W {
     label:                              '\u0423'
     base:                               '\u0443'
     shift, capslock:                    '\u0423'
+    shift+capslock:                     '\u0443'
     ralt:                               'w'
-    ralt+shift, ralt+capslock:          'W'
+    shift+ralt, capslock+ralt:          'W'
+    shift+capslock+ralt:                'w'
 }
 
 key E {
     label:                              '\u0415'
     base:                               '\u0435'
     shift, capslock:                    '\u0415'
+    shift+capslock:                     '\u0435'
     ralt:                               'e'
-    ralt+shift, ralt+capslock:          'E'
+    shift+ralt, capslock+ralt:          'E'
+    shift+capslock+ralt:                'e'
 }
 
 key R {
     label:                              '\u0418'
     base:                               '\u0438'
     shift, capslock:                    '\u0418'
+    shift+capslock:                     '\u0438'
     ralt:                               'r'
-    ralt+shift, ralt+capslock:          'R'
+    shift+ralt, capslock+ralt:          'R'
+    shift+capslock+ralt:                'r'
 }
 
 key T {
     label:                              '\u0428'
     base:                               '\u0448'
     shift, capslock:                    '\u0428'
+    shift+capslock:                     '\u0448'
     ralt:                               't'
-    ralt+shift, ralt+capslock:          'T'
+    shift+ralt, capslock+ralt:          'T'
+    shift+capslock+ralt:                't'
 }
 
 key Y {
     label:                              '\u0429'
     base:                               '\u0449'
     shift, capslock:                    '\u0429'
+    shift+capslock:                     '\u0449'
     ralt:                               'y'
-    ralt+shift, ralt+capslock:          'Y'
+    shift+ralt, capslock+ralt:          'Y'
+    shift+capslock+ralt:                'y'
 }
 
 key U {
     label:                              '\u041a'
     base:                               '\u043a'
     shift, capslock:                    '\u041a'
+    shift+capslock:                     '\u043a'
     ralt:                               'u'
-    ralt+shift, ralt+capslock:          'U'
+    shift+ralt, capslock+ralt:          'U'
+    shift+capslock+ralt:                'u'
 }
 
 key I {
     label:                              '\u0421'
     base:                               '\u0441'
     shift, capslock:                    '\u0421'
+    shift+capslock:                     '\u0441'
     ralt:                               'i'
-    ralt+shift, ralt+capslock:          'I'
+    shift+ralt, capslock+ralt:          'I'
+    shift+capslock+ralt:                'i'
 }
 
 key O {
     label:                              '\u0414'
     base:                               '\u0434'
     shift, capslock:                    '\u0414'
+    shift+capslock:                     '\u0434'
     ralt:                               'o'
-    ralt+shift, ralt+capslock:          'O'
+    shift+ralt, capslock+ralt:          'O'
+    shift+capslock+ralt:                'o'
 }
 
 key P {
     label:                              '\u0417'
     base:                               '\u0437'
     shift, capslock:                    '\u0417'
+    shift+capslock:                     '\u0437'
     ralt:                               'p'
-    ralt+shift, ralt+capslock:          'P'
+    shift+ralt, capslock+ralt:          'P'
+    shift+capslock+ralt:                'p'
 }
 
 key LEFT_BRACKET {
     label:                              '\u0426'
     base:                               '\u0446'
     shift, capslock:                    '\u0426'
+    shift+capslock:                     '\u0446'
     ralt:                               '['
-    ralt+shift:                         '{'
+    shift+ralt:                         '{'
 }
 
 key RIGHT_BRACKET {
@@ -213,7 +233,7 @@
     base:                               ';'
     shift:                              '\u00a7'
     ralt:                               ']'
-    ralt+shift:                         '}'
+    shift+ralt:                         '}'
 }
 
 ### ROW 3
@@ -222,78 +242,97 @@
     label:                              '\u042c'
     base:                               '\u044c'
     shift, capslock:                    '\u042c'
+    shift+capslock:                     '\u044c'
     ralt:                               'a'
-    ralt+shift, ralt+capslock:          'A'
+    shift+ralt, capslock+ralt:          'A'
+    shift+capslock+ralt:                'a'
 }
 
 key S {
     label:                              '\u042f'
     base:                               '\u044f'
     shift, capslock:                    '\u042f'
+    shift+capslock:                     '\u044f'
     ralt:                               's'
-    ralt+shift, ralt+capslock:          'S'
+    shift+ralt, capslock+ralt:          'S'
+    shift+capslock+ralt:                's'
 }
 
 key D {
     label:                              '\u0410'
     base:                               '\u0430'
     shift, capslock:                    '\u0410'
+    shift+capslock:                     '\u0430'
     ralt:                               'd'
-    ralt+shift, ralt+capslock:          'D'
+    shift+ralt, capslock+ralt:          'D'
+    shift+capslock+ralt:                'd'
 }
 
 key F {
     label:                              '\u041e'
     base:                               '\u043e'
     shift, capslock:                    '\u041e'
+    shift+capslock:                     '\u043e'
     ralt:                               'f'
-    ralt+shift, ralt+capslock:          'F'
+    shift+ralt, capslock+ralt:          'F'
+    shift+capslock+ralt:                'f'
 }
 
 key G {
     label:                              '\u0416'
     base:                               '\u0436'
     shift, capslock:                    '\u0416'
+    shift+capslock:                     '\u0436'
     ralt:                               'g'
-    ralt+shift, ralt+capslock:          'G'
+    shift+ralt, capslock+ralt:          'G'
+    shift+capslock+ralt:                'g'
 }
 
 key H {
     label:                              '\u0413'
     base:                               '\u0433'
     shift, capslock:                    '\u0413'
+    shift+capslock:                     '\u0433'
     ralt:                               'h'
-    ralt+shift, ralt+capslock:          'H'
+    shift+ralt, capslock+ralt:          'H'
+    shift+capslock+ralt:                'h'
 }
 
 key J {
     label:                              '\u0422'
     base:                               '\u0442'
     shift, capslock:                    '\u0422'
+    shift+capslock:                     '\u0442'
     ralt:                               'j'
-    ralt+shift, ralt+capslock:          'J'
+    shift+ralt, capslock+ralt:          'J'
+    shift+capslock+ralt:                'j'
 }
 
 key K {
     label:                              '\u041d'
     base:                               '\u043d'
     shift, capslock:                    '\u041d'
+    shift+capslock:                     '\u043d'
     ralt:                               'k'
-    ralt+shift, ralt+capslock:          'K'
+    shift+ralt, capslock+ralt:          'K'
+    shift+capslock+ralt:                'k'
 }
 
 key L {
     label:                              '\u0412'
     base:                               '\u0432'
     shift, capslock:                    '\u0412'
+    shift+capslock:                     '\u0432'
     ralt:                               'l'
-    ralt+shift, ralt+capslock:          'L'
+    shift+ralt, capslock+ralt:          'L'
+    shift+capslock+ralt:                'l'
 }
 
 key SEMICOLON {
     label:                              '\u041c'
     base:                               '\u043c'
     shift, capslock:                    '\u041c'
+    shift+capslock:                     '\u043c'
     ralt:                               ';'
     ralt+shift:                         ':'
 }
@@ -302,6 +341,7 @@
     label:                              '\u0427'
     base:                               '\u0447'
     shift, capslock:                    '\u0427'
+    shift+capslock:                     '\u0447'
     ralt:                               '\''
     ralt+shift:                         '"'
 }
@@ -328,62 +368,77 @@
     label:                              '\u042e'
     base:                               '\u044e'
     shift, capslock:                    '\u042e'
+    shift+capslock:                     '\u044e'
     ralt:                               'z'
-    ralt+shift, ralt+capslock:          'Z'
+    shift+ralt, capslock+ralt:          'Z'
+    shift+capslock+ralt:                'z'
 }
 
 key X {
     label:                              '\u0419'
     base:                               '\u0439'
     shift, capslock:                    '\u0419'
+    shift+capslock:                     '\u0439'
     ralt:                               'x'
-    ralt+shift, ralt+capslock:          'X'
+    shift+ralt, capslock+ralt:          'X'
+    shift+capslock+ralt:                'x'
 }
 
 key C {
     label:                              '\u042a'
     base:                               '\u044a'
     shift, capslock:                    '\u042a'
+    shift+capslock:                     '\u044a'
     ralt:                               'c'
-    ralt+shift, ralt+capslock:          'C'
+    shift+ralt, capslock+ralt:          'C'
+    shift+capslock+ralt:                'c'
 }
 
 key V {
     label:                              '\u042d'
     base:                               '\u044d'
     shift, capslock:                    '\u042d'
+    shift+capslock:                     '\u044d'
     ralt:                               'v'
-    ralt+shift, ralt+capslock:          'V'
+    shift+ralt, capslock+ralt:          'V'
+    shift+capslock+ralt:                'v'
 }
 
 key B {
     label:                              '\u0424'
     base:                               '\u0444'
     shift, capslock:                    '\u0424'
+    shift+capslock:                     '\u0444'
     ralt:                               'b'
-    ralt+shift, ralt+capslock:          'B'
+    shift+ralt, capslock+ralt:          'B'
+    shift+capslock+ralt:                'b'
 }
 
 key N {
     label:                              '\u0425'
     base:                               '\u0445'
     shift, capslock:                    '\u0425'
+    shift+capslock:                     '\u0445'
     ralt:                               'n'
-    ralt+shift, ralt+capslock:          'N'
+    shift+ralt, capslock+ralt:          'N'
+    shift+capslock+ralt:                'n'
 }
 
 key M {
     label:                              '\u041f'
     base:                               '\u043f'
     shift, capslock:                    '\u041f'
+    shift+capslock:                     '\u043f'
     ralt:                               'm'
-    ralt+shift, ralt+capslock:          'M'
+    shift+ralt, capslock+ralt:          'M'
+    shift+capslock+ralt:                'm'
 }
 
 key COMMA {
     label:                              '\u0420'
     base:                               '\u0440'
     shift, capslock:                    '\u0420'
+    shift+capslock:                     '\u0440'
     ralt:                               ','
     ralt+shift:                         '<'
 }
@@ -392,6 +447,7 @@
     label:                              '\u041b'
     base:                               '\u043b'
     shift, capslock:                    '\u041b'
+    shift+capslock:                     '\u043b'
     ralt:                               '.'
     ralt+shift:                         '>'
 }
@@ -400,6 +456,7 @@
     label:                              '\u0411'
     base:                               '\u0431'
     shift, capslock:                    '\u0411'
+    shift+capslock:                     '\u0431'
     ralt:                               '/'
     ralt+shift:                         '?'
 }
diff --git a/packages/InputDevices/res/raw/keyboard_layout_bulgarian_phonetic.kcm b/packages/InputDevices/res/raw/keyboard_layout_bulgarian_phonetic.kcm
index 8878807..6314158 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_bulgarian_phonetic.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_bulgarian_phonetic.kcm
@@ -28,6 +28,7 @@
     label:                              '`'
     base:                               '\u044e'
     shift, capslock:                    '\u042e'
+    shift+capslock:                     '\u044e'
     ralt:                               '`'
     ralt+shift:                         '~'
 }
@@ -122,88 +123,108 @@
 key Q {
     label:                              '\u0447'
     base:                               '\u0447'
-    shift:                              '\u0427'
-    capslock:                           '\u0427'
+    shift, capslock:                    '\u0427'
+    shift+capslock:                     '\u0447'
     ralt:                               'q'
-    ralt+shift, ralt+capslock:          'Q'
+    shift+ralt, capslock+ralt:          'Q'
+    shift+capslock+ralt:                'q'
 }
 
 key W {
     label:                              '\u0448'
     base:                               '\u0448'
     shift, capslock:                    '\u0428'
+    shift+capslock:                     '\u0448'
     ralt:                               'w'
-    ralt+shift, ralt+capslock:          'W'
+    shift+ralt, capslock+ralt:          'W'
+    shift+capslock+ralt:                'w'
 }
 
 key E {
     label:                              '\u0435'
     base:                               '\u0435'
     shift, capslock:                    '\u0415'
+    shift+capslock:                     '\u0435'
     ralt:                               'e'
-    ralt+shift, ralt+capslock:          'E'
+    shift+ralt, capslock+ralt:          'E'
+    shift+capslock+ralt:                'e'
 }
 
 key R {
     label:                              '\u0440'
     base:                               '\u0440'
     shift, capslock:                    '\u0420'
+    shift+capslock:                     '\u0440'
     ralt:                               'r'
-    ralt+shift, ralt+capslock:          'R'
+    shift+ralt, capslock+ralt:          'R'
+    shift+capslock+ralt:                'r'
 }
 
 key T {
     label:                              '\u0442'
     base:                               '\u0442'
     shift, capslock:                    '\u0422'
+    shift+capslock:                     '\u0442'
     ralt:                               't'
-    ralt+shift, ralt+capslock:          'T'
+    shift+ralt, capslock+ralt:          'T'
+    shift+capslock+ralt:                't'
 }
 
 key Y {
     label:                              '\u044a'
     base:                               '\u044a'
     shift, capslock:                    '\u042a'
+    shift+capslock:                     '\u044a'
     ralt:                               'y'
-    ralt+shift, ralt+capslock:          'Y'
+    shift+ralt, capslock+ralt:          'Y'
+    shift+capslock+ralt:                'y'
 }
 
 key U {
     label:                              '\u0443'
     base:                               '\u0443'
     shift, capslock:                    '\u0423'
+    shift+capslock:                     '\u0443'
     ralt:                               'u'
-    ralt+shift, ralt+capslock:          'U'
+    shift+ralt, capslock+ralt:          'U'
+    shift+capslock+ralt:                'u'
 }
 
 key I {
     label:                              '\u0438'
     base:                               '\u0438'
     shift, capslock:                    '\u0418'
+    shift+capslock:                     '\u0438'
     ralt:                               'i'
-    ralt+shift, ralt+capslock:          'I'
+    shift+ralt, capslock+ralt:          'I'
+    shift+capslock+ralt:                'i'
 }
 
 key O {
     label:                              '\u043e'
     base:                               '\u043e'
     shift, capslock:                    '\u041e'
+    shift+capslock:                     '\u043e'
     ralt:                               'o'
-    ralt+shift, ralt+capslock:          'O'
+    shift+ralt, capslock+ralt:          'O'
+    shift+capslock+ralt:                'o'
 }
 
 key P {
     label:                              '\u043f'
     base:                               '\u043f'
     shift, capslock:                    '\u041f'
+    shift+capslock:                     '\u043f'
     ralt:                               'p'
-    ralt+shift, ralt+capslock:          'P'
+    shift+ralt, capslock+ralt:          'P'
+    shift+capslock+ralt:                'p'
 }
 
 key LEFT_BRACKET {
     label:                              '\u044f'
     base:                               '\u044f'
     shift, capslock:                    '\u042f'
+    shift+capslock:                     '\u044f'
     ralt:                               '['
     ralt+shift:                         '{'
 }
@@ -211,7 +232,8 @@
 key RIGHT_BRACKET {
     label:                              '\u0449'
     base:                               '\u0449'
-    shift:                              '\u0429'
+    shift, capslock:                    '\u0429'
+    shift+capslock:                     '\u0449'
     ralt:                               ']'
     ralt+shift:                         '}'
 }
@@ -219,9 +241,8 @@
 key BACKSLASH {
     label:                              '\u044c'
     base:                               '\u044c'
-    shift:                              '\u042c'
-    capslock:                           '\u042c'
-    shift+capslock:                     '\u040d'
+    shift, capslock:                    '\u042c'
+    shift+capslock:                     '\u044c'
     ralt:                               '\\'
     ralt+shift:                         '|'
 }
@@ -232,78 +253,96 @@
     label:                              '\u0430'
     base:                               '\u0430'
     shift, capslock:                    '\u0410'
+    shift+capslock:                     '\u0430'
     ralt:                               'a'
-    ralt+shift, ralt+capslock:          'A'
+    shift+ralt, capslock+ralt:          'A'
+    shift+capslock+ralt:                'a'
 }
 
 key S {
     label:                              '\u0441'
     base:                               '\u0441'
     shift, capslock:                    '\u0421'
+    shift+capslock:                     '\u0441'
     ralt:                               's'
-    ralt+shift, ralt+capslock:          'S'
+    shift+ralt, capslock+ralt:          'S'
+    shift+capslock+ralt:                's'
 }
 
 key D {
     label:                              '\u0434'
     base:                               '\u0434'
     shift, capslock:                    '\u0414'
+    shift+capslock:                     '\u0434'
     ralt:                               'd'
-    ralt+shift, ralt+capslock:          'D'
+    shift+ralt, capslock+ralt:          'D'
+    shift+capslock+ralt:                'd'
 }
 
 key F {
     label:                              '\u0444'
     base:                               '\u0444'
     shift, capslock:                    '\u0424'
+    shift+capslock:                     '\u0444'
     ralt:                               'f'
-    ralt+shift, ralt+capslock:          'F'
+    shift+ralt, capslock+ralt:          'F'
+    shift+capslock+ralt:                'f'
 }
 
 key G {
     label:                              '\u0433'
     base:                               '\u0433'
     shift, capslock:                    '\u0413'
+    shift+capslock:                     '\u0433'
     ralt:                               'g'
-    ralt+shift, ralt+capslock:          'G'
+    shift+ralt, capslock+ralt:          'G'
+    shift+capslock+ralt:                'g'
 }
 
 key H {
     label:                              '\u0445'
     base:                               '\u0445'
     shift, capslock:                    '\u0425'
+    shift+capslock:                     '\u0445'
     ralt:                               'h'
-    ralt+shift, ralt+capslock:          'H'
+    shift+ralt, capslock+ralt:          'H'
+    shift+capslock+ralt:                'h'
 }
 
 key J {
     label:                              '\u0439'
     base:                               '\u0439'
     shift, capslock:                    '\u0419'
+    shift+capslock:                     '\u0439'
     ralt:                               'j'
-    ralt+shift, ralt+capslock:          'J'
+    shift+ralt, capslock+ralt:          'J'
+    shift+capslock+ralt:                'j'
 }
 
 key K {
     label:                              '\u043a'
     base:                               '\u043a'
     shift, capslock:                    '\u041a'
+    shift+capslock:                     '\u043a'
     ralt:                               'k'
-    ralt+shift, ralt+capslock:          'K'
+    shift+ralt, capslock+ralt:          'K'
+    shift+capslock+ralt:                'k'
 }
 
 key L {
     label:                              '\u043b'
     base:                               '\u043b'
     shift, capslock:                    '\u041b'
+    shift+capslock:                     '\u043b'
     ralt:                               'l'
-    ralt+shift, ralt+capslock:          'L'
+    shift+ralt, capslock+ralt:          'L'
+    shift+capslock+ralt:                'l'
 }
 
 key SEMICOLON {
     label:                              ';'
     base:                               ';'
-    shift, capslock:                    ':'
+    shift:                              ':'
     ralt:                               ';'
     ralt+shift:                         ':'
 }
@@ -311,7 +350,7 @@
 key APOSTROPHE {
     label:                              '\''
     base:                               '\''
-    shift, capslock:                    '"'
+    shift:                              '"'
     ralt:                               '\''
     ralt+shift:                         '"'
 }
@@ -322,6 +361,7 @@
     label:                              '\u045d'
     base:                               '\u045d'
     shift, capslock:                    '\u040d'
+    shift+capslock:                     '\u045d'
     ralt:                               '\\'
     ralt+shift:                         '|'
 }
@@ -330,62 +370,76 @@
     label:                              '\u0437'
     base:                               '\u0437'
     shift, capslock:                    '\u0417'
+    shift+capslock:                     '\u0437'
     ralt:                               'z'
-    ralt+shift, ralt+capslock:          'Z'
+    shift+ralt, capslock+ralt:          'Z'
+    shift+capslock+ralt:                'z'
 }
 
 key X {
     label:                              '\u0436'
     base:                               '\u0436'
     shift, capslock:                    '\u0416'
+    shift+capslock:                     '\u0436'
     ralt:                               'x'
-    ralt+shift, ralt+capslock:          'X'
+    shift+ralt, capslock+ralt:          'X'
+    shift+capslock+ralt:                'x'
 }
 
 key C {
     label:                              '\u0446'
     base:                               '\u0446'
     shift, capslock:                    '\u0426'
+    shift+capslock:                     '\u0446'
     ralt:                               'c'
-    ralt+shift, ralt+capslock:          'C'
+    shift+ralt, capslock+ralt:          'C'
+    shift+capslock+ralt:                'c'
 }
 
 key V {
     label:                              '\u0432'
     base:                               '\u0432'
     shift, capslock:                    '\u0412'
+    shift+capslock:                     '\u0432'
     ralt:                               'v'
-    ralt+shift, ralt+capslock:          'V'
+    shift+ralt, capslock+ralt:          'V'
+    shift+capslock+ralt:                'v'
 }
 
 key B {
     label:                              '\u0431'
     base:                               '\u0431'
     shift, capslock:                    '\u0411'
+    shift+capslock:                     '\u0431'
     ralt:                               'b'
-    ralt+shift, ralt+capslock:          'B'
+    shift+ralt, capslock+ralt:          'B'
+    shift+capslock+ralt:                'b'
 }
 
 key N {
     label:                              '\u043d'
     base:                               '\u043d'
     shift, capslock:                    '\u041d'
+    shift+capslock:                     '\u043d'
     ralt:                               'n'
-    ralt+shift, ralt+capslock:          'N'
+    shift+ralt, capslock+ralt:          'N'
+    shift+capslock+ralt:                'n'
 }
 
 key M {
     label:                              '\u043c'
     base:                               '\u043c'
     shift, capslock:                    '\u041c'
+    shift+capslock:                     '\u043c'
     ralt:                               'm'
-    ralt+shift, ralt+capslock:          'M'
+    shift+ralt, capslock+ralt:          'M'
+    shift+capslock+ralt:                'm'
 }
 
 key COMMA {
     label:                              ','
     base:                               ','
-    shift, capslock:                    '\u201e'
+    shift:                              '\u201e'
     ralt:                               ','
     ralt+shift:                         '<'
 }
@@ -393,7 +447,7 @@
 key PERIOD {
     label:                              '.'
     base:                               '.'
-    shift, capslock:                    '\u201c'
+    shift:                              '\u201c'
     ralt:                               '.'
     ralt+shift:                         '>'
 }
@@ -401,7 +455,7 @@
 key SLASH {
     label:                              '/'
     base:                               '/'
-    shift, capslock:                    '?'
+    shift:                              '?'
     ralt:                               '/'
     ralt+shift:                         '?'
 }
diff --git a/packages/InputDevices/res/raw/keyboard_layout_croatian_and_slovenian.kcm b/packages/InputDevices/res/raw/keyboard_layout_croatian_and_slovenian.kcm
index 96445a4..1c774cc 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_croatian_and_slovenian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_croatian_and_slovenian.kcm
@@ -122,6 +122,7 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
     ralt:                               '\\'
 }
 
@@ -129,6 +130,7 @@
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
     ralt:                               '|'
 }
 
@@ -136,6 +138,7 @@
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
 }
 
@@ -143,48 +146,56 @@
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Z {
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
     label:                              '\u0160'
     base:                               '\u0161'
     shift, capslock:                    '\u0160'
+    shift+capslock:                     '\u0161'
     ralt:                               '\u00f7'
 }
 
@@ -192,6 +203,7 @@
     label:                              '\u0110'
     base:                               '\u0111'
     shift, capslock:                    '\u0110'
+    shift+capslock:                     '\u0111'
     ralt:                               '\u00d7'
 }
 
@@ -201,24 +213,28 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
     ralt:                               '['
 }
 
@@ -226,6 +242,7 @@
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
     ralt:                               ']'
 }
 
@@ -233,40 +250,48 @@
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
     ralt:                               '\u0268'
-    ralt+shift, ralt+capslock:          '\u0197'
+    shift+ralt, capslock+ralt:          '\u0197'
+    shift+capslock+ralt:                '\u0268'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
     ralt:                               '\u0142'
-    ralt+shift, ralt+capslock:          '\u0141'
+    shift+ralt, capslock+ralt:          '\u0141'
+    shift+capslock+ralt:                '\u0142'
 }
 
 key SEMICOLON {
     label:                              '\u010c'
     base:                               '\u010d'
     shift, capslock:                    '\u010c'
+    shift+capslock:                     '\u010d'
 }
 
 key APOSTROPHE {
     label:                              '\u0106'
     base:                               '\u0107'
     shift, capslock:                    '\u0106'
+    shift+capslock:                     '\u0107'
     ralt:                               '\u00df'
 }
 
@@ -274,6 +299,7 @@
     label:                              '\u017d'
     base:                               '\u017e'
     shift, capslock:                    '\u017d'
+    shift+capslock:                     '\u017e'
     ralt:                               '\u00a4'
 }
 
@@ -289,24 +315,28 @@
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
     ralt:                               '@'
 }
 
@@ -314,6 +344,7 @@
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
     ralt:                               '{'
 }
 
@@ -321,6 +352,7 @@
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
     ralt:                               '}'
 }
 
@@ -328,6 +360,7 @@
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
     ralt:                               '\u00a7'
 }
 
diff --git a/packages/InputDevices/res/raw/keyboard_layout_czech.kcm b/packages/InputDevices/res/raw/keyboard_layout_czech.kcm
index 32750e0..08b012e 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_czech.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_czech.kcm
@@ -131,18 +131,21 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
 }
 
 key W {
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
 }
 
@@ -150,42 +153,49 @@
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Y {
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
@@ -211,54 +221,63 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
@@ -300,24 +319,28 @@
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
     ralt:                               '@'
 }
 
@@ -325,18 +348,21 @@
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
     ralt:                               '\u00b5'
 }
 
diff --git a/packages/InputDevices/res/raw/keyboard_layout_czech_qwerty.kcm b/packages/InputDevices/res/raw/keyboard_layout_czech_qwerty.kcm
index 457d4da..cad262b 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_czech_qwerty.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_czech_qwerty.kcm
@@ -131,18 +131,21 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
 }
 
 key W {
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
 }
 
@@ -150,42 +153,49 @@
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
@@ -211,54 +221,63 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
@@ -300,24 +319,28 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
     ralt:                               '@'
 }
 
@@ -325,18 +348,21 @@
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
     ralt:                               '\u00b5'
 }
 
diff --git a/packages/InputDevices/res/raw/keyboard_layout_danish.kcm b/packages/InputDevices/res/raw/keyboard_layout_danish.kcm
index 9168d12..83ee8c3 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_danish.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_danish.kcm
@@ -115,76 +115,90 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
     ralt:                               '\u00e2'
-    ralt+capslock, shift+ralt:          '\u00c2'
+    shift+ralt, capslock+ralt:          '\u00c2'
+    shift+capslock+ralt:                '\u00e2'
 }
 
 key W {
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
-    ralt+capslock:                      '\u20ac'
 }
 
 key R {
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
     ralt:                               '\u0167'
-    ralt+capslock, shift+ralt:          '\u0166'
+    shift+ralt, capslock+ralt:          '\u0166'
+    shift+capslock+ralt:                '\u0167'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
     ralt:                               '\u00ef'
-    ralt+capslock, shift+ralt:          '\u00cf'
+    shift+ralt, capslock+ralt:          '\u00cf'
+    shift+capslock+ralt:                '\u00ef'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
     ralt:                               '\u00f5'
-    ralt+capslock, shift+ralt:          '\u00d5'
+    shift+ralt, capslock+ralt:          '\u00d5'
+    shift+capslock+ralt:                '\u00f5'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
     label:                              '\u00c5'
     base:                               '\u00e5'
     shift, capslock:                    '\u00c5'
+    shift+capslock:                     '\u00e5'
 }
 
 key RIGHT_BRACKET {
@@ -200,84 +214,104 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
     ralt:                               '\u00e1'
-    ralt+capslock, shift+ralt:          '\u00c1'
+    shift+ralt, capslock+ralt:          '\u00c1'
+    shift+capslock+ralt:                '\u00e1'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
     ralt:                               '\u0161'
-    ralt+capslock, shift+ralt:          '\u0160'
+    shift+ralt, capslock+ralt:          '\u0160'
+    shift+capslock+ralt:                '\u0161'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
     ralt:                               '\u0111'
-    ralt+capslock, shift+ralt:          '\u0110'
+    shift+ralt, capslock+ralt:          '\u0110'
+    shift+capslock+ralt:                '\u0111'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
     ralt:                               '\u01e5'
-    ralt+capslock, shift+ralt:          '\u01e4'
+    shift+ralt, capslock+ralt:          '\u01e4'
+    shift+capslock+ralt:                '\u01e5'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
     ralt:                               '\u01e7'
-    ralt+capslock, shift+ralt:          '\u01e6'
+    shift+ralt, capslock+ralt:          '\u01e6'
+    shift+capslock+ralt:                '\u01e7'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
     ralt:                               '\u021f'
-    ralt+capslock, shift+ralt:          '\u021e'
+    shift+ralt, capslock+ralt:          '\u021e'
+    shift+capslock+ralt:                '\u021f'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
     ralt:                               '\u01e9'
-    ralt+capslock, shift+ralt:          '\u01e8'
+    shift+ralt, capslock+ralt:          '\u01e8'
+    shift+capslock+ralt:                '\u01e9'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
     label:                              '\u00c6'
     base:                               '\u00e6'
     shift, capslock:                    '\u00c6'
+    shift+capslock:                     '\u00e6'
     ralt:                               '\u00e4'
-    ralt+capslock, shift+ralt:          '\u00c4'
+    shift+ralt, capslock+ralt:          '\u00c4'
+    shift+capslock+ralt:                '\u00e4'
 }
 
 key APOSTROPHE {
     label:                              '\u00d8'
     base:                               '\u00f8'
     shift, capslock:                    '\u00d8'
+    shift+capslock:                     '\u00f8'
     ralt:                               '\u00f6'
-    ralt+capslock, shift+ralt:          '\u00d6'
+    shift+ralt, capslock+ralt:          '\u00d6'
+    shift+capslock+ralt:                '\u00f6'
 }
 
 key BACKSLASH {
@@ -299,53 +333,65 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
     ralt:                               '\u017e'
-    ralt+capslock, shift+ralt:          '\u017d'
+    shift+ralt, capslock+ralt:          '\u017d'
+    shift+capslock+ralt:                '\u017e'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
     ralt:                               '\u010d'
-    ralt+capslock, shift+ralt:          '\u010c'
+    shift+ralt, capslock+ralt:          '\u010c'
+    shift+capslock+ralt:                '\u010d'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
     ralt:                               '\u01ef'
-    ralt+capslock, shift+ralt:          '\u01ee'
+    shift+ralt, capslock+ralt:          '\u01ee'
+    shift+capslock+ralt:                '\u01ef'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
     ralt:                               '\u0292'
-    ralt+capslock, shift+ralt:          '\u01b7'
+    shift+ralt, capslock+ralt:          '\u01b7'
+    shift+capslock+ralt:                '\u0292'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
     ralt:                               '\u014b'
-    ralt+capslock, shift+ralt:          '\u014a'
+    shift+ralt, capslock+ralt:          '\u014a'
+    shift+capslock+ralt:                '\u014b'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
-    ralt, ralt+capslock:                '\u00b5'
+    shift+capslock:                     'm'
+    ralt:                               '\u00b5'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_english_uk.kcm b/packages/InputDevices/res/raw/keyboard_layout_english_uk.kcm
index 6d9c2e5..93a5082 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_english_uk.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_english_uk.kcm
@@ -108,68 +108,82 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
 }
 
 key W {
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u00e9'
-    shift+ralt:                         '\u00c9'
+    shift+ralt, capslock+ralt:          '\u00c9'
+    shift+capslock+ralt:                '\u00e9'
 }
 
 key R {
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
     ralt:                               '\u00fa'
-    shift+ralt:                         '\u00da'
+    shift+ralt, capslock+ralt:          '\u00da'
+    shift+capslock+ralt:                '\u00fa'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
     ralt:                               '\u00ed'
-    shift+ralt:                         '\u00cd'
+    shift+ralt, capslock+ralt:          '\u00cd'
+    shift+capslock+ralt:                '\u00ed'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
     ralt:                               '\u00f3'
-    shift+ralt:                         '\u00d3'
+    shift+ralt, capslock+ralt:          '\u00d3'
+    shift+capslock+ralt:                '\u00f3'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
@@ -190,56 +204,66 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
     ralt:                               '\u00e1'
-    shift+ralt:                         '\u00c1'
+    shift+ralt, capslock+ralt:          '\u00c1'
+    shift+capslock+ralt:                '\u00e1'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
@@ -274,42 +298,49 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_english_us.kcm b/packages/InputDevices/res/raw/keyboard_layout_english_us.kcm
index 050b149..da76448 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_english_us.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_english_us.kcm
@@ -106,60 +106,70 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
 }
 
 key W {
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
 }
 
 key R {
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
@@ -186,54 +196,63 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
@@ -254,42 +273,49 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_english_us_colemak.kcm b/packages/InputDevices/res/raw/keyboard_layout_english_us_colemak.kcm
index 72e6d04..e52ccf0 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_english_us_colemak.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_english_us_colemak.kcm
@@ -125,60 +125,70 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
 }
 
 key W {
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key SEMICOLON {
     label:                              ';'
     base:                               ';'
     shift, capslock:                    ':'
+    shift+capslock:                     ':'
 }
 
 key LEFT_BRACKET {
@@ -205,54 +215,63 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key R {
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key O {
@@ -273,42 +292,49 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_english_us_dvorak.kcm b/packages/InputDevices/res/raw/keyboard_layout_english_us_dvorak.kcm
index df6a3fd..6ff627b 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_english_us_dvorak.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_english_us_dvorak.kcm
@@ -160,42 +160,49 @@
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key R {
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SLASH {
@@ -222,60 +229,70 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key MINUS {
@@ -296,52 +313,61 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key W {
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key Z {
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
diff --git a/packages/InputDevices/res/raw/keyboard_layout_english_us_intl.kcm b/packages/InputDevices/res/raw/keyboard_layout_english_us_intl.kcm
index aa31493..dff17b3 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_english_us_intl.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_english_us_intl.kcm
@@ -121,30 +121,37 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
     ralt:                               '\u00e4'
     shift+ralt, capslock+ralt:          '\u00c4'
+    shift+capslock+ralt:                '\u00e4'
 }
 
 key W {
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
     ralt:                               '\u00e5'
     shift+ralt, capslock+ralt:          '\u00c5'
+    shift+capslock+ralt:                '\u00e5'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u00e9'
     shift+ralt, capslock+ralt:          '\u00c9'
+    shift+capslock+ralt:                '\u00e9'
 }
 
 key R {
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
     ralt:                               '\u00ae'
 }
 
@@ -152,48 +159,60 @@
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
     ralt:                               '\u00fe'
     shift+ralt, capslock+ralt:          '\u00de'
+    shift+capslock+ralt:                '\u00fe'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
     ralt:                               '\u00fc'
     shift+ralt, capslock+ralt:          '\u00dc'
+    shift+capslock+ralt:                '\u00fc'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
     ralt:                               '\u00fa'
     shift+ralt, capslock+ralt:          '\u00da'
+    shift+capslock+ralt:                '\u00fa'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
     ralt:                               '\u00ed'
     shift+ralt, capslock+ralt:          '\u00cd'
+    shift+capslock+ralt:                '\u00ed'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
     ralt:                               '\u00f3'
     shift+ralt, capslock+ralt:          '\u00d3'
+    shift+capslock+ralt:                '\u00f3'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
     ralt:                               '\u00f6'
     shift+ralt, capslock+ralt:          '\u00d6'
+    shift+capslock+ralt:                '\u00f6'
 }
 
 key LEFT_BRACKET {
@@ -224,14 +243,17 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
     ralt:                               '\u00e1'
-    shift+ralt, ralt+capslock:          '\u00c1'
+    shift+ralt, capslock+ralt:          '\u00c1'
+    shift+capslock+ralt:                '\u00e1'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
     ralt:                               '\u00df'
     shift+ralt:                         '\u00a7'
 }
@@ -240,46 +262,55 @@
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
     ralt:                               '\u00f0'
     shift+ralt, capslock+ralt:          '\u00d0'
+    shift+capslock+ralt:                '\u00f0'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
     ralt:                               '\u00f8'
     shift+ralt, capslock+ralt:          '\u00d8'
+    shift+capslock+ralt:                '\u00f8'
 }
 
 key SEMICOLON {
@@ -312,20 +343,24 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
     ralt:                               '\u00e6'
     shift+ralt, capslock+ralt:          '\u00c6'
+    shift+capslock+ralt:                '\u00e6'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
     ralt:                               '\u00a9'
     shift+ralt:                         '\u00a2'
 }
@@ -334,26 +369,31 @@
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
     ralt:                               '\u00f1'
     shift+ralt, capslock+ralt:          '\u00d1'
+    shift+capslock+ralt:                '\u00f1'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
     ralt:                               '\u00b5'
 }
 
@@ -363,6 +403,7 @@
     shift:                              '<'
     ralt:                               '\u00e7'
     shift+ralt, capslock+ralt:          '\u00c7'
+    shift+capslock+ralt:                '\u00e7'
 }
 
 key PERIOD {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_english_us_workman.kcm b/packages/InputDevices/res/raw/keyboard_layout_english_us_workman.kcm
index fe82c8d..713afba 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_english_us_workman.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_english_us_workman.kcm
@@ -129,60 +129,70 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key R {
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key W {
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key SEMICOLON {
     label:                              ';'
     base:                               ';'
     shift, capslock:                    ':'
+    shift+capslock:                     ':'
 }
 
 key LEFT_BRACKET {
@@ -209,48 +219,56 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
 }
 
 key O {
@@ -263,6 +281,7 @@
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key APOSTROPHE {
@@ -277,42 +296,49 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_estonian.kcm b/packages/InputDevices/res/raw/keyboard_layout_estonian.kcm
index ef545b8..27a03da 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_estonian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_estonian.kcm
@@ -116,18 +116,21 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
 }
 
 key W {
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
 }
 
@@ -135,54 +138,63 @@
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
     label:                              '\u00dc'
     base:                               '\u00fc'
     shift, capslock:                    '\u00dc'
+    shift+capslock:                     '\u00fc'
 }
 
 key RIGHT_BRACKET {
     label:                              '\u00d5'
     base:                               '\u00f5'
     shift, capslock:                    '\u00d5'
+    shift+capslock:                     '\u00f5'
     ralt:                               '\u00a7'
 }
 
@@ -192,68 +204,80 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
     ralt:                               '\u0161'
-    ralt+shift, ralt+capslock:          '\u0160'
+    shift+ralt, capslock+ralt:          '\u0160'
+    shift+capslock+ralt:                '\u0161'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
     label:                              '\u00d6'
     base:                               '\u00f6'
     shift, capslock:                    '\u00d6'
+    shift+capslock:                     '\u00f6'
 }
 
 key APOSTROPHE {
     label:                              '\u00c4'
     base:                               '\u00e4'
     shift, capslock:                    '\u00c4'
+    shift+capslock:                     '\u00e4'
     ralt:                               '\u0302'
 }
 
@@ -277,44 +301,52 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
     ralt:                               '\u017e'
-    ralt+shift, ralt+capslock:          '\u017d'
+    shift+ralt, capslock+ralt:          '\u017d'
+    shift+capslock+ralt:                '\u017e'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_finnish.kcm b/packages/InputDevices/res/raw/keyboard_layout_finnish.kcm
index b4deed4..79096ad 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_finnish.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_finnish.kcm
@@ -115,76 +115,90 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
     ralt:                               '\u00e2'
-    ralt+capslock, shift+ralt:          '\u00c2'
+    shift+ralt, capslock+ralt:          '\u00c2'
+    shift+capslock+ralt:                '\u00e2'
 }
 
 key W {
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
-    ralt+capslock:                      '\u20ac'
 }
 
 key R {
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
     ralt:                               '\u0167'
-    ralt+capslock, shift+ralt:          '\u0166'
+    shift+ralt, capslock+ralt:          '\u0166'
+    shift+capslock+ralt:                '\u0167'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
     ralt:                               '\u00ef'
-    ralt+capslock, shift+ralt:          '\u00cf'
+    shift+ralt, capslock+ralt:          '\u00cf'
+    shift+capslock+ralt:                '\u00ef'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
     ralt:                               '\u00f5'
-    ralt+capslock, shift+ralt:          '\u00d5'
+    shift+ralt, capslock+ralt:          '\u00d5'
+    shift+capslock+ralt:                '\u00f5'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
     label:                              '\u00c5'
     base:                               '\u00e5'
     shift, capslock:                    '\u00c5'
+    shift+capslock:                     '\u00e5'
 }
 
 key RIGHT_BRACKET {
@@ -200,84 +214,104 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
     ralt:                               '\u00e1'
-    ralt+capslock, shift+ralt:          '\u00c1'
+    shift+ralt, capslock+ralt:          '\u00c1'
+    shift+capslock+ralt:                '\u00e1'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
     ralt:                               '\u0161'
-    ralt+capslock, shift+ralt:          '\u0160'
+    shift+ralt, capslock+ralt:          '\u0160'
+    shift+capslock+ralt:                '\u0161'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
     ralt:                               '\u0111'
-    ralt+capslock, shift+ralt:          '\u0110'
+    shift+ralt, capslock+ralt:          '\u0110'
+    shift+capslock+ralt:                '\u0111'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
     ralt:                               '\u01e5'
-    ralt+capslock, shift+ralt:          '\u01e4'
+    shift+ralt, capslock+ralt:          '\u01e4'
+    shift+capslock+ralt:                '\u01e5'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
     ralt:                               '\u01e7'
-    ralt+capslock, shift+ralt:          '\u01e6'
+    shift+ralt, capslock+ralt:          '\u01e6'
+    shift+capslock+ralt:                '\u01e7'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
     ralt:                               '\u021f'
-    ralt+capslock, shift+ralt:          '\u021e'
+    shift+ralt, capslock+ralt:          '\u021e'
+    shift+capslock+ralt:                '\u021f'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
     ralt:                               '\u01e9'
-    ralt+capslock, shift+ralt:          '\u01e8'
+    shift+ralt, capslock+ralt:          '\u01e8'
+    shift+capslock+ralt:                '\u01e9'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
     label:                              '\u00d6'
     base:                               '\u00f6'
     shift, capslock:                    '\u00d6'
+    shift+capslock:                     '\u00f6'
     ralt:                               '\u00f8'
-    ralt+capslock, shift+ralt:          '\u00d8'
+    shift+ralt, capslock+ralt:          '\u00d8'
+    shift+capslock+ralt:                '\u00f8'
 }
 
 key APOSTROPHE {
     label:                              '\u00c4'
     base:                               '\u00e4'
     shift, capslock:                    '\u00c4'
+    shift+capslock:                     '\u00e4'
     ralt:                               '\u00e6'
-    ralt+capslock, shift+ralt:          '\u00c6'
+    shift+ralt, capslock+ralt:          '\u00c6'
+    shift+capslock+ralt:                '\u00e6'
 }
 
 key BACKSLASH {
@@ -299,53 +333,65 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
     ralt:                               '\u017e'
-    ralt+capslock, shift+ralt:          '\u017d'
+    shift+ralt, capslock+ralt:          '\u017d'
+    shift+capslock+ralt:                '\u017e'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
     ralt:                               '\u010d'
-    ralt+capslock, shift+ralt:          '\u010c'
+    shift+ralt, capslock+ralt:          '\u010c'
+    shift+capslock+ralt:                '\u010d'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
     ralt:                               '\u01ef'
-    ralt+capslock, shift+ralt:          '\u01ee'
+    shift+ralt, capslock+ralt:          '\u01ee'
+    shift+capslock+ralt:                '\u01ef'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
     ralt:                               '\u0292'
-    ralt+capslock, shift+ralt:          '\u01b7'
+    shift+ralt, capslock+ralt:          '\u01b7'
+    shift+capslock+ralt:                '\u0292'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
     ralt:                               '\u014b'
-    ralt+capslock, shift+ralt:          '\u014a'
+    shift+ralt, capslock+ralt:          '\u014a'
+    shift+capslock+ralt:                '\u014b'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
-    ralt, ralt+capslock:                '\u00b5'
+    shift+capslock:                     'm'
+    ralt:                               '\u00b5'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_french.kcm b/packages/InputDevices/res/raw/keyboard_layout_french.kcm
index 89e83da..4906304 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_french.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_french.kcm
@@ -123,18 +123,21 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key Z {
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
 }
 
@@ -142,42 +145,49 @@
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
@@ -199,60 +209,70 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key APOSTROPHE {
@@ -279,36 +299,42 @@
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_french_ca.kcm b/packages/InputDevices/res/raw/keyboard_layout_french_ca.kcm
index 55ddd60..03b5c19 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_french_ca.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_french_ca.kcm
@@ -119,54 +119,63 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
 }
 
 key W {
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
 }
 
 key R {
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
     ralt:                               '\u00a7'
 }
 
@@ -174,6 +183,7 @@
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
     ralt:                               '\u00b6'
 }
 
@@ -196,54 +206,63 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
@@ -279,42 +298,49 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
     ralt:                               '\u00b5'
 }
 
@@ -335,5 +361,6 @@
     label:                              '\u00c9'
     base:                               '\u00e9'
     shift, capslock:                    '\u00c9'
+    shift+capslock:                     '\u00e9'
     ralt:                               '\u0301'
 }
diff --git a/packages/InputDevices/res/raw/keyboard_layout_georgian.kcm b/packages/InputDevices/res/raw/keyboard_layout_georgian.kcm
index 35b66a3..a8f229f 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_georgian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_georgian.kcm
@@ -28,6 +28,7 @@
     label:                              '\u201e'
     base:                               '\u201e'
     shift, capslock:                    '\u201c'
+    shift+capslock:                     '\u201e'
     ralt:                               '`'
     ralt+shift:                         '~'
 }
@@ -128,79 +129,92 @@
     label:                              '\u10e5'
     base:                               '\u10e5'
     ralt:                               'q'
-    ralt+shift, ralt+capslock:          'Q'
+    shift+ralt, capslock+ralt:          'Q'
+    shift+capslock+ralt:                'q'
 }
 
 key W {
     label:                              '\u10ec'
     base:                               '\u10ec'
     shift, capslock:                    '\u10ed'
+    shift+capslock:                     '\u10ec'
     ralt:                               'w'
-    ralt+shift, ralt+capslock:          'W'
+    shift+ralt, capslock+ralt:          'W'
+    shift+capslock+ralt:                'w'
 }
 
 key E {
     label:                              '\u10d4'
     base:                               '\u10d4'
     ralt:                               'e'
-    ralt+shift, ralt+capslock:          'E'
+    shift+ralt, capslock+ralt:          'E'
+    shift+capslock+ralt:                'e'
 }
 
 key R {
     label:                              '\u10e0'
     base:                               '\u10e0'
     shift, capslock:                    '\u10e6'
+    shift+capslock:                     '\u10e0'
     ralt:                               'r'
-    ralt+shift, ralt+capslock:          'R'
+    shift+ralt, capslock+ralt:          'R'
+    shift+capslock+ralt:                'r'
 }
 
 key T {
     label:                              '\u10e2'
     base:                               '\u10e2'
     shift, capslock:                    '\u10d7'
+    shift+capslock:                     '\u10e2'
     ralt:                               't'
-    ralt+shift, ralt+capslock:          'T'
+    shift+ralt, capslock+ralt:          'T'
+    shift+capslock+ralt:                't'
 }
 
 key Y {
     label:                              '\u10e7'
     base:                               '\u10e7'
     ralt:                               'y'
-    ralt+shift, ralt+capslock:          'Y'
+    shift+ralt, capslock+ralt:          'Y'
+    shift+capslock+ralt:                'y'
 }
 
 key U {
     label:                              '\u10e3'
     base:                               '\u10e3'
     ralt:                               'u'
-    ralt+shift, ralt+capslock:          'U'
+    shift+ralt, capslock+ralt:          'U'
+    shift+capslock+ralt:                'u'
 }
 
 key I {
     label:                              '\u10d8'
     base:                               '\u10d8'
     ralt:                               'i'
-    ralt+shift, ralt+capslock:          'I'
+    shift+ralt, capslock+ralt:          'I'
+    shift+capslock+ralt:                'i'
 }
 
 key O {
     label:                              '\u10dd'
     base:                               '\u10dd'
     ralt:                               'o'
-    ralt+shift, ralt+capslock:          'O'
+    shift+ralt, capslock+ralt:          'O'
+    shift+capslock+ralt:                'o'
 }
 
 key P {
     label:                              '\u10de'
     base:                               '\u10de'
     ralt:                               'p'
-    ralt+shift, ralt+capslock:          'P'
+    shift+ralt, capslock+ralt:          'P'
+    shift+capslock+ralt:                'p'
 }
 
 key LEFT_BRACKET {
     label:                              '['
     base:                               '['
-    shift, capslock:                    '{'
+    shift:                              '{'
     ralt:                               '['
     ralt+shift:                         '{'
 }
@@ -208,7 +222,7 @@
 key RIGHT_BRACKET {
     label:                              ']'
     base:                               ']'
-    shift, capslock:                    '}'
+    shift:                              '}'
     ralt:                               ']'
     ralt+shift:                         '}'
 }
@@ -227,72 +241,84 @@
     label:                              '\u10d0'
     base:                               '\u10d0'
     ralt:                               'a'
-    ralt+shift, ralt+capslock:          'A'
+    shift+ralt, capslock+ralt:          'A'
+    shift+capslock+ralt:                'a'
 }
 
 key S {
     label:                              '\u10e1'
     base:                               '\u10e1'
     shift, capslock:                    '\u10e8'
+    shift+capslock:                     '\u10e1'
     ralt:                               's'
-    ralt+shift, ralt+capslock:          'S'
+    shift+ralt, capslock+ralt:          'S'
+    shift+capslock+ralt:                's'
 }
 
 key D {
     label:                              '\u10d3'
     base:                               '\u10d3'
     ralt:                               'd'
-    ralt+shift, ralt+capslock:          'D'
+    shift+ralt, capslock+ralt:          'D'
+    shift+capslock+ralt:                'd'
 }
 
 key F {
     label:                              '\u10e4'
     base:                               '\u10e4'
     ralt:                               'f'
-    ralt+shift, ralt+capslock:          'F'
+    shift+ralt, capslock+ralt:          'F'
+    shift+capslock+ralt:                'f'
 }
 
 key G {
     label:                              '\u10d2'
     base:                               '\u10d2'
     ralt:                               'g'
-    ralt+shift, ralt+capslock:          'G'
+    shift+ralt, capslock+ralt:          'G'
+    shift+capslock+ralt:                'g'
 }
 
 key H {
     label:                              '\u10f0'
     base:                               '\u10f0'
     ralt:                               'h'
-    ralt+shift, ralt+capslock:          'H'
+    shift+ralt, capslock+ralt:          'H'
+    shift+capslock+ralt:                'h'
 }
 
 key J {
     label:                              '\u10ef'
     base:                               '\u10ef'
     shift, capslock:                    '\u10df'
+    shift+capslock:                     '\u10ef'
     ralt:                               'j'
-    ralt+shift, ralt+capslock:          'J'
+    shift+ralt, capslock+ralt:          'J'
+    shift+capslock+ralt:                'j'
 }
 
 key K {
     label:                              '\u10d9'
     base:                               '\u10d9'
     ralt:                               'k'
-    ralt+shift, ralt+capslock:          'K'
+    shift+ralt, capslock+ralt:          'K'
+    shift+capslock+ralt:                'k'
 }
 
 key L {
     label:                              '\u10da'
     base:                               '\u10da'
     shift, capslock:                    '\u20be'
+    shift+capslock:                     '\u10da'
     ralt:                               'l'
-    ralt+shift, ralt+capslock:          'L'
+    shift+ralt, capslock+ralt:          'L'
+    shift+capslock+ralt:                'l'
 }
 
 key SEMICOLON {
     label:                              ';'
     base:                               ';'
-    shift, capslock:                    ':'
+    shift:                              ':'
     ralt:                               ';'
     ralt+shift:                         ':'
 }
@@ -300,7 +326,7 @@
 key APOSTROPHE {
     label:                              '\''
     base:                               '\''
-    shift, capslock:                    '"'
+    shift:                              '"'
     ralt:                               '\''
     ralt+shift:                         '"'
 }
@@ -311,57 +337,66 @@
     label:                              '\u10d6'
     base:                               '\u10d6'
     shift, capslock:                    '\u10eb'
+    shift+capslock:                     '\u10d6'
     ralt:                               'z'
-    ralt+shift, ralt+capslock:          'Z'
+    shift+ralt, capslock+ralt:          'Z'
+    shift+capslock+ralt:                'z'
 }
 
 key X {
     label:                              '\u10ee'
     base:                               '\u10ee'
     ralt:                               'x'
-    ralt+shift, ralt+capslock:          'X'
+    shift+ralt, capslock+ralt:          'X'
+    shift+capslock+ralt:                'x'
 }
 
 key C {
     label:                              '\u10ea'
     base:                               '\u10ea'
     shift, capslock:                    '\u10e9'
+    shift+capslock:                     '\u10ea'
     ralt:                               'c'
-    ralt+shift, ralt+capslock:          'C'
+    shift+ralt, capslock+ralt:          'C'
+    shift+capslock+ralt:                'c'
 }
 
 key V {
     label:                              '\u10d5'
     base:                               '\u10d5'
     ralt:                               'v'
-    ralt+shift, ralt+capslock:          'V'
+    shift+ralt, capslock+ralt:          'V'
+    shift+capslock+ralt:                'v'
 }
 
 key B {
     label:                              '\u10d1'
     base:                               '\u10d1'
     ralt:                               'b'
-    ralt+shift, ralt+capslock:          'B'
+    shift+ralt, capslock+ralt:          'B'
+    shift+capslock+ralt:                'b'
 }
 
 key N {
     label:                              '\u10dc'
     base:                               '\u10dc'
     ralt:                               'n'
-    ralt+shift, ralt+capslock:          'N'
+    shift+ralt, capslock+ralt:          'N'
+    shift+capslock+ralt:                'n'
 }
 
 key M {
     label:                              '\u10db'
     base:                               '\u10db'
     ralt:                               'm'
-    ralt+shift, ralt+capslock:          'M'
+    shift+ralt, capslock+ralt:          'M'
+    shift+capslock+ralt:                'm'
 }
 
 key COMMA {
     label:                              ','
     base:                               ','
-    shift, capslock:                    '<'
+    shift:                              '<'
     ralt:                               ','
     ralt+shift:                         '<'
 }
@@ -369,7 +404,7 @@
 key PERIOD {
     label:                              '.'
     base:                               '.'
-    shift, capslock:                    '>'
+    shift:                              '>'
     ralt:                               '.'
     ralt+shift:                         '>'
 }
diff --git a/packages/InputDevices/res/raw/keyboard_layout_german.kcm b/packages/InputDevices/res/raw/keyboard_layout_german.kcm
index d9caa32..23ccc9a 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_german.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_german.kcm
@@ -18,7 +18,7 @@
 
 type OVERLAY
 
-map key 12 SLASH            # § ? \
+map key 12 SLASH            # § ? \
 map key 21 Z
 map key 44 Y
 map key 53 MINUS            # - _
@@ -117,6 +117,7 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
     ralt:                               '@'
 }
 
@@ -124,12 +125,14 @@
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
 }
 
@@ -137,48 +140,56 @@
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Z {
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
     label:                              '\u00dc'
     base:                               '\u00fc'
     shift, capslock:                    '\u00dc'
+    shift+capslock:                     '\u00fc'
 }
 
 key RIGHT_BRACKET {
@@ -194,66 +205,77 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
     label:                              '\u00d6'
     base:                               '\u00f6'
     shift, capslock:                    '\u00d6'
+    shift+capslock:                     '\u00f6'
 }
 
 key APOSTROPHE {
     label:                              '\u00c4'
     base:                               '\u00e4'
     shift, capslock:                    '\u00c4'
+    shift+capslock:                     '\u00e4'
 }
 
 key BACKSLASH {
@@ -275,42 +297,49 @@
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
     ralt:                               '\u00b5'
 }
 
diff --git a/packages/InputDevices/res/raw/keyboard_layout_greek.kcm b/packages/InputDevices/res/raw/keyboard_layout_greek.kcm
index a7684e1..6eff114 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_greek.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_greek.kcm
@@ -24,88 +24,88 @@
 
 key GRAVE {
     label:                              '`'
-    base, capslock:                     '`'
+    base:                               '`'
     shift:                              '~'
 }
 
 key 1 {
     label:                              '1'
-    base, capslock:                     '1'
+    base:                               '1'
     shift:                              '!'
 }
 
 key 2 {
     label:                              '2'
-    base, capslock:                     '2'
+    base:                               '2'
     shift:                              '@'
     ralt:                               '\u00b2'
 }
 
 key 3 {
     label:                              '3'
-    base, capslock:                     '3'
+    base:                               '3'
     shift:                              '#'
     ralt:                               '\u00b3'
 }
 
 key 4 {
     label:                              '4'
-    base, capslock:                     '4'
+    base:                               '4'
     shift:                              '$'
     ralt:                               '\u00a3'
 }
 
 key 5 {
     label:                              '5'
-    base, capslock:                     '5'
+    base:                               '5'
     shift:                              '%'
     ralt:                               '\u00a7'
 }
 
 key 6 {
     label:                              '6'
-    base, capslock:                     '6'
+    base:                               '6'
     shift:                              '^'
     ralt:                               '\u00b6'
 }
 
 key 7 {
     label:                              '7'
-    base, capslock:                     '7'
+    base:                               '7'
     shift:                              '&'
 }
 
 key 8 {
     label:                              '8'
-    base, capslock:                     '8'
+    base:                               '8'
     shift:                              '*'
     ralt:                               '\u00a4'
 }
 
 key 9 {
     label:                              '9'
-    base, capslock:                     '9'
+    base:                               '9'
     shift:                              '('
     ralt:                               '\u00a6'
 }
 
 key 0 {
     label:                              '0'
-    base, capslock:                     '0'
+    base:                               '0'
     shift:                              ')'
     ralt:                               '\u00b0'
 }
 
 key MINUS {
     label:                              '-'
-    base, capslock:                     '-'
+    base:                               '-'
     shift:                              '_'
     ralt:                               '\u00b1'
 }
 
 key EQUALS {
     label:                              '='
-    base, capslock:                     '='
+    base:                               '='
     shift:                              '+'
     ralt:                               '\u00bd'
 }
@@ -114,13 +114,13 @@
 
 key Q {
     label:                              'Q'
-    base, capslock:                     ';'
+    base:                               ';'
     shift:                              ':'
 }
 
 key W {
     label:                              'W'
-    base, capslock:                     '\u03c2'
+    base:                               '\u03c2'
     shift:                              '\u0385'
 }
 
@@ -128,6 +128,7 @@
     label:                              'E'
     base:                               '\u03b5'
     shift, capslock:                    '\u0395'
+    shift+capslock:                     '\u03b5'
     ralt:                               '\u20ac'
 }
 
@@ -135,6 +136,7 @@
     label:                              'R'
     base:                               '\u03c1'
     shift, capslock:                    '\u03a1'
+    shift+capslock:                     '\u03c1'
     ralt:                               '\u00ae'
 }
 
@@ -142,12 +144,14 @@
     label:                              'T'
     base:                               '\u03c4'
     shift, capslock:                    '\u03a4'
+    shift+capslock:                     '\u03c4'
 }
 
 key Y {
     label:                              'Y'
     base:                               '\u03c5'
     shift, capslock:                    '\u03a5'
+    shift+capslock:                     '\u03c5'
     ralt:                               '\u00a5'
 }
 
@@ -155,36 +159,40 @@
     label:                              'U'
     base:                               '\u03b8'
     shift, capslock:                    '\u0398'
+    shift+capslock:                     '\u03b8'
 }
 
 key I {
     label:                              'I'
     base:                               '\u03b9'
     shift, capslock:                    '\u0399'
+    shift+capslock:                     '\u03b9'
 }
 
 key O {
     label:                              'O'
     base:                               '\u03bf'
     shift, capslock:                    '\u039f'
+    shift+capslock:                     '\u03bf'
 }
 
 key P {
     label:                              'P'
     base:                               '\u03c0'
     shift, capslock:                    '\u03a0'
+    shift+capslock:                     '\u03c0'
 }
 
 key LEFT_BRACKET {
     label:                              '['
-    base, capslock:                     '['
+    base:                               '['
     shift:                              '{'
     ralt:                               '\u00ab'
 }
 
 key RIGHT_BRACKET {
     label:                              ']'
-    base, capslock:                     ']'
+    base:                               ']'
     shift:                              '}'
     ralt:                               '\u00bb'
 }
@@ -195,59 +203,68 @@
     label:                              'A'
     base:                               '\u03b1'
     shift, capslock:                    '\u0391'
+    shift+capslock:                     '\u03b1'
 }
 
 key S {
     label:                              'S'
     base:                               '\u03c3'
     shift, capslock:                    '\u03a3'
+    shift+capslock:                     '\u03c3'
 }
 
 key D {
     label:                              'D'
     base:                               '\u03b4'
     shift, capslock:                    '\u0394'
+    shift+capslock:                     '\u03b4'
 }
 
 key F {
     label:                              'F'
     base:                               '\u03c6'
     shift, capslock:                    '\u03a6'
+    shift+capslock:                     '\u03c6'
 }
 
 key G {
     label:                              'G'
     base:                               '\u03b3'
     shift, capslock:                    '\u0393'
+    shift+capslock:                     '\u03b3'
 }
 
 key H {
     label:                              'H'
     base:                               '\u03b7'
     shift, capslock:                    '\u0397'
+    shift+capslock:                     '\u03b7'
 }
 
 key J {
     label:                              'J'
     base:                               '\u03be'
     shift, capslock:                    '\u039e'
+    shift+capslock:                     '\u03be'
 }
 
 key K {
     label:                              'K'
     base:                               '\u03ba'
     shift, capslock:                    '\u039a'
+    shift+capslock:                     '\u03ba'
 }
 
 key L {
     label:                              'L'
     base:                               '\u03bb'
     shift, capslock:                    '\u039b'
+    shift+capslock:                     '\u03bb'
 }
 
 key SEMICOLON {
     label:                              ';'
-    base, capslock:                     '\u0301'
+    base:                               '\u0301'
 #should be \u0384 (greek tonos)
     shift:                              '\u0308'
     ralt:                               '\u0385'
@@ -255,13 +272,13 @@
 
 key APOSTROPHE {
     label:                              '\''
-    base, capslock:                     '\''
+    base:                               '\''
     shift:                              '"'
 }
 
 key BACKSLASH {
     label:                              '\\'
-    base, capslock:                     '\\'
+    base:                               '\\'
     shift:                              '|'
     ralt:                               '\u00ac'
 }
@@ -270,7 +287,7 @@
 
 key PLUS {
     label:                              '<'
-    base, capslock:                     '<'
+    base:                               '<'
     shift:                              '>'
     ralt:                               '\\'
     shift+ralt:                         '|'
@@ -280,18 +297,21 @@
     label:                              'Z'
     base:                               '\u03b6'
     shift, capslock:                    '\u0396'
+    shift+capslock:                     '\u03b6'
 }
 
 key X {
     label:                              'X'
     base:                               '\u03c7'
     shift, capslock:                    '\u03a7'
+    shift+capslock:                     '\u03c7'
 }
 
 key C {
     label:                              'C'
     base:                               '\u03c8'
     shift, capslock:                    '\u03a8'
+    shift+capslock:                     '\u03c8'
     ralt:                               '\u00a9'
 }
 
@@ -299,40 +319,44 @@
     label:                              'V'
     base:                               '\u03c9'
     shift, capslock:                    '\u03a9'
+    shift+capslock:                     '\u03c9'
 }
 
 key B {
     label:                              'B'
     base:                               '\u03b2'
     shift, capslock:                    '\u0392'
+    shift+capslock:                     '\u03b2'
 }
 
 key N {
     label:                              'N'
     base:                               '\u03bd'
     shift, capslock:                    '\u039d'
+    shift+capslock:                     '\u03bd'
 }
 
 key M {
     label:                              'M'
     base:                               '\u03bc'
     shift, capslock:                    '\u039c'
+    shift+capslock:                     '\u03bc'
 }
 
 key COMMA {
     label:                              ','
-    base, capslock:                     ','
+    base:                               ','
     shift:                              '<'
 }
 
 key PERIOD {
     label:                              '.'
-    base, capslock:                     '.'
+    base:                               '.'
     shift:                              '>'
 }
 
 key SLASH {
     label:                              '/'
-    base, capslock:                     '/'
+    base:                               '/'
     shift:                              '?'
 }
diff --git a/packages/InputDevices/res/raw/keyboard_layout_hebrew.kcm b/packages/InputDevices/res/raw/keyboard_layout_hebrew.kcm
index 283cb4e..11ade42 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_hebrew.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_hebrew.kcm
@@ -121,18 +121,21 @@
     label:                              'Q'
     base:                               '/'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
 }
 
 key W {
     label:                              'W'
     base:                               '\u0027'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               '\u05e7'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
 }
 
@@ -140,24 +143,28 @@
     label:                              'R'
     base:                               '\u05e8'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               '\u05d0'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Y {
     label:                              'Y'
     base:                               '\u05d8'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               '\u05d5'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
     ralt:                               '\u05f0'
 }
 
@@ -165,29 +172,32 @@
     label:                              'I'
     base:                               '\u05df'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key O {
     label:                              'O'
     base:                               '\u05dd'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key P {
     label:                              'P'
     base:                               '\u05e4'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
     label:                              ']'
-    base, capslock:                     ']'
+    base:                               ']'
     shift:                              '}'
 }
 
 key RIGHT_BRACKET {
     label:                              '['
-    base, capslock:                     '['
+    base:                               '['
     shift:                              '{'
 }
 
@@ -197,36 +207,42 @@
     label:                              'A'
     base:                               '\u05e9'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key S {
     label:                              'S'
     base:                               '\u05d3'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key D {
     label:                              'D'
     base:                               '\u05d2'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               '\u05db'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               '\u05e2'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               '\u05d9'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
     ralt:                               '\u05f2'
 }
 
@@ -234,6 +250,7 @@
     label:                              'J'
     base:                               '\u05d7'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
     ralt:                               '\u05f1'
 }
 
@@ -241,12 +258,14 @@
     label:                              'K'
     base:                               '\u05dc'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               '\u05da'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
@@ -254,6 +273,7 @@
     base:                               '\u05e3'
     shift:                              ':'
     capslock:                           ';'
+    shift+capslock:                     ':'
 }
 
 key APOSTROPHE {
@@ -261,6 +281,7 @@
     base:                               ','
     shift:                              '"'
     capslock:                           '\''
+    shift+capslock:                     '"'
 }
 
 key BACKSLASH {
@@ -273,7 +294,7 @@
 
 key PLUS {
     label:                              '\\'
-    base, capslock:                     '\\'
+    base:                               '\\'
     shift:                              '|'
 }
 
@@ -281,42 +302,49 @@
     label:                              'Z'
     base:                               '\u05d6'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key X {
     label:                              'X'
     base:                               '\u05e1'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               '\u05d1'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               '\u05d4'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               '\u05e0'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               '\u05de'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key M {
     label:                              'M'
     base:                               '\u05e6'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key COMMA {
@@ -324,6 +352,7 @@
     base:                               '\u05ea'
     shift:                              '>'
     capslock:                           ','
+    shift+capslock:                     '>'
 }
 
 key PERIOD {
@@ -331,6 +360,7 @@
     base:                               '\u05e5'
     shift:                              '<'
     capslock:                           '.'
+    shift+capslock:                     '<'
 }
 
 key SLASH {
@@ -338,4 +368,5 @@
     base:                               '.'
     shift:                              '?'
     capslock:                           '/'
+    shift+capslock:                     '?'
 }
diff --git a/packages/InputDevices/res/raw/keyboard_layout_hungarian.kcm b/packages/InputDevices/res/raw/keyboard_layout_hungarian.kcm
index dafb50b..6c947c7 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_hungarian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_hungarian.kcm
@@ -101,6 +101,7 @@
     label:                              '\u00d6'
     base:                               '\u00f6'
     shift, capslock:                    '\u00d6'
+    shift+capslock:                     '\u00f6'
     ralt:                               '\u030b'
 }
 
@@ -108,6 +109,7 @@
     label:                              '\u00dc'
     base:                               '\u00fc'
     shift, capslock:                    '\u00dc'
+    shift+capslock:                     '\u00fc'
     ralt:                               '\u0308'
 }
 
@@ -115,6 +117,7 @@
     label:                              '\u00d3'
     base:                               '\u00f3'
     shift, capslock:                    '\u00d3'
+    shift+capslock:                     '\u00f3'
     ralt:                               '\u0327'
 }
 
@@ -124,6 +127,7 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
     ralt:                               '\\'
 }
 
@@ -131,6 +135,7 @@
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
     ralt:                               '|'
 }
 
@@ -138,6 +143,7 @@
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u00c4'
 }
 
@@ -145,24 +151,28 @@
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Z {
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
     ralt:                               '\u20ac'
 }
 
@@ -170,6 +180,7 @@
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
     ralt:                               '\u00cd'
 }
 
@@ -177,18 +188,21 @@
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
     label:                              '\u0150'
     base:                               '\u0151'
     shift, capslock:                    '\u0150'
+    shift+capslock:                     '\u0151'
     ralt:                               '\u00f7'
 }
 
@@ -196,6 +210,7 @@
     label:                              '\u00da'
     base:                               '\u00fa'
     shift, capslock:                    '\u00da'
+    shift+capslock:                     '\u00fa'
     ralt:                               '\u00d7'
 }
 
@@ -205,6 +220,7 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
     ralt:                               '\u00e4'
 }
 
@@ -212,6 +228,7 @@
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
     ralt:                               '\u0111'
 }
 
@@ -219,6 +236,7 @@
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
     ralt:                               '\u0110'
 }
 
@@ -226,6 +244,7 @@
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
     ralt:                               '['
 }
 
@@ -233,6 +252,7 @@
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
     ralt:                               ']'
 }
 
@@ -240,12 +260,14 @@
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
     ralt:                               '\u00ed'
 }
 
@@ -253,6 +275,7 @@
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
     ralt:                               '\u0197'
 }
 
@@ -260,6 +283,7 @@
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
     ralt:                               '\u0141'
 }
 
@@ -267,6 +291,7 @@
     label:                              '\u00c9'
     base:                               '\u00e9'
     shift, capslock:                    '\u00c9'
+    shift+capslock:                     '\u00e9'
     ralt:                               '$'
 }
 
@@ -274,6 +299,7 @@
     label:                              '\u00c1'
     base:                               '\u00e1'
     shift, capslock:                    '\u00c1'
+    shift+capslock:                     '\u00e1'
     ralt:                               '\u00df'
 }
 
@@ -281,6 +307,7 @@
     label:                              '\u0170'
     base:                               '\u0171'
     shift, capslock:                    '\u0170'
+    shift+capslock:                     '\u0171'
     ralt:                               '\u00a4'
 }
 
@@ -290,6 +317,7 @@
     label:                              '\u00cd'
     base:                               '\u00ed'
     shift, capslock:                    '\u00cd'
+    shift+capslock:                     '\u00ed'
     ralt:                               '<'
 }
 
@@ -297,6 +325,7 @@
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
     ralt:                               '>'
 }
 
@@ -304,6 +333,7 @@
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
     ralt:                               '#'
 }
 
@@ -311,6 +341,7 @@
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
     ralt:                               '&'
 }
 
@@ -318,6 +349,7 @@
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
     ralt:                               '@'
 }
 
@@ -325,6 +357,7 @@
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
     ralt:                               '{'
 }
 
@@ -332,6 +365,7 @@
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
     ralt:                               '}'
 }
 
@@ -339,6 +373,7 @@
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_icelandic.kcm b/packages/InputDevices/res/raw/keyboard_layout_icelandic.kcm
index 117f58b..5131b4f 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_icelandic.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_icelandic.kcm
@@ -99,6 +99,7 @@
     label:                              '\u00d6'
     base:                               '\u00f6'
     shift, capslock:                    '\u00d6'
+    shift+capslock:                     '\u00f6'
     ralt:                               '\\'
 }
 
@@ -114,6 +115,7 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
     ralt:                               '@'
 }
 
@@ -121,12 +123,14 @@
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
 }
 
@@ -134,48 +138,56 @@
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
     label:                              '\u0110'
     base:                               '\u0111'
     shift, capslock:                    '\u0110'
+    shift+capslock:                     '\u0111'
 }
 
 key RIGHT_BRACKET {
@@ -191,60 +203,70 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
     label:                              '\u00c6'
     base:                               '\u00e6'
     shift, capslock:                    '\u00c6'
+    shift+capslock:                     '\u00e6'
 }
 
 key APOSTROPHE {
@@ -274,42 +296,49 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
     ralt:                               '\u00b5'
 }
 
@@ -329,4 +358,5 @@
     label:                              '\u00de'
     base:                               '\u00fe'
     shift, capslock:                    '\u00de'
+    shift+capslock:                     '\u00fe'
 }
diff --git a/packages/InputDevices/res/raw/keyboard_layout_italian.kcm b/packages/InputDevices/res/raw/keyboard_layout_italian.kcm
index bd2d25a..309d8b2 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_italian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_italian.kcm
@@ -109,18 +109,21 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
 }
 
 key W {
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
 }
 
@@ -128,42 +131,49 @@
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
@@ -188,54 +198,63 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
@@ -270,42 +289,49 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_latvian_qwerty.kcm b/packages/InputDevices/res/raw/keyboard_layout_latvian_qwerty.kcm
index d4bc0c0..3b77cb1 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_latvian_qwerty.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_latvian_qwerty.kcm
@@ -119,70 +119,85 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
 }
 
 key W {
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u0113'
-    shift+ralt, ralt+capslock:          '\u0112'
+    shift+ralt, capslock+ralt:          '\u0112'
+    shift+capslock+ralt:                '\u0113'
 }
 
 key R {
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
     ralt:                               '\u0157'
-    shift+ralt, ralt+capslock:          '\u0156'
+    shift+ralt, capslock+ralt:          '\u0156'
+    shift+capslock+ralt:                '\u0157'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
     ralt:                               '\u016b'
-    shift+ralt, ralt+capslock:          '\u016a'
+    shift+ralt, capslock+ralt:          '\u016a'
+    shift+capslock+ralt:                '\u016b'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
     ralt:                               '\u012b'
-    shift+ralt, ralt+capslock:          '\u012a'
+    shift+ralt, capslock+ralt:          '\u012a'
+    shift+capslock+ralt:                '\u012b'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
     ralt:                               '\u00f5'
-    shift+ralt, ralt+capslock:          '\u00d5'
+    shift+ralt, capslock+ralt:          '\u00d5'
+    shift+capslock+ralt:                '\u00f5'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
@@ -204,64 +219,78 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
     ralt:                               '\u0101'
-    shift+ralt, ralt+capslock:          '\u0100'
+    shift+ralt, capslock+ralt:          '\u0100'
+    shift+capslock+ralt:                '\u0101'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
     ralt:                               '\u0161'
-    shift+ralt, ralt+capslock:          '\u0160'
+    shift+ralt, capslock+ralt:          '\u0160'
+    shift+capslock+ralt:                '\u0161'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
     ralt:                               '\u0123'
-    shift+ralt, ralt+capslock:          '\u0122'
+    shift+ralt, capslock+ralt:          '\u0122'
+    shift+capslock+ralt:                '\u0123'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
     ralt:                               '\u0137'
-    shift+ralt, ralt+capslock:          '\u0136'
+    shift+ralt, capslock+ralt:          '\u0136'
+    shift+capslock+ralt:                '\u0137'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
     ralt:                               '\u013c'
-    shift+ralt, ralt+capslock:          '\u013b'
+    shift+ralt, capslock+ralt:          '\u013b'
+    shift+capslock+ralt:                '\u013c'
 }
 
 key SEMICOLON {
@@ -298,48 +327,58 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
     ralt:                               '\u017e'
-    shift+ralt, ralt+capslock:          '\u017d'
+    shift+ralt, capslock+ralt:          '\u017d'
+    shift+capslock+ralt:                '\u017e'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
     ralt:                               '\u010d'
-    shift+ralt, ralt+capslock:          '\u010c'
+    shift+ralt, capslock+ralt:          '\u010c'
+    shift+capslock+ralt:                '\u010d'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
     ralt:                               '\u0146'
-    shift+ralt, ralt+capslock:          '\u0145'
+    shift+ralt, capslock+ralt:          '\u0145'
+    shift+capslock+ralt:                '\u0146'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_lithuanian.kcm b/packages/InputDevices/res/raw/keyboard_layout_lithuanian.kcm
index 72ca333..bcfdb12 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_lithuanian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_lithuanian.kcm
@@ -32,6 +32,7 @@
     label:                              '1'
     base:                               '\u0105'
     shift, capslock:                    '\u0104'
+    shift+capslock:                     '\u0105'
     ralt:                               '1'
     shift+ralt:                         '!'
 }
@@ -40,6 +41,7 @@
     label:                              '2'
     base:                               '\u010d'
     shift, capslock:                    '\u010c'
+    shift+capslock:                     '\u010d'
     ralt:                               '2'
     shift+ralt:                         '@'
 }
@@ -48,6 +50,7 @@
     label:                              '3'
     base:                               '\u0119'
     shift, capslock:                    '\u0118'
+    shift+capslock:                     '\u0119'
     ralt:                               '3'
     shift+ralt:                         '#'
 }
@@ -56,6 +59,7 @@
     label:                              '4'
     base:                               '\u0117'
     shift, capslock:                    '\u0116'
+    shift+capslock:                     '\u0117'
     ralt:                               '4'
     shift+ralt:                         '$'
 }
@@ -64,6 +68,7 @@
     label:                              '5'
     base:                               '\u012f'
     shift, capslock:                    '\u012e'
+    shift+capslock:                     '\u012f'
     ralt:                               '5'
     shift+ralt:                         '%'
 }
@@ -72,6 +77,7 @@
     label:                              '6'
     base:                               '\u0161'
     shift, capslock:                    '\u0160'
+    shift+capslock:                     '\u0161'
     ralt:                               '6'
     shift+ralt:                         '\u0302'
 }
@@ -80,6 +86,7 @@
     label:                              '7'
     base:                               '\u0173'
     shift, capslock:                    '\u0172'
+    shift+capslock:                     '\u0173'
     ralt:                               '7'
     shift+ralt:                         '&'
 }
@@ -88,6 +95,7 @@
     label:                              '8'
     base:                               '\u016b'
     shift, capslock:                    '\u016a'
+    shift+capslock:                     '\u016b'
     ralt:                               '8'
     shift+ralt:                         '*'
 }
@@ -116,6 +124,7 @@
     label:                              '='
     base:                               '\u017e'
     shift, capslock:                    '\u017d'
+    shift+capslock:                     '\u017e'
     ralt:                               '='
     shift+ralt:                         '+'
 }
@@ -126,18 +135,21 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
 }
 
 key W {
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
 }
 
@@ -145,42 +157,49 @@
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
@@ -201,54 +220,63 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
@@ -281,42 +309,49 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_mongolian.kcm b/packages/InputDevices/res/raw/keyboard_layout_mongolian.kcm
index 3d4a8c6..77cc672 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_mongolian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_mongolian.kcm
@@ -28,6 +28,7 @@
     label:                              '='
     base:                               '='
     shift, capslock:                    '+'
+    shift+capslock:                     '+'
     ralt:                               '`'
     ralt+shift:                         '~'
 }
@@ -122,86 +123,107 @@
     label:                              '\u0444'
     base:                               '\u0444'
     shift, capslock:                    '\u0424'
+    shift+capslock:                     '\u0444'
     ralt:                               'q'
-    ralt+shift, ralt+capslock:          'Q'
+    shift+ralt, capslock+ralt:          'Q'
+    shift+capslock+ralt:                'q'
 }
 
 key W {
     label:                              '\u0446'
     base:                               '\u0446'
     shift, capslock:                    '\u0426'
+    shift+capslock:                     '\u0446'
     ralt:                               'w'
-    ralt+shift, ralt+capslock:          'W'
+    shift+ralt, capslock+ralt:          'W'
+    shift+capslock+ralt:                'w'
 }
 
 key E {
     label:                              '\u0443'
     base:                               '\u0443'
     shift, capslock:                    '\u0423'
+    shift+capslock:                     '\u0443'
     ralt:                               'e'
-    ralt+shift, ralt+capslock:          'E'
+    shift+ralt, capslock+ralt:          'E'
+    shift+capslock+ralt:                'e'
 }
 
 key R {
     label:                              '\u0436'
     base:                               '\u0436'
     shift, capslock:                    '\u0416'
+    shift+capslock:                     '\u0436'
     ralt:                               'r'
-    ralt+shift, ralt+capslock:          'R'
+    shift+ralt, capslock+ralt:          'R'
+    shift+capslock+ralt:                'r'
 }
 
 key T {
     label:                              '\u044d'
     base:                               '\u044d'
     shift, capslock:                    '\u042d'
+    shift+capslock:                     '\u044d'
     ralt:                               't'
-    ralt+shift, ralt+capslock:          'T'
+    shift+ralt, capslock+ralt:          'T'
+    shift+capslock+ralt:                't'
 }
 
 key Y {
     label:                              '\u043d'
     base:                               '\u043d'
     shift, capslock:                    '\u041d'
+    shift+capslock:                     '\u043d'
     ralt:                               'y'
-    ralt+shift, ralt+capslock:          'Y'
+    shift+ralt, capslock+ralt:          'Y'
+    shift+capslock+ralt:                'y'
 }
 
 key U {
     label:                              '\u0433'
     base:                               '\u0433'
     shift, capslock:                    '\u0413'
+    shift+capslock:                     '\u0433'
     ralt:                               'u'
-    ralt+shift, ralt+capslock:          'U'
+    shift+ralt, capslock+ralt:          'U'
+    shift+capslock+ralt:                'u'
 }
 
 key I {
     label:                              '\u0448'
     base:                               '\u0448'
     shift, capslock:                    '\u0428'
+    shift+capslock:                     '\u0448'
     ralt:                               'i'
-    ralt+shift, ralt+capslock:          'I'
+    shift+ralt, capslock+ralt:          'I'
+    shift+capslock+ralt:                'i'
 }
 
 key O {
     label:                              '\u04af'
     base:                               '\u04af'
     shift, capslock:                    '\u04ae'
+    shift+capslock:                     '\u04af'
     ralt:                               'o'
-    ralt+shift, ralt+capslock:          'O'
+    shift+ralt, capslock+ralt:          'O'
+    shift+capslock+ralt:                'o'
 }
 
 key P {
     label:                              '\u0437'
     base:                               '\u0437'
     shift, capslock:                    '\u0417'
+    shift+capslock:                     '\u0437'
     ralt:                               'p'
-    ralt+shift, ralt+capslock:          'P'
+    shift+ralt, capslock+ralt:          'P'
+    shift+capslock+ralt:                'p'
 }
 
 key LEFT_BRACKET {
     label:                              '\u043a'
     base:                               '\u043a'
     shift, capslock:                    '\u041a'
+    shift+capslock:                     '\u043a'
     ralt:                               '['
     ralt+shift:                         '{'
 }
@@ -210,6 +232,7 @@
     label:                              '\u044a'
     base:                               '\u044a'
     shift, capslock:                    '\u042a'
+    shift+capslock:                     '\u044a'
     ralt:                               ']'
     ralt+shift:                         '}'
 }
@@ -220,78 +243,97 @@
     label:                              '\u0439'
     base:                               '\u0439'
     shift, capslock:                    '\u0419'
+    shift+capslock:                     '\u0439'
     ralt:                               'a'
-    ralt+shift, ralt+capslock:          'A'
+    shift+ralt, capslock+ralt:          'A'
+    shift+capslock+ralt:                'a'
 }
 
 key S {
     label:                              '\u044b'
     base:                               '\u044b'
     shift, capslock:                    '\u042b'
+    shift+capslock:                     '\u044b'
     ralt:                               's'
-    ralt+shift, ralt+capslock:          'S'
+    shift+ralt, capslock+ralt:          'S'
+    shift+capslock+ralt:                's'
 }
 
 key D {
     label:                              '\u0431'
     base:                               '\u0431'
     shift, capslock:                    '\u0411'
+    shift+capslock:                     '\u0431'
     ralt:                               'd'
-    ralt+shift, ralt+capslock:          'D'
+    shift+ralt, capslock+ralt:          'D'
+    shift+capslock+ralt:                'd'
 }
 
 key F {
     label:                              '\u04e9'
     base:                               '\u04e9'
     shift, capslock:                    '\u04e8'
+    shift+capslock:                     '\u04e9'
     ralt:                               'f'
-    ralt+shift, ralt+capslock:          'F'
+    shift+ralt, capslock+ralt:          'F'
+    shift+capslock+ralt:                'f'
 }
 
 key G {
     label:                              '\u0430'
     base:                               '\u0430'
     shift, capslock:                    '\u0410'
+    shift+capslock:                     '\u0430'
     ralt:                               'g'
-    ralt+shift, ralt+capslock:          'G'
+    shift+ralt, capslock+ralt:          'G'
+    shift+capslock+ralt:                'g'
 }
 
 key H {
     label:                              '\u0445'
     base:                               '\u0445'
     shift, capslock:                    '\u0425'
+    shift+capslock:                     '\u0445'
     ralt:                               'h'
-    ralt+shift, ralt+capslock:          'H'
+    shift+ralt, capslock+ralt:          'H'
+    shift+capslock+ralt:                'h'
 }
 
 key J {
     label:                              '\u0440'
     base:                               '\u0440'
     shift, capslock:                    '\u0420'
+    shift+capslock:                     '\u0440'
     ralt:                               'j'
-    ralt+shift, ralt+capslock:          'J'
+    shift+ralt, capslock+ralt:          'J'
+    shift+capslock+ralt:                'j'
 }
 
 key K {
     label:                              '\u043e'
     base:                               '\u043e'
     shift, capslock:                    '\u041e'
+    shift+capslock:                     '\u043e'
     ralt:                               'k'
-    ralt+shift, ralt+capslock:          'K'
+    shift+ralt, capslock+ralt:          'K'
+    shift+capslock+ralt:                'k'
 }
 
 key L {
     label:                              '\u043b'
     base:                               '\u043b'
     shift, capslock:                    '\u041b'
+    shift+capslock:                     '\u043b'
     ralt:                               'l'
-    ralt+shift, ralt+capslock:          'L'
+    shift+ralt, capslock+ralt:          'L'
+    shift+capslock+ralt:                'l'
 }
 
 key SEMICOLON {
     label:                              '\u0434'
     base:                               '\u0434'
     shift, capslock:                    '\u0414'
+    shift+capslock:                     '\u0434'
     ralt:                               ';'
     ralt+shift:                         ':'
 }
@@ -300,6 +342,7 @@
     label:                              '\u043f'
     base:                               '\u043f'
     shift, capslock:                    '\u041f'
+    shift+capslock:                     '\u043f'
     ralt:                               '\''
     ralt+shift:                         '"'
 }
@@ -318,62 +361,77 @@
     label:                              '\u044f'
     base:                               '\u044f'
     shift, capslock:                    '\u042f'
+    shift+capslock:                     '\u044f'
     ralt:                               'z'
-    ralt+shift, ralt+capslock:          'Z'
+    shift+ralt, capslock+ralt:          'Z'
+    shift+capslock+ralt:                'z'
 }
 
 key X {
     label:                              '\u0447'
     base:                               '\u0447'
     shift, capslock:                    '\u0427'
+    shift+capslock:                     '\u0447'
     ralt:                               'x'
-    ralt+shift, ralt+capslock:          'X'
+    shift+ralt, capslock+ralt:          'X'
+    shift+capslock+ralt:                'x'
 }
 
 key C {
     label:                              '\u0451'
     base:                               '\u0451'
     shift, capslock:                    '\u0401'
+    shift+capslock:                     '\u0451'
     ralt:                               'c'
-    ralt+shift, ralt+capslock:          'C'
+    shift+ralt, capslock+ralt:          'C'
+    shift+capslock+ralt:                'c'
 }
 
 key V {
     label:                              '\u0441'
     base:                               '\u0441'
     shift, capslock:                    '\u0421'
+    shift+capslock:                     '\u0441'
     ralt:                               'v'
-    ralt+shift, ralt+capslock:          'V'
+    shift+ralt, capslock+ralt:          'V'
+    shift+capslock+ralt:                'v'
 }
 
 key B {
     label:                              '\u043c'
     base:                               '\u043c'
     shift, capslock:                    '\u041c'
+    shift+capslock:                     '\u043c'
     ralt:                               'b'
-    ralt+shift, ralt+capslock:          'B'
+    shift+ralt, capslock+ralt:          'B'
+    shift+capslock+ralt:                'b'
 }
 
 key N {
     label:                              '\u0438'
     base:                               '\u0438'
     shift, capslock:                    '\u0418'
+    shift+capslock:                     '\u0438'
     ralt:                               'n'
-    ralt+shift, ralt+capslock:          'N'
+    shift+ralt, capslock+ralt:          'N'
+    shift+capslock+ralt:                'n'
 }
 
 key M {
     label:                              '\u0442'
     base:                               '\u0442'
     shift, capslock:                    '\u0422'
+    shift+capslock:                     '\u0442'
     ralt:                               'm'
-    ralt+shift, ralt+capslock:          'M'
+    shift+ralt, capslock+ralt:          'M'
+    shift+capslock+ralt:                'm'
 }
 
 key COMMA {
     label:                              '\u044c'
     base:                               '\u044c'
     shift, capslock:                    '\u042c'
+    shift+capslock:                     '\u044c'
     ralt:                               ','
     ralt+shift:                         '<'
 }
@@ -382,6 +440,7 @@
     label:                              '\u0432'
     base:                               '\u0432'
     shift, capslock:                    '\u0412'
+    shift+capslock:                     '\u0432'
     ralt:                               '.'
     ralt+shift:                         '>'
 }
diff --git a/packages/InputDevices/res/raw/keyboard_layout_norwegian.kcm b/packages/InputDevices/res/raw/keyboard_layout_norwegian.kcm
index 560dd16..cae1c94 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_norwegian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_norwegian.kcm
@@ -115,76 +115,90 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
     ralt:                               '\u00e2'
-    ralt+capslock, shift+ralt:          '\u00c2'
+    shift+ralt, capslock+ralt:          '\u00c2'
+    shift+capslock+ralt:                '\u00e2'
 }
 
 key W {
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
-    ralt+capslock:                      '\u20ac'
 }
 
 key R {
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
     ralt:                               '\u0167'
-    ralt+capslock, shift+ralt:          '\u0166'
+    shift+ralt, capslock+ralt:          '\u0166'
+    shift+capslock+ralt:                '\u0167'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
     ralt:                               '\u00ef'
-    ralt+capslock, shift+ralt:          '\u00cf'
+    shift+ralt, capslock+ralt:          '\u00cf'
+    shift+capslock+ralt:                '\u00ef'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
     ralt:                               '\u00f5'
-    ralt+capslock, shift+ralt:          '\u00d5'
+    shift+ralt, capslock+ralt:          '\u00d5'
+    shift+capslock+ralt:                '\u00f5'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
     label:                              '\u00c5'
     base:                               '\u00e5'
     shift, capslock:                    '\u00c5'
+    shift+capslock:                     '\u00e5'
 }
 
 key RIGHT_BRACKET {
@@ -200,84 +214,104 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
     ralt:                               '\u00e1'
-    ralt+capslock, shift+ralt:          '\u00c1'
+    shift+ralt, capslock+ralt:          '\u00c1'
+    shift+capslock+ralt:                '\u00e1'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
     ralt:                               '\u0161'
-    ralt+capslock, shift+ralt:          '\u0160'
+    shift+ralt, capslock+ralt:          '\u0160'
+    shift+capslock+ralt:                '\u0161'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
     ralt:                               '\u0111'
-    ralt+capslock, shift+ralt:          '\u0110'
+    shift+ralt, capslock+ralt:          '\u0110'
+    shift+capslock+ralt:                '\u0111'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
     ralt:                               '\u01e5'
-    ralt+capslock, shift+ralt:          '\u01e4'
+    shift+ralt, capslock+ralt:          '\u01e4'
+    shift+capslock+ralt:                '\u01e5'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
     ralt:                               '\u01e7'
-    ralt+capslock, shift+ralt:          '\u01e6'
+    shift+ralt, capslock+ralt:          '\u01e6'
+    shift+capslock+ralt:                '\u01e7'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
     ralt:                               '\u021f'
-    ralt+capslock, shift+ralt:          '\u021e'
+    shift+ralt, capslock+ralt:          '\u021e'
+    shift+capslock+ralt:                '\u021f'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
     ralt:                               '\u01e9'
-    ralt+capslock, shift+ralt:          '\u01e8'
+    shift+ralt, capslock+ralt:          '\u01e8'
+    shift+capslock+ralt:                '\u01e9'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
     label:                              '\u00d8'
     base:                               '\u00f8'
     shift, capslock:                    '\u00d8'
+    shift+capslock:                     '\u00f8'
     ralt:                               '\u00f6'
-    ralt+capslock, shift+ralt:          '\u00d6'
+    shift+ralt, capslock+ralt:          '\u00d6'
+    shift+capslock+ralt:                '\u00f6'
 }
 
 key APOSTROPHE {
     label:                              '\u00c6'
     base:                               '\u00e6'
     shift, capslock:                    '\u00c6'
+    shift+capslock:                     '\u00e6'
     ralt:                               '\u00e4'
-    ralt+capslock, shift+ralt:          '\u00c4'
+    shift+ralt, capslock+ralt:          '\u00c4'
+    shift+capslock+ralt:                '\u00e4'
 }
 
 key BACKSLASH {
@@ -298,53 +332,65 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
     ralt:                               '\u017e'
-    ralt+capslock, shift+ralt:          '\u017d'
+    shift+ralt, capslock+ralt:          '\u017d'
+    shift+capslock+ralt:                '\u017e'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
     ralt:                               '\u010d'
-    ralt+capslock, shift+ralt:          '\u010c'
+    shift+ralt, capslock+ralt:          '\u010c'
+    shift+capslock+ralt:                '\u010d'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
     ralt:                               '\u01ef'
-    ralt+capslock, shift+ralt:          '\u01ee'
+    shift+ralt, capslock+ralt:          '\u01ee'
+    shift+capslock+ralt:                '\u01ef'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
     ralt:                               '\u0292'
-    ralt+capslock, shift+ralt:          '\u01b7'
+    shift+ralt, capslock+ralt:          '\u01b7'
+    shift+capslock+ralt:                '\u0292'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
     ralt:                               '\u014b'
-    ralt+capslock, shift+ralt:          '\u014a'
+    shift+ralt, capslock+ralt:          '\u014a'
+    shift+capslock+ralt:                '\u014b'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
-    ralt, ralt+capslock:                '\u00b5'
+    shift+capslock:                     'm'
+    ralt:                               '\u00b5'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_persian.kcm b/packages/InputDevices/res/raw/keyboard_layout_persian.kcm
index bfe7821..6744922 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_persian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_persian.kcm
@@ -22,231 +22,222 @@
     label:                              '\u0634'
     base:                               '\u0634'
     shift, capslock:                    '\u0624'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u0634'
 }
 
 key B {
     label:                              '\u0630'
     base:                               '\u0630'
     shift, capslock:                    '\u200C'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u0630'
 }
 
 key C {
     label:                              '\u0632'
     base:                               '\u0632'
     shift, capslock:                    '\u0698'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u0632'
 }
 
 key D {
     label:                              '\u06CC'
     base:                               '\u06CC'
     shift, capslock:                    '\u064A'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u06CC'
 }
 
 key E {
     label:                              '\u062B'
     base:                               '\u062B'
     shift, capslock:                    '\u064D'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u062B'
 }
 
 key F {
     label:                              '\u0628'
     base:                               '\u0628'
     shift, capslock:                    '\u0625'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u0628'
 }
 
 key G {
     label:                              '\u0644'
     base:                               '\u0644'
     shift, capslock:                    '\u0623'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u0644'
 }
 
 key H {
     label:                              '\u0627'
     base:                               '\u0627'
     shift, capslock:                    '\u0622'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u0627'
 }
 
 key I {
     label:                              '\u0647'
     base:                               '\u0647'
     shift, capslock:                    '\u0651'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u0647'
 }
 
 key J {
     label:                              '\u062A'
     base:                               '\u062A'
     shift, capslock:                    '\u0629'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u062A'
 }
 
 key K {
     label:                              '\u0646'
     base:                               '\u0646'
     shift, capslock:                    '\u00AB'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u0646'
 }
 
 key L {
     label:                              '\u0645'
     base:                               '\u0645'
     shift, capslock:                    '\u00BB'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u0645'
 }
 
 key M {
     label:                              '\u067E'
     base:                               '\u067E'
     shift, capslock:                    '\u0621'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u067E'
 }
 
 key N {
     label:                              '\u062F'
     base:                               '\u062F'
     shift, capslock:                    '\u0654'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u062F'
 }
 
 key O {
     label:                              '\u062E'
     base:                               '\u062E'
-    shift, capslock:                    ']'
-    ctrl, alt, meta:                    none
+    shift:                              ']'
 }
 
 key P {
     label:                              '\u062D'
     base:                               '\u062D'
-    shift, capslock:                    '['
-    ctrl, alt, meta:                    none
+    shift:                              '['
 }
 
 key Q {
     label:                              '\u0636'
     base:                               '\u0636'
     shift, capslock:                    '\u0652'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u0636'
 }
 
 key R {
     label:                              '\u0642'
     base:                               '\u0642'
     shift, capslock:                    '\u064B'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u0642'
 }
 
 key S {
     label:                              '\u0633'
     base:                               '\u0633'
     shift, capslock:                    '\u0626'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u0633'
 }
 
 key T {
     label:                              '\u0641'
     base:                               '\u0641'
     shift, capslock:                    '\u064F'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u0641'
 }
 
 key U {
     label:                              '\u0639'
     base:                               '\u0639'
     shift, capslock:                    '\u064E'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u0639'
 }
 
 key V {
     label:                              '\u0631'
     base:                               '\u0631'
     shift, capslock:                    '\u0670'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u0631'
 }
 
 key W {
     label:                              '\u0635'
     base:                               '\u0635'
     shift, capslock:                    '\u064C'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u0635'
 }
 
 key X {
     label:                              '\u0637'
     base:                               '\u0637'
     shift, capslock:                    '\u0653'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u0637'
 }
 
 key Y {
     label:                              '\u063A'
     base:                               '\u063A'
     shift, capslock:                    '\u0650'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u063A'
 }
 
 key Z {
     label:                              '\u0638'
     base:                               '\u0638'
     shift, capslock:                    '\u0643'
-    ctrl, alt, meta:                    none
+    shift+capslock:                     '\u0638'
 }
 
 key 0 {
     label, number:                      '\u06F0'
     base:                               '\u06F0'
     shift:                              '('
-    ctrl, alt, meta:                    none
 }
 
 key 1 {
     label, number:                      '\u06F1'
     base:                               '\u06F1'
     shift:                              '!'
-    ctrl, alt, meta:                    none
 }
 
 key 2 {
     label, number:                      '\u06F2'
     base:                               '\u06F2'
     shift:                              '\u066C'
-    ctrl, alt, meta:                    none
 
 }
 key 3 {
     label, number:                      '\u06F3'
     base:                               '\u06F3'
     shift:                              '\u066B'
-    ctrl, alt, meta:                    none
 }
 
 key 4 {
     label, number:                      '\u06F4'
     base:                               '\u06F4'
     shift:                              '\uFDFC'
-    ctrl, alt, meta:                    none
 }
 
 key 5 {
     label, number:                      '\u06F5'
     base:                               '\u06F5'
     shift:                              '\u066A'
-    ctrl, alt, meta:                    none
 }
 
 key 6 {
     label, number:                      '\u06F6'
     base:                               '\u06F6'
     shift:                              '\u00D7'
-    ctrl, alt, meta:                    none
 }
 
 
@@ -254,248 +245,82 @@
     label, number:                      '\u06F7'
     base:                               '\u06F7'
     shift:                              '\u060C'
-    ctrl, alt, meta:                    none
 }
 
 key 8 {
     label, number:                      '\u06F8'
     base:                               '\u06F8'
     shift:                              '*'
-    ctrl, alt, meta:                    none
 }
 
 key 9 {
     label, number:                      '\u06F9'
     base:                               '\u06F9'
     shift:                              ')'
-    ctrl, alt, meta:                    none
-}
-
-key SPACE {
-    label:                              ' '
-    base:                               ' '
-    ctrl, alt, meta:                    none
-}
-
-key ENTER {
-    label:                              '\n'
-    base:                               '\n'
-    ctrl, alt, meta:                    none
-}
-
-key TAB {
-    label:                              '\t'
-    base:                               '\t'
-    ctrl, alt, meta:                    none
 }
 
 key COMMA {
     label, number:                      '\u0648'
     base:                               '\u0648'
     shift:                              '>'
-    ctrl, alt, meta:                    none
 }
 
 key PERIOD {
     label, number:                      '.'
     base:                               '.'
     shift:                              '<'
-    ctrl, alt, meta:                    none
 }
 
 key SLASH {
     label, number:                      '/'
     base:                               '/'
     shift:                              '\u061F'
-    ctrl, alt, meta:                    none
 }
 
 key GRAVE {
     label, number:                      '`'
     base:                               '`'
     shift:                              '\u00F7'
-    ctrl, alt, meta:                    none
 }
 
-
 key MINUS {
     label, number:                      '-'
     base:                               '-'
     shift:                              '_'
-    ctrl, alt, meta:                    none
 }
 
 key EQUALS {
     label, number:                      '='
     base:                               '='
     shift:                              '+'
-    ctrl, alt, meta:                    none
 }
 
 key LEFT_BRACKET {
     label, number:                      '\u062C'
     base:                               '\u062C'
     shift:                              '}'
-    ctrl, alt, meta:                    none
 }
 
 key RIGHT_BRACKET {
     label, number:                      '\u0686'
     base:                               '\u0686'
     shift:                              '{'
-    ctrl, alt, meta:                    none
 }
 
 key BACKSLASH {
     label, number:                      '\\'
     base:                               '\\'
     shift:                              '|'
-    ctrl, alt, meta:                    none
 }
 
 key SEMICOLON {
     label, number:                      '\u06A9'
     base:                               '\u06A9'
     shift:                              ':'
-    ctrl, alt, meta:                    none
 }
 
 key APOSTROPHE {
     label, number:                      '\''
     base:                               '\''
     shift:                              '\"'
-    ctrl, alt, meta:                    none
-}
-
-### Numeric keypad ###
-
-key NUMPAD_0 {
-    label, number:                      '0'
-    base:                               fallback INSERT
-    numlock:                            '0'
-    ctrl, alt, meta:                    none
-}
-
-key NUMPAD_1 {
-    label, number:                      '1'
-    base:                               fallback MOVE_END
-    numlock:                            '1'
-    ctrl, alt, meta:                    none
-}
-
-key NUMPAD_2 {
-    label, number:                      '2'
-    base:                               fallback DPAD_DOWN
-    numlock:                            '2'
-    ctrl, alt, meta:                    none
-}
-
-key NUMPAD_3 {
-    label, number:                      '3'
-    base:                               fallback PAGE_DOWN
-    numlock:                            '3'
-    ctrl, alt, meta:                    none
-}
-
-key NUMPAD_4 {
-    label, number:                      '4'
-    base:                               fallback DPAD_LEFT
-    numlock:                            '4'
-    ctrl, alt, meta:                    none
-}
-
-key NUMPAD_5 {
-    label, number:                      '5'
-    base:                               fallback DPAD_CENTER
-    numlock:                            '5'
-    ctrl, alt, meta:                    none
-}
-
-key NUMPAD_6 {
-    label, number:                      '6'
-    base:                               fallback DPAD_RIGHT
-    numlock:                            '6'
-    ctrl, alt, meta:                    none
-}
-
-key NUMPAD_7 {
-    label, number:                      '7'
-    base:                               fallback MOVE_HOME
-    numlock:                            '7'
-    ctrl, alt, meta:                    none
-}
-
-key NUMPAD_8 {
-    label, number:                      '8'
-    base:                               fallback DPAD_UP
-    numlock:                            '8'
-    ctrl, alt, meta:                    none
-}
-
-key NUMPAD_9 {
-    label, number:                      '9'
-    base:                               fallback PAGE_UP
-    numlock:                            '9'
-    ctrl, alt, meta:                    none
-}
-
-key NUMPAD_LEFT_PAREN {
-    label, number:                      ')'
-    base:                               ')'
-    ctrl, alt, meta:                    none
-}
-
-key NUMPAD_RIGHT_PAREN {
-    label, number:                      '('
-    base:                               '('
-    ctrl, alt, meta:                    none
-}
-
-key NUMPAD_DIVIDE {
-    label, number:                      '/'
-    base:                               '/'
-    ctrl, alt, meta:                    none
-}
-
-key NUMPAD_MULTIPLY {
-    label, number:                      '*'
-    base:                               '*'
-    ctrl, alt, meta:                    none
-}
-
-key NUMPAD_SUBTRACT {
-    label, number:                      '-'
-    base:                               '-'
-    ctrl, alt, meta:                    none
-}
-
-key NUMPAD_ADD {
-    label, number:                      '+'
-    base:                               '+'
-    ctrl, alt, meta:                    none
-}
-
-key NUMPAD_DOT {
-    label, number:                      '.'
-    base:                               fallback FORWARD_DEL
-    numlock:                            '.'
-    ctrl, alt, meta:                    none
-}
-
-key NUMPAD_COMMA {
-    label, number:                      ','
-    base:                               ','
-    ctrl, alt, meta:                    none
-}
-
-key NUMPAD_EQUALS {
-    label, number:                      '='
-    base:                               '='
-    ctrl, alt, meta:                    none
-}
-
-key NUMPAD_ENTER {
-    label:                              '\n'
-    base:                               '\n' fallback ENTER
-    ctrl, alt, meta:                    none fallback ENTER
-}
+}
\ No newline at end of file
diff --git a/packages/InputDevices/res/raw/keyboard_layout_polish.kcm b/packages/InputDevices/res/raw/keyboard_layout_polish.kcm
index 559ec07..66fbefc1 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_polish.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_polish.kcm
@@ -104,64 +104,76 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
 }
 
 key W {
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u0119'
-    ralt+shift, ralt+capslock:          '\u0118'
+    shift+ralt, capslock+ralt:          '\u0118'
+    shift+capslock+ralt:                '\u0119'
 }
 
 key R {
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
     ralt:                               '\u00F3'
-    ralt+shift, ralt+capslock:          '\u00D3'
+    shift+ralt, capslock+ralt:          '\u00D3'
+    shift+capslock+ralt:                '\u00F3'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
@@ -188,60 +200,72 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
     ralt:                               '\u0105'
-    ralt+shift, ralt+capslock:          '\u0104'
+    shift+ralt, capslock+ralt:          '\u0104'
+    shift+capslock+ralt:                '\u0105'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
     ralt:                               '\u015b'
-    ralt+shift, ralt+capslock:          '\u015a'
+    shift+ralt, capslock+ralt:          '\u015a'
+    shift+capslock+ralt:                '\u015b'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
     ralt:                               '\u0142'
-    ralt+shift, ralt+capslock:          '\u0141'
+    shift+ralt, capslock+ralt:          '\u0141'
+    shift+capslock+ralt:                '\u0142'
 }
 
 key SEMICOLON {
@@ -262,50 +286,61 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
     ralt:                               '\u017c'
-    ralt+shift, ralt+capslock:          '\u017b'
+    shift+ralt, capslock+ralt:          '\u017b'
+    shift+capslock+ralt:                '\u017c'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
     ralt:                               '\u017a'
-    ralt+shift, ralt+capslock:          '\u0179'
+    shift+ralt, capslock+ralt:          '\u0179'
+    shift+capslock+ralt:                '\u017a'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
     ralt:                               '\u0107'
-    ralt+shift, ralt+capslock:          '\u0106'
+    shift+ralt, capslock+ralt:          '\u0106'
+    shift+capslock+ralt:                '\u0107'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
     ralt:                               '\u0144'
-    ralt+shift, ralt+capslock:          '\u0143'
+    shift+ralt, capslock+ralt:          '\u0143'
+    shift+capslock+ralt:                '\u0144'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_portuguese.kcm b/packages/InputDevices/res/raw/keyboard_layout_portuguese.kcm
index 47ee867..6fe0e47 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_portuguese.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_portuguese.kcm
@@ -115,18 +115,21 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
 }
 
 key W {
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
 }
 
@@ -134,42 +137,49 @@
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
@@ -191,60 +201,70 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
     label:                              '\u00c7'
     base:                               '\u00e7'
     shift, capslock:                    '\u00c7'
+    shift+capslock:                     '\u00e7'
 }
 
 key APOSTROPHE {
@@ -272,42 +292,49 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_russian.kcm b/packages/InputDevices/res/raw/keyboard_layout_russian.kcm
index 41c6bb3..ecada49 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_russian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_russian.kcm
@@ -28,6 +28,7 @@
     label:                              '\u0401'
     base:                               '\u0451'
     shift, capslock:                    '\u0401'
+    shift+capslock:                     '\u0451'
     ralt:                               '`'
     ralt+shift:                         '~'
 }
@@ -124,86 +125,107 @@
     label:                              '\u0419'
     base:                               '\u0439'
     shift, capslock:                    '\u0419'
+    shift+capslock:                     '\u0439'
     ralt:                               'q'
-    ralt+shift, ralt+capslock:          'Q'
+    shift+ralt, capslock+ralt:          'Q'
+    shift+capslock+ralt:                'q'
 }
 
 key W {
     label:                              '\u0426'
     base:                               '\u0446'
     shift, capslock:                    '\u0426'
+    shift+capslock:                     '\u0446'
     ralt:                               'w'
-    ralt+shift, ralt+capslock:          'W'
+    shift+ralt, capslock+ralt:          'W'
+    shift+capslock+ralt:                'w'
 }
 
 key E {
     label:                              '\u0423'
     base:                               '\u0443'
     shift, capslock:                    '\u0423'
+    shift+capslock:                     '\u0443'
     ralt:                               'e'
-    ralt+shift, ralt+capslock:          'E'
+    shift+ralt, capslock+ralt:          'E'
+    shift+capslock+ralt:                'e'
 }
 
 key R {
     label:                              '\u041a'
     base:                               '\u043a'
     shift, capslock:                    '\u041a'
+    shift+capslock:                     '\u043a'
     ralt:                               'r'
-    ralt+shift, ralt+capslock:          'R'
+    shift+ralt, capslock+ralt:          'R'
+    shift+capslock+ralt:                'r'
 }
 
 key T {
     label:                              '\u0415'
     base:                               '\u0435'
     shift, capslock:                    '\u0415'
+    shift+capslock:                     '\u0435'
     ralt:                               't'
-    ralt+shift, ralt+capslock:          'T'
+    shift+ralt, capslock+ralt:          'T'
+    shift+capslock+ralt:                't'
 }
 
 key Y {
     label:                              '\u041d'
     base:                               '\u043d'
     shift, capslock:                    '\u041d'
+    shift+capslock:                     '\u043d'
     ralt:                               'y'
-    ralt+shift, ralt+capslock:          'Y'
+    shift+ralt, capslock+ralt:          'Y'
+    shift+capslock+ralt:                'y'
 }
 
 key U {
     label:                              '\u0413'
     base:                               '\u0433'
     shift, capslock:                    '\u0413'
+    shift+capslock:                     '\u0433'
     ralt:                               'u'
-    ralt+shift, ralt+capslock:          'U'
+    shift+ralt, capslock+ralt:          'U'
+    shift+capslock+ralt:                'u'
 }
 
 key I {
     label:                              '\u0428'
     base:                               '\u0448'
     shift, capslock:                    '\u0428'
+    shift+capslock:                     '\u0448'
     ralt:                               'i'
-    ralt+shift, ralt+capslock:          'I'
+    shift+ralt, capslock+ralt:          'I'
+    shift+capslock+ralt:                'i'
 }
 
 key O {
     label:                              '\u0429'
     base:                               '\u0449'
     shift, capslock:                    '\u0429'
+    shift+capslock:                     '\u0449'
     ralt:                               'o'
-    ralt+shift, ralt+capslock:          'O'
+    shift+ralt, capslock+ralt:          'O'
+    shift+capslock+ralt:                'o'
 }
 
 key P {
     label:                              '\u0417'
     base:                               '\u0437'
     shift, capslock:                    '\u0417'
+    shift+capslock:                     '\u0437'
     ralt:                               'p'
-    ralt+shift, ralt+capslock:          'P'
+    shift+ralt, capslock+ralt:          'P'
+    shift+capslock+ralt:                'p'
 }
 
 key LEFT_BRACKET {
     label:                              '\u0425'
     base:                               '\u0445'
     shift, capslock:                    '\u0425'
+    shift+capslock:                     '\u0445'
     ralt:                               '['
     ralt+shift:                         '{'
 }
@@ -212,6 +234,7 @@
     label:                              '\u042a'
     base:                               '\u044a'
     shift, capslock:                    '\u042a'
+    shift+capslock:                     '\u044a'
     ralt:                               ']'
     ralt+shift:                         '}'
 }
@@ -222,78 +245,97 @@
     label:                              '\u0424'
     base:                               '\u0444'
     shift, capslock:                    '\u0424'
+    shift+capslock:                     '\u0444'
     ralt:                               'a'
-    ralt+shift, ralt+capslock:          'A'
+    shift+ralt, capslock+ralt:          'A'
+    shift+capslock+ralt:                'a'
 }
 
 key S {
     label:                              '\u042b'
     base:                               '\u044b'
     shift, capslock:                    '\u042b'
+    shift+capslock:                     '\u044b'
     ralt:                               's'
-    ralt+shift, ralt+capslock:          'S'
+    shift+ralt, capslock+ralt:          'S'
+    shift+capslock+ralt:                's'
 }
 
 key D {
     label:                              '\u0412'
     base:                               '\u0432'
     shift, capslock:                    '\u0412'
+    shift+capslock:                     '\u0432'
     ralt:                               'd'
-    ralt+shift, ralt+capslock:          'D'
+    shift+ralt, capslock+ralt:          'D'
+    shift+capslock+ralt:                'd'
 }
 
 key F {
     label:                              '\u0410'
     base:                               '\u0430'
     shift, capslock:                    '\u0410'
+    shift+capslock:                     '\u0430'
     ralt:                               'f'
-    ralt+shift, ralt+capslock:          'F'
+    shift+ralt, capslock+ralt:          'F'
+    shift+capslock+ralt:                'f'
 }
 
 key G {
     label:                              '\u041f'
     base:                               '\u043f'
     shift, capslock:                    '\u041f'
+    shift+capslock:                     '\u043f'
     ralt:                               'g'
-    ralt+shift, ralt+capslock:          'G'
+    shift+ralt, capslock+ralt:          'G'
+    shift+capslock+ralt:                'g'
 }
 
 key H {
     label:                              '\u0420'
     base:                               '\u0440'
     shift, capslock:                    '\u0420'
+    shift+capslock:                     '\u0440'
     ralt:                               'h'
-    ralt+shift, ralt+capslock:          'H'
+    shift+ralt, capslock+ralt:          'H'
+    shift+capslock+ralt:                'h'
 }
 
 key J {
     label:                              '\u041e'
     base:                               '\u043e'
     shift, capslock:                    '\u041e'
+    shift+capslock:                     '\u043e'
     ralt:                               'j'
-    ralt+shift, ralt+capslock:          'J'
+    shift+ralt, capslock+ralt:          'J'
+    shift+capslock+ralt:                'j'
 }
 
 key K {
     label:                              '\u041b'
     base:                               '\u043b'
     shift, capslock:                    '\u041b'
+    shift+capslock:                     '\u043b'
     ralt:                               'k'
-    ralt+shift, ralt+capslock:          'K'
+    shift+ralt, capslock+ralt:          'K'
+    shift+capslock+ralt:                'k'
 }
 
 key L {
     label:                              '\u0414'
     base:                               '\u0434'
     shift, capslock:                    '\u0414'
+    shift+capslock:                     '\u0434'
     ralt:                               'l'
-    ralt+shift, ralt+capslock:          'L'
+    shift+ralt, capslock+ralt:          'L'
+    shift+capslock+ralt:                'l'
 }
 
 key SEMICOLON {
     label:                              '\u0416'
     base:                               '\u0436'
     shift, capslock:                    '\u0416'
+    shift+capslock:                     '\u0436'
     ralt:                               ';'
     ralt+shift:                         ':'
 }
@@ -302,6 +344,7 @@
     label:                              '\u042d'
     base:                               '\u044d'
     shift, capslock:                    '\u042d'
+    shift+capslock:                     '\u044d'
     ralt:                               '\''
     ralt+shift:                         '"'
 }
@@ -319,62 +362,77 @@
     label:                              '\u042f'
     base:                               '\u044f'
     shift, capslock:                    '\u042f'
+    shift+capslock:                     '\u044f'
     ralt:                               'z'
-    ralt+shift, ralt+capslock:          'Z'
+    shift+ralt, capslock+ralt:          'Z'
+    shift+capslock+ralt:                'z'
 }
 
 key X {
     label:                              '\u0427'
     base:                               '\u0447'
     shift, capslock:                    '\u0427'
+    shift+capslock:                     '\u0447'
     ralt:                               'x'
-    ralt+shift, ralt+capslock:          'X'
+    shift+ralt, capslock+ralt:          'X'
+    shift+capslock+ralt:                'x'
 }
 
 key C {
     label:                              '\u0421'
     base:                               '\u0441'
     shift, capslock:                    '\u0421'
+    shift+capslock:                     '\u0441'
     ralt:                               'c'
-    ralt+shift, ralt+capslock:          'C'
+    shift+ralt, capslock+ralt:          'C'
+    shift+capslock+ralt:                'c'
 }
 
 key V {
     label:                              '\u041c'
     base:                               '\u043c'
     shift, capslock:                    '\u041c'
+    shift+capslock:                     '\u043c'
     ralt:                               'v'
-    ralt+shift, ralt+capslock:          'V'
+    shift+ralt, capslock+ralt:          'V'
+    shift+capslock+ralt:                'v'
 }
 
 key B {
     label:                              '\u0418'
     base:                               '\u0438'
     shift, capslock:                    '\u0418'
+    shift+capslock:                     '\u0438'
     ralt:                               'b'
-    ralt+shift, ralt+capslock:          'B'
+    shift+ralt, capslock+ralt:          'B'
+    shift+capslock+ralt:                'b'
 }
 
 key N {
     label:                              '\u0422'
     base:                               '\u0442'
     shift, capslock:                    '\u0422'
+    shift+capslock:                     '\u0442'
     ralt:                               'n'
-    ralt+shift, ralt+capslock:          'N'
+    shift+ralt, capslock+ralt:          'N'
+    shift+capslock+ralt:                'n'
 }
 
 key M {
     label:                              '\u042c'
     base:                               '\u044c'
     shift, capslock:                    '\u042c'
+    shift+capslock:                     '\u044c'
     ralt:                               'm'
-    ralt+shift, ralt+capslock:          'M'
+    shift+ralt, capslock+ralt:          'M'
+    shift+capslock+ralt:                'm'
 }
 
 key COMMA {
     label:                              '\u0411'
     base:                               '\u0431'
     shift, capslock:                    '\u0411'
+    shift+capslock:                     '\u0431'
     ralt:                               ','
     ralt+shift:                         '<'
 }
@@ -383,6 +441,7 @@
     label:                              '\u042e'
     base:                               '\u044e'
     shift, capslock:                    '\u042e'
+    shift+capslock:                     '\u044e'
     ralt:                               '.'
     ralt+shift:                         '>'
 }
diff --git a/packages/InputDevices/res/raw/keyboard_layout_russian_mac.kcm b/packages/InputDevices/res/raw/keyboard_layout_russian_mac.kcm
index 11c2ad4..5417bc3 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_russian_mac.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_russian_mac.kcm
@@ -126,86 +126,107 @@
     label:                              '\u0419'
     base:                               '\u0439'
     shift, capslock:                    '\u0419'
+    shift+capslock:                     '\u0439'
     ralt:                               'q'
-    ralt+shift, ralt+capslock:          'Q'
+    shift+ralt, capslock+ralt:          'Q'
+    shift+capslock+ralt:                'q'
 }
 
 key W {
     label:                              '\u0426'
     base:                               '\u0446'
     shift, capslock:                    '\u0426'
+    shift+capslock:                     '\u0446'
     ralt:                               'w'
-    ralt+shift, ralt+capslock:          'W'
+    shift+ralt, capslock+ralt:          'W'
+    shift+capslock+ralt:                'w'
 }
 
 key E {
     label:                              '\u0423'
     base:                               '\u0443'
     shift, capslock:                    '\u0423'
+    shift+capslock:                     '\u0443'
     ralt:                               'e'
-    ralt+shift, ralt+capslock:          'E'
+    shift+ralt, capslock+ralt:          'E'
+    shift+capslock+ralt:                'e'
 }
 
 key R {
     label:                              '\u041a'
     base:                               '\u043a'
     shift, capslock:                    '\u041a'
+    shift+capslock:                     '\u043a'
     ralt:                               'r'
-    ralt+shift, ralt+capslock:          'R'
+    shift+ralt, capslock+ralt:          'R'
+    shift+capslock+ralt:                'r'
 }
 
 key T {
     label:                              '\u0415'
     base:                               '\u0435'
     shift, capslock:                    '\u0415'
+    shift+capslock:                     '\u0435'
     ralt:                               't'
-    ralt+shift, ralt+capslock:          'T'
+    shift+ralt, capslock+ralt:          'T'
+    shift+capslock+ralt:                't'
 }
 
 key Y {
     label:                              '\u041d'
     base:                               '\u043d'
     shift, capslock:                    '\u041d'
+    shift+capslock:                     '\u043d'
     ralt:                               'y'
-    ralt+shift, ralt+capslock:          'Y'
+    shift+ralt, capslock+ralt:          'Y'
+    shift+capslock+ralt:                'y'
 }
 
 key U {
     label:                              '\u0413'
     base:                               '\u0433'
     shift, capslock:                    '\u0413'
+    shift+capslock:                     '\u0433'
     ralt:                               'u'
-    ralt+shift, ralt+capslock:          'U'
+    shift+ralt, capslock+ralt:          'U'
+    shift+capslock+ralt:                'u'
 }
 
 key I {
     label:                              '\u0428'
     base:                               '\u0448'
     shift, capslock:                    '\u0428'
+    shift+capslock:                     '\u0448'
     ralt:                               'i'
-    ralt+shift, ralt+capslock:          'I'
+    shift+ralt, capslock+ralt:          'I'
+    shift+capslock+ralt:                'i'
 }
 
 key O {
     label:                              '\u0429'
     base:                               '\u0449'
     shift, capslock:                    '\u0429'
+    shift+capslock:                     '\u0449'
     ralt:                               'o'
-    ralt+shift, ralt+capslock:          'O'
+    shift+ralt, capslock+ralt:          'O'
+    shift+capslock+ralt:                'o'
 }
 
 key P {
     label:                              '\u0417'
     base:                               '\u0437'
     shift, capslock:                    '\u0417'
+    shift+capslock:                     '\u0437'
     ralt:                               'p'
-    ralt+shift, ralt+capslock:          'P'
+    shift+ralt, capslock+ralt:          'P'
+    shift+capslock+ralt:                'p'
 }
 
 key LEFT_BRACKET {
     label:                              '\u0425'
     base:                               '\u0445'
     shift, capslock:                    '\u0425'
+    shift+capslock:                     '\u0445'
     ralt:                               '['
     ralt+shift:                         '{'
 }
@@ -214,6 +235,7 @@
     label:                              '\u042a'
     base:                               '\u044a'
     shift, capslock:                    '\u042a'
+    shift+capslock:                     '\u044a'
     ralt:                               ']'
     ralt+shift:                         '}'
 }
@@ -224,78 +246,97 @@
     label:                              '\u0424'
     base:                               '\u0444'
     shift, capslock:                    '\u0424'
+    shift+capslock:                     '\u0444'
     ralt:                               'a'
-    ralt+shift, ralt+capslock:          'A'
+    shift+ralt, capslock+ralt:          'A'
+    shift+capslock+ralt:                'a'
 }
 
 key S {
     label:                              '\u042b'
     base:                               '\u044b'
     shift, capslock:                    '\u042b'
+    shift+capslock:                     '\u044b'
     ralt:                               's'
-    ralt+shift, ralt+capslock:          'S'
+    shift+ralt, capslock+ralt:          'S'
+    shift+capslock+ralt:                's'
 }
 
 key D {
     label:                              '\u0412'
     base:                               '\u0432'
     shift, capslock:                    '\u0412'
+    shift+capslock:                     '\u0432'
     ralt:                               'd'
-    ralt+shift, ralt+capslock:          'D'
+    shift+ralt, capslock+ralt:          'D'
+    shift+capslock+ralt:                'd'
 }
 
 key F {
     label:                              '\u0410'
     base:                               '\u0430'
     shift, capslock:                    '\u0410'
+    shift+capslock:                     '\u0430'
     ralt:                               'f'
-    ralt+shift, ralt+capslock:          'F'
+    shift+ralt, capslock+ralt:          'F'
+    shift+capslock+ralt:                'f'
 }
 
 key G {
     label:                              '\u041f'
     base:                               '\u043f'
     shift, capslock:                    '\u041f'
+    shift+capslock:                     '\u043f'
     ralt:                               'g'
-    ralt+shift, ralt+capslock:          'G'
+    shift+ralt, capslock+ralt:          'G'
+    shift+capslock+ralt:                'g'
 }
 
 key H {
     label:                              '\u0420'
     base:                               '\u0440'
     shift, capslock:                    '\u0420'
+    shift+capslock:                     '\u0440'
     ralt:                               'h'
-    ralt+shift, ralt+capslock:          'H'
+    shift+ralt, capslock+ralt:          'H'
+    shift+capslock+ralt:                'h'
 }
 
 key J {
     label:                              '\u041e'
     base:                               '\u043e'
     shift, capslock:                    '\u041e'
+    shift+capslock:                     '\u043e'
     ralt:                               'j'
-    ralt+shift, ralt+capslock:          'J'
+    shift+ralt, capslock+ralt:          'J'
+    shift+capslock+ralt:                'j'
 }
 
 key K {
     label:                              '\u041b'
     base:                               '\u043b'
     shift, capslock:                    '\u041b'
+    shift+capslock:                     '\u043b'
     ralt:                               'k'
-    ralt+shift, ralt+capslock:          'K'
+    shift+ralt, capslock+ralt:          'K'
+    shift+capslock+ralt:                'k'
 }
 
 key L {
     label:                              '\u0414'
     base:                               '\u0434'
     shift, capslock:                    '\u0414'
+    shift+capslock:                     '\u0434'
     ralt:                               'l'
-    ralt+shift, ralt+capslock:          'L'
+    shift+ralt, capslock+ralt:          'L'
+    shift+capslock+ralt:                'l'
 }
 
 key SEMICOLON {
     label:                              '\u0416'
     base:                               '\u0436'
     shift, capslock:                    '\u0416'
+    shift+capslock:                     '\u0436'
     ralt:                               ';'
     ralt+shift:                         ':'
 }
@@ -304,6 +345,7 @@
     label:                              '\u042d'
     base:                               '\u044d'
     shift, capslock:                    '\u042d'
+    shift+capslock:                     '\u044d'
     ralt:                               '\''
     ralt+shift:                         '"'
 }
@@ -312,6 +354,7 @@
     label:                              '\u0401'
     base:                               '\u0451'
     shift, capslock:                    '\u0401'
+    shift+capslock:                     '\u0451'
     ralt:                               '\\'
     ralt+shift:                         '|'
 }
@@ -330,62 +373,77 @@
     label:                              '\u042f'
     base:                               '\u044f'
     shift, capslock:                    '\u042f'
+    shift+capslock:                     '\u044f'
     ralt:                               'z'
-    ralt+shift, ralt+capslock:          'Z'
+    shift+ralt, capslock+ralt:          'Z'
+    shift+capslock+ralt:                'z'
 }
 
 key X {
     label:                              '\u0427'
     base:                               '\u0447'
     shift, capslock:                    '\u0427'
+    shift+capslock:                     '\u0447'
     ralt:                               'x'
-    ralt+shift, ralt+capslock:          'X'
+    shift+ralt, capslock+ralt:          'X'
+    shift+capslock+ralt:                'x'
 }
 
 key C {
     label:                              '\u0421'
     base:                               '\u0441'
     shift, capslock:                    '\u0421'
+    shift+capslock:                     '\u0441'
     ralt:                               'c'
-    ralt+shift, ralt+capslock:          'C'
+    shift+ralt, capslock+ralt:          'C'
+    shift+capslock+ralt:                'c'
 }
 
 key V {
     label:                              '\u041c'
     base:                               '\u043c'
     shift, capslock:                    '\u041c'
+    shift+capslock:                     '\u043c'
     ralt:                               'v'
-    ralt+shift, ralt+capslock:          'V'
+    shift+ralt, capslock+ralt:          'V'
+    shift+capslock+ralt:                'v'
 }
 
 key B {
     label:                              '\u0418'
     base:                               '\u0438'
     shift, capslock:                    '\u0418'
+    shift+capslock:                     '\u0438'
     ralt:                               'b'
-    ralt+shift, ralt+capslock:          'B'
+    shift+ralt, capslock+ralt:          'B'
+    shift+capslock+ralt:                'b'
 }
 
 key N {
     label:                              '\u0422'
     base:                               '\u0442'
     shift, capslock:                    '\u0422'
+    shift+capslock:                     '\u0442'
     ralt:                               'n'
-    ralt+shift, ralt+capslock:          'N'
+    shift+ralt, capslock+ralt:          'N'
+    shift+capslock+ralt:                'n'
 }
 
 key M {
     label:                              '\u042c'
     base:                               '\u044c'
     shift, capslock:                    '\u042c'
+    shift+capslock:                     '\u044c'
     ralt:                               'm'
-    ralt+shift, ralt+capslock:          'M'
+    shift+ralt, capslock+ralt:          'M'
+    shift+capslock+ralt:                'm'
 }
 
 key COMMA {
     label:                              '\u0411'
     base:                               '\u0431'
     shift, capslock:                    '\u0411'
+    shift+capslock:                     '\u0431'
     ralt:                               ','
     ralt+shift:                         '<'
 }
@@ -394,6 +452,7 @@
     label:                              '\u042e'
     base:                               '\u044e'
     shift, capslock:                    '\u042e'
+    shift+capslock:                     '\u044e'
     ralt:                               '.'
     ralt+shift:                         '>'
 }
diff --git a/packages/InputDevices/res/raw/keyboard_layout_slovak.kcm b/packages/InputDevices/res/raw/keyboard_layout_slovak.kcm
index 2eb0f63..5065aa8 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_slovak.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_slovak.kcm
@@ -118,6 +118,7 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
     ralt:                               '\\'
 }
 
@@ -125,6 +126,7 @@
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
     ralt:                               '|'
 }
 
@@ -132,6 +134,7 @@
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
 }
 
@@ -139,42 +142,49 @@
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
     ralt:                               '\''
 }
 
@@ -198,12 +208,14 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
     ralt:                               '\u0111'
 }
 
@@ -211,6 +223,7 @@
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
     ralt:                               '\u0110'
 }
 
@@ -218,6 +231,7 @@
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
     ralt:                               '['
 }
 
@@ -225,6 +239,7 @@
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
     ralt:                               ']'
 }
 
@@ -232,18 +247,21 @@
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
     ralt:                               '\u0142'
 }
 
@@ -251,6 +269,7 @@
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
     ralt:                               '\u0141'
 }
 
@@ -288,6 +307,7 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
     ralt:                               '>'
 }
 
@@ -295,6 +315,7 @@
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
     ralt:                               '#'
 }
 
@@ -302,6 +323,7 @@
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
     ralt:                               '&'
 }
 
@@ -309,6 +331,7 @@
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
     ralt:                               '@'
 }
 
@@ -316,6 +339,7 @@
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
     ralt:                               '{'
 }
 
@@ -323,6 +347,7 @@
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
     ralt:                               '}'
 }
 
@@ -330,6 +355,7 @@
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_spanish.kcm b/packages/InputDevices/res/raw/keyboard_layout_spanish.kcm
index da9159b..6a63e70 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_spanish.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_spanish.kcm
@@ -113,18 +113,21 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
 }
 
 key W {
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
 }
 
@@ -132,42 +135,49 @@
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
@@ -190,60 +200,70 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
     label:                              '\u00d1'
     base:                               '\u00f1'
     shift, capslock:                    '\u00d1'
+    shift+capslock:                     '\u00f1'
 }
 
 key APOSTROPHE {
@@ -257,6 +277,7 @@
     label:                              '\u00c7'
     base:                               '\u00e7'
     shift, capslock:                    '\u00c7'
+    shift+capslock:                     '\u00e7'
     ralt:                               '}'
 }
 
@@ -272,42 +293,49 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_spanish_latin.kcm b/packages/InputDevices/res/raw/keyboard_layout_spanish_latin.kcm
index 16eb53f..29aab97 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_spanish_latin.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_spanish_latin.kcm
@@ -109,6 +109,7 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
     ralt:                               '@'
 }
 
@@ -116,54 +117,63 @@
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
 }
 
 key R {
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
@@ -186,60 +196,70 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
     label:                              '\u00d1'
     base:                               '\u00f1'
     shift, capslock:                    '\u00d1'
+    shift+capslock:                     '\u00f1'
 }
 
 key APOSTROPHE {
@@ -268,42 +288,49 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_swedish.kcm b/packages/InputDevices/res/raw/keyboard_layout_swedish.kcm
index 8a4e9a5..f12804f 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_swedish.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_swedish.kcm
@@ -115,76 +115,90 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
     ralt:                               '\u00e2'
-    ralt+capslock, shift+ralt:          '\u00c2'
+    shift+ralt, capslock+ralt:          '\u00c2'
+    shift+capslock+ralt:                '\u00e2'
 }
 
 key W {
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
-    ralt+capslock:                      '\u20ac'
 }
 
 key R {
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
     ralt:                               '\u0167'
-    ralt+capslock, shift+ralt:          '\u0166'
+    shift+ralt, capslock+ralt:          '\u0166'
+    shift+capslock+ralt:                '\u0167'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
     ralt:                               '\u00ef'
-    ralt+capslock, shift+ralt:          '\u00cf'
+    shift+ralt, capslock+ralt:          '\u00cf'
+    shift+capslock+ralt:                '\u00ef'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
     ralt:                               '\u00f5'
-    ralt+capslock, shift+ralt:          '\u00d5'
+    shift+ralt, capslock+ralt:          '\u00d5'
+    shift+capslock+ralt:                '\u00f5'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
     label:                              '\u00c5'
     base:                               '\u00e5'
     shift, capslock:                    '\u00c5'
+    shift+capslock:                     '\u00e5'
 }
 
 key RIGHT_BRACKET {
@@ -200,84 +214,104 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
     ralt:                               '\u00e1'
-    ralt+capslock, shift+ralt:          '\u00c1'
+    shift+ralt, capslock+ralt:          '\u00c1'
+    shift+capslock+ralt:                '\u00e1'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
     ralt:                               '\u0161'
-    ralt+capslock, shift+ralt:          '\u0160'
+    shift+ralt, capslock+ralt:          '\u0160'
+    shift+capslock+ralt:                '\u0161'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
     ralt:                               '\u0111'
-    ralt+capslock, shift+ralt:          '\u0110'
+    shift+ralt, capslock+ralt:          '\u0110'
+    shift+capslock+ralt:                '\u0111'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
     ralt:                               '\u01e5'
-    ralt+capslock, shift+ralt:          '\u01e4'
+    shift+ralt, capslock+ralt:          '\u01e4'
+    shift+capslock+ralt:                '\u01e5'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
     ralt:                               '\u01e7'
-    ralt+capslock, shift+ralt:          '\u01e6'
+    shift+ralt, capslock+ralt:          '\u01e6'
+    shift+capslock+ralt:                '\u01e7'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
     ralt:                               '\u021f'
-    ralt+capslock, shift+ralt:          '\u021e'
+    shift+ralt, capslock+ralt:          '\u021e'
+    shift+capslock+ralt:                '\u021f'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
     ralt:                               '\u01e9'
-    ralt+capslock, shift+ralt:          '\u01e8'
+    shift+ralt, capslock+ralt:          '\u01e8'
+    shift+capslock+ralt:                '\u01e9'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
     label:                              '\u00d6'
     base:                               '\u00f6'
     shift, capslock:                    '\u00d6'
+    shift+capslock:                     '\u00f6'
     ralt:                               '\u00f8'
-    ralt+capslock, shift+ralt:          '\u00d8'
+    shift+ralt, capslock+ralt:          '\u00d8'
+    shift+capslock+ralt:                '\u00f8'
 }
 
 key APOSTROPHE {
     label:                              '\u00c4'
     base:                               '\u00e4'
     shift, capslock:                    '\u00c4'
+    shift+capslock:                     '\u00e4'
     ralt:                               '\u00e6'
-    ralt+capslock, shift+ralt:          '\u00c6'
+    shift+ralt, capslock+ralt:          '\u00c6'
+    shift+capslock+ralt:                '\u00e6'
 }
 
 key BACKSLASH {
@@ -299,53 +333,65 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
     ralt:                               '\u017e'
-    ralt+capslock, shift+ralt:          '\u017d'
+    shift+ralt, capslock+ralt:          '\u017d'
+    shift+capslock+ralt:                '\u017e'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
     ralt:                               '\u010d'
-    ralt+capslock, shift+ralt:          '\u010c'
+    shift+ralt, capslock+ralt:          '\u010c'
+    shift+capslock+ralt:                '\u010d'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
     ralt:                               '\u01ef'
-    ralt+capslock, shift+ralt:          '\u01ee'
+    shift+ralt, capslock+ralt:          '\u01ee'
+    shift+capslock+ralt:                '\u01ef'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
     ralt:                               '\u0292'
-    ralt+capslock, shift+ralt:          '\u01b7'
+    shift+ralt, capslock+ralt:          '\u01b7'
+    shift+capslock+ralt:                '\u0292'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
     ralt:                               '\u014b'
-    ralt+capslock, shift+ralt:          '\u014a'
+    shift+ralt, capslock+ralt:          '\u014a'
+    shift+capslock+ralt:                '\u014b'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
-    ralt, ralt+capslock:                '\u00b5'
+    shift+capslock:                     'm'
+    ralt:                               '\u00b5'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_swiss_french.kcm b/packages/InputDevices/res/raw/keyboard_layout_swiss_french.kcm
index 9e20462..6476793 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_swiss_french.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_swiss_french.kcm
@@ -119,18 +119,21 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
 }
 
 key W {
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
 }
 
@@ -138,42 +141,49 @@
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Z {
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
@@ -196,54 +206,63 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
@@ -279,42 +298,49 @@
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_swiss_german.kcm b/packages/InputDevices/res/raw/keyboard_layout_swiss_german.kcm
index 7fbd1a9..9d6f367 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_swiss_german.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_swiss_german.kcm
@@ -119,18 +119,21 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
 }
 
 key W {
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
 }
 
@@ -138,42 +141,49 @@
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Z {
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               'i'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
@@ -198,54 +208,63 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
 }
 
 key D {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
@@ -285,42 +304,49 @@
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_turkish.kcm b/packages/InputDevices/res/raw/keyboard_layout_turkish.kcm
index e193d34..2a8fcef 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_turkish.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_turkish.kcm
@@ -124,6 +124,7 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
     ralt:                               '@'
 }
 
@@ -131,12 +132,14 @@
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
 }
 
 key E {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
 }
 
@@ -144,50 +147,59 @@
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key T {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
 }
 
 key Y {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
 }
 
 key U {
     label:                              'U'
     base:                               'u'
     shift, capslock:                    'U'
+    shift+capslock:                     'u'
 }
 
 key I {
     label:                              'I'
     base:                               '\u0131'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
     ralt:                               'i'
-    ralt+shift, ralt+capslock:          '\u0130'
+    shift+ralt, capslock+ralt:          '\u0130'
+    shift+capslock+ralt:                'i'
 }
 
 key O {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
 }
 
 key LEFT_BRACKET {
     label:                              '\u011e'
     base:                               '\u011f'
     shift, capslock:                    '\u011e'
+    shift+capslock:                     '\u011f'
     ralt:                               '\u0308'
 }
 
@@ -195,6 +207,7 @@
     label:                              '\u00dc'
     base:                               '\u00fc'
     shift, capslock:                    '\u00dc'
+    shift+capslock:                     '\u00fc'
     ralt:                               '\u0303'
 }
 
@@ -204,14 +217,17 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
     ralt:                               '\u00e6'
-    ralt+shift, ralt+capslock:          '\u00c6'
+    shift+ralt, capslock+ralt:          '\u00c6'
+    shift+capslock+ralt:                '\u00e6'
 }
 
 key S {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
     ralt:                               '\u00df'
 }
 
@@ -219,48 +235,56 @@
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
 }
 
 key F {
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
 }
 
 key G {
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key H {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
 }
 
 key J {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
 }
 
 key K {
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
     label:                              '\u015e'
     base:                               '\u015f'
     shift, capslock:                    '\u015e'
+    shift+capslock:                     '\u015f'
     ralt:                               '\u0301'
 }
 
@@ -268,6 +292,7 @@
     label:                              '\u0130'
     base:                               'i'
     shift, capslock:                    '\u0130'
+    shift+capslock:                     'i'
 }
 
 key COMMA {
@@ -290,54 +315,63 @@
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key X {
     label:                              'X'
     base:                               'x'
     shift, capslock:                    'X'
+    shift+capslock:                     'x'
 }
 
 key C {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key V {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
 }
 
 key B {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
 }
 
 key N {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key M {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key EQUALS {
     label:                              '\u00d6'
     base:                               '\u00f6'
     shift, capslock:                    '\u00d6'
+    shift+capslock:                     '\u00f6'
 }
 
 key BACKSLASH {
     label:                              '\u00c7'
     base:                               '\u00e7'
     shift, capslock:                    '\u00c7'
+    shift+capslock:                     '\u00e7'
 }
 
 key PERIOD {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_turkish_f.kcm b/packages/InputDevices/res/raw/keyboard_layout_turkish_f.kcm
index 5b96da0..b27f6fa 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_turkish_f.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_turkish_f.kcm
@@ -125,6 +125,7 @@
     label:                              'F'
     base:                               'f'
     shift, capslock:                    'F'
+    shift+capslock:                     'f'
     ralt:                               '@'
 }
 
@@ -132,32 +133,38 @@
     label:                              'G'
     base:                               'g'
     shift, capslock:                    'G'
+    shift+capslock:                     'g'
 }
 
 key E {
     label:                              '\u011f'
     base:                               '\u011f'
     shift, capslock:                    '\u011e'
+    shift+capslock:                     '\u011f'
 }
 
 key R {
     label:                              '\u0131'
     base:                               '\u0131'
     shift, capslock:                    'I'
+    shift+capslock:                     'i'
     ralt:                               '\u00b6'
-    ralt+shift, ralt+capslock:          '\u00ae'
+    shift+ralt, capslock+ralt:          '\u00ae'
+    shift+capslock+ralt:                '\u00b6'
 }
 
 key T {
     label:                              'O'
     base:                               'o'
     shift, capslock:                    'O'
+    shift+capslock:                     'o'
 }
 
 key Y {
     label:                              'D'
     base:                               'd'
     shift, capslock:                    'D'
+    shift+capslock:                     'd'
     ralt:                               '\u00a5'
 }
 
@@ -165,26 +172,31 @@
     label:                              'R'
     base:                               'r'
     shift, capslock:                    'R'
+    shift+capslock:                     'r'
 }
 
 key I {
     label:                              'N'
     base:                               'n'
     shift, capslock:                    'N'
+    shift+capslock:                     'n'
 }
 
 key O {
     label:                              'H'
     base:                               'h'
     shift, capslock:                    'H'
+    shift+capslock:                     'h'
     ralt:                               '\u00f8'
-    ralt+shift, ralt+capslock:          '\u00d8'
+    shift+ralt, capslock+ralt:          '\u00d8'
+    shift+capslock+ralt:                '\u00f8'
 }
 
 key P {
     label:                              'P'
     base:                               'p'
     shift, capslock:                    'P'
+    shift+capslock:                     'p'
     ralt:                               '\u00a3'
 }
 
@@ -192,6 +204,7 @@
     label:                              'Q'
     base:                               'q'
     shift, capslock:                    'Q'
+    shift+capslock:                     'q'
     ralt:                               '"'
 }
 
@@ -199,6 +212,7 @@
     label:                              'W'
     base:                               'w'
     shift, capslock:                    'W'
+    shift+capslock:                     'w'
     ralt:                               '~'
 }
 
@@ -208,22 +222,27 @@
     label:                              '\u0075'
     base:                               '\u0075'
     shift, capslock:                    '\u0055'
+    shift+capslock:                     '\u0075'
     ralt:                               '\u00e6'
-    ralt+shift, ralt+capslock:          '\u00c6'
+    shift+ralt, capslock+ralt:          '\u00c6'
+    shift+capslock+ralt:                '\u00e6'
 }
 
 key S {
     label:                              'i'
     base:                               'i'
     shift, capslock:                    '\u0130'
+    shift+capslock:                     'i'
     ralt:                               '\u00df'
-    ralt+shift, ralt+capslock:          '\u00a7'
+    shift+ralt, capslock+ralt:          '\u00a7'
+    shift+capslock+ralt:                '\u00df'
 }
 
 key D {
     label:                              'E'
     base:                               'e'
     shift, capslock:                    'E'
+    shift+capslock:                     'e'
     ralt:                               '\u20ac'
 }
 
@@ -231,6 +250,7 @@
     label:                              'A'
     base:                               'a'
     shift, capslock:                    'A'
+    shift+capslock:                     'a'
     ralt:                               '\u00aa'
 }
 
@@ -238,12 +258,14 @@
     label:                              '\u00fc'
     base:                               '\u00fc'
     shift, capslock:                    '\u00dc'
+    shift+capslock:                     '\u00fc'
 }
 
 key H {
     label:                              'T'
     base:                               't'
     shift, capslock:                    'T'
+    shift+capslock:                     't'
     ralt:                               '\u20ba'
 }
 
@@ -251,24 +273,28 @@
     label:                              'K'
     base:                               'k'
     shift, capslock:                    'K'
+    shift+capslock:                     'k'
 }
 
 key K {
     label:                              'M'
     base:                               'm'
     shift, capslock:                    'M'
+    shift+capslock:                     'm'
 }
 
 key L {
     label:                              'L'
     base:                               'l'
     shift, capslock:                    'L'
+    shift+capslock:                     'l'
 }
 
 key SEMICOLON {
     label:                              'Y'
     base:                               'y'
     shift, capslock:                    'Y'
+    shift+capslock:                     'y'
     ralt:                               '\u00b4'
 }
 
@@ -276,6 +302,7 @@
     label:                              '\u015f'
     base:                               '\u015f'
     shift, capslock:                    '\u015e'
+    shift+capslock:                     '\u015f'
 }
 
 key COMMA {
@@ -292,63 +319,76 @@
     base:                               '<'
     shift:                              '>'
     ralt:                               '|'
-    ralt+shift, ralt+capslock:          '\u00a6'
+    shift+ralt, capslock+ralt:          '\u00a6'
+    shift+capslock+ralt:                '|'
 }
 
 key Z {
     label:                              'J'
     base:                               'j'
     shift, capslock:                    'J'
+    shift+capslock:                     'j'
     ralt:                               '\u00ab'
-    ralt+shift, ralt+capslock:          '<'
+    shift+ralt, capslock+ralt:          '<'
+    shift+capslock+ralt:                '\u00ab'
 }
 
 key X {
     label:                              '\u00f6'
     base:                               '\u00f6'
     shift, capslock:                    '\u00d6'
+    shift+capslock:                     '\u00f6'
     ralt:                               '\u00bb'
-    ralt+shift, ralt+capslock:          '>'
+    shift+ralt, capslock+ralt:          '>'
+    shift+capslock+ralt:                '\u00bb'
 }
 
 key C {
     label:                              'V'
     base:                               'v'
     shift, capslock:                    'V'
+    shift+capslock:                     'v'
     ralt:                               '\u00a2'
-    ralt+shift, ralt+capslock:          '\u00a9'
+    shift+ralt, capslock+ralt:          '\u00a9'
+    shift+capslock+ralt:                '\u00a2'
 }
 
 key V {
     label:                              'C'
     base:                               'c'
     shift, capslock:                    'C'
+    shift+capslock:                     'c'
 }
 
 key B {
     label:                              '\u00e7'
     base:                               '\u00e7'
     shift, capslock:                    '\u00c7'
+    shift+capslock:                     '\u00e7'
 }
 
 key N {
     label:                              'Z'
     base:                               'z'
     shift, capslock:                    'Z'
+    shift+capslock:                     'z'
 }
 
 key M {
     label:                              'S'
     base:                               's'
     shift, capslock:                    'S'
+    shift+capslock:                     's'
     ralt:                               '\u00b5'
-    ralt+shift, ralt+capslock:          '\u00ba'
+    shift+ralt, capslock+ralt:          '\u00ba'
+    shift+capslock+ralt:                '\u00b5'
 }
 
 key EQUALS {
     label:                              'B'
     base:                               'b'
     shift, capslock:                    'B'
+    shift+capslock:                     'b'
     ralt:                               '\u00d7'
 }
 
@@ -356,6 +396,7 @@
     label:                              '.'
     base:                               '.'
     shift, capslock:                    ':'
+    shift+capslock:                     ':'
     ralt:                               '\u00f7'
 }
 
diff --git a/packages/InputDevices/res/raw/keyboard_layout_ukrainian.kcm b/packages/InputDevices/res/raw/keyboard_layout_ukrainian.kcm
index a802460..1346bbb 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_ukrainian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_ukrainian.kcm
@@ -28,6 +28,7 @@
     label:                              '\u0401'
     base:                               '\u0451'
     shift, capslock:                    '\u0401'
+    shift+capslock:                     '\u0451'
     ralt:                               '`'
     ralt+shift:                         '~'
 }
@@ -124,86 +125,107 @@
     label:                              '\u0419'
     base:                               '\u0439'
     shift, capslock:                    '\u0419'
+    shift+capslock:                     '\u0439'
     ralt:                               'q'
-    ralt+shift, ralt+capslock:          'Q'
+    shift+ralt, capslock+ralt:          'Q'
+    shift+capslock+ralt:                'q'
 }
 
 key W {
     label:                              '\u0426'
     base:                               '\u0446'
     shift, capslock:                    '\u0426'
+    shift+capslock:                     '\u0446'
     ralt:                               'w'
-    ralt+shift, ralt+capslock:          'W'
+    shift+ralt, capslock+ralt:          'W'
+    shift+capslock+ralt:                'w'
 }
 
 key E {
     label:                              '\u0423'
     base:                               '\u0443'
     shift, capslock:                    '\u0423'
+    shift+capslock:                     '\u0443'
     ralt:                               'e'
-    ralt+shift, ralt+capslock:          'E'
+    shift+ralt, capslock+ralt:          'E'
+    shift+capslock+ralt:                'e'
 }
 
 key R {
     label:                              '\u041a'
     base:                               '\u043a'
     shift, capslock:                    '\u041a'
+    shift+capslock:                     '\u043a'
     ralt:                               'r'
-    ralt+shift, ralt+capslock:          'R'
+    shift+ralt, capslock+ralt:          'R'
+    shift+capslock+ralt:                'r'
 }
 
 key T {
     label:                              '\u0415'
     base:                               '\u0435'
     shift, capslock:                    '\u0415'
+    shift+capslock:                     '\u0435'
     ralt:                               't'
-    ralt+shift, ralt+capslock:          'T'
+    shift+ralt, capslock+ralt:          'T'
+    shift+capslock+ralt:                't'
 }
 
 key Y {
     label:                              '\u041d'
     base:                               '\u043d'
     shift, capslock:                    '\u041d'
+    shift+capslock:                     '\u043d'
     ralt:                               'y'
-    ralt+shift, ralt+capslock:          'Y'
+    shift+ralt, capslock+ralt:          'Y'
+    shift+capslock+ralt:                'y'
 }
 
 key U {
     label:                              '\u0413'
     base:                               '\u0433'
     shift, capslock:                    '\u0413'
+    shift+capslock:                     '\u0433'
     ralt:                               'u'
-    ralt+shift, ralt+capslock:          'U'
+    shift+ralt, capslock+ralt:          'U'
+    shift+capslock+ralt:                'u'
 }
 
 key I {
     label:                              '\u0428'
     base:                               '\u0448'
     shift, capslock:                    '\u0428'
+    shift+capslock:                     '\u0448'
     ralt:                               'i'
-    ralt+shift, ralt+capslock:          'I'
+    shift+ralt, capslock+ralt:          'I'
+    shift+capslock+ralt:                'i'
 }
 
 key O {
     label:                              '\u0429'
     base:                               '\u0449'
     shift, capslock:                    '\u0429'
+    shift+capslock:                     '\u0449'
     ralt:                               'o'
-    ralt+shift, ralt+capslock:          'O'
+    shift+ralt, capslock+ralt:          'O'
+    shift+capslock+ralt:                'o'
 }
 
 key P {
     label:                              '\u0417'
     base:                               '\u0437'
     shift, capslock:                    '\u0417'
+    shift+capslock:                     '\u0437'
     ralt:                               'p'
-    ralt+shift, ralt+capslock:          'P'
+    shift+ralt, capslock+ralt:          'P'
+    shift+capslock+ralt:                'p'
 }
 
 key LEFT_BRACKET {
     label:                              '\u0425'
     base:                               '\u0445'
     shift, capslock:                    '\u0425'
+    shift+capslock:                     '\u0445'
     ralt:                               '['
     ralt+shift:                         '{'
 }
@@ -212,6 +234,7 @@
     label:                              '\u0407'
     base:                               '\u0457'
     shift, capslock:                    '\u0407'
+    shift+capslock:                     '\u0457'
     ralt:                               ']'
     ralt+shift:                         '}'
 }
@@ -222,78 +245,97 @@
     label:                              '\u0424'
     base:                               '\u0444'
     shift, capslock:                    '\u0424'
+    shift+capslock:                     '\u0444'
     ralt:                               'a'
-    ralt+shift, ralt+capslock:          'A'
+    shift+ralt, capslock+ralt:          'A'
+    shift+capslock+ralt:                'a'
 }
 
 key S {
     label:                              '\u0406'
     base:                               '\u0456'
     shift, capslock:                    '\u0406'
+    shift+capslock:                     '\u0456'
     ralt:                               's'
-    ralt+shift, ralt+capslock:          'S'
+    shift+ralt, capslock+ralt:          'S'
+    shift+capslock+ralt:                's'
 }
 
 key D {
     label:                              '\u0412'
     base:                               '\u0432'
     shift, capslock:                    '\u0412'
+    shift+capslock:                     '\u0432'
     ralt:                               'd'
-    ralt+shift, ralt+capslock:          'D'
+    shift+ralt, capslock+ralt:          'D'
+    shift+capslock+ralt:                'd'
 }
 
 key F {
     label:                              '\u0410'
     base:                               '\u0430'
     shift, capslock:                    '\u0410'
+    shift+capslock:                     '\u0430'
     ralt:                               'f'
-    ralt+shift, ralt+capslock:          'F'
+    shift+ralt, capslock+ralt:          'F'
+    shift+capslock+ralt:                'f'
 }
 
 key G {
     label:                              '\u041f'
     base:                               '\u043f'
     shift, capslock:                    '\u041f'
+    shift+capslock:                     '\u043f'
     ralt:                               'g'
-    ralt+shift, ralt+capslock:          'G'
+    shift+ralt, capslock+ralt:          'G'
+    shift+capslock+ralt:                'g'
 }
 
 key H {
     label:                              '\u0420'
     base:                               '\u0440'
     shift, capslock:                    '\u0420'
+    shift+capslock:                     '\u0440'
     ralt:                               'h'
-    ralt+shift, ralt+capslock:          'H'
+    shift+ralt, capslock+ralt:          'H'
+    shift+capslock+ralt:                'h'
 }
 
 key J {
     label:                              '\u041e'
     base:                               '\u043e'
     shift, capslock:                    '\u041e'
+    shift+capslock:                     '\u043e'
     ralt:                               'j'
-    ralt+shift, ralt+capslock:          'J'
+    shift+ralt, capslock+ralt:          'J'
+    shift+capslock+ralt:                'j'
 }
 
 key K {
     label:                              '\u041b'
     base:                               '\u043b'
     shift, capslock:                    '\u041b'
+    shift+capslock:                     '\u043b'
     ralt:                               'k'
-    ralt+shift, ralt+capslock:          'K'
+    shift+ralt, capslock+ralt:          'K'
+    shift+capslock+ralt:                'k'
 }
 
 key L {
     label:                              '\u0414'
     base:                               '\u0434'
     shift, capslock:                    '\u0414'
+    shift+capslock:                     '\u0434'
     ralt:                               'l'
-    ralt+shift, ralt+capslock:          'L'
+    shift+ralt, capslock+ralt:          'L'
+    shift+capslock+ralt:                'l'
 }
 
 key SEMICOLON {
     label:                              '\u0416'
     base:                               '\u0436'
     shift, capslock:                    '\u0416'
+    shift+capslock:                     '\u0436'
     ralt:                               ';'
     ralt+shift:                         ':'
 }
@@ -302,6 +344,7 @@
     label:                              '\u0404'
     base:                               '\u0454'
     shift, capslock:                    '\u0404'
+    shift+capslock:                     '\u0454'
     ralt:                               '\''
     ralt+shift:                         '"'
 }
@@ -319,6 +362,7 @@
     label:                              '\u0490'
     base:                               '\u0491'
     shift, capslock:                    '\u0490'
+    shift+capslock:                     '\u0491'
     ralt:                               '\\'
     ralt+shift:                         '|'
 }
@@ -327,62 +371,77 @@
     label:                              '\u042f'
     base:                               '\u044f'
     shift, capslock:                    '\u042f'
+    shift+capslock:                     '\u044f'
     ralt:                               'z'
-    ralt+shift, ralt+capslock:          'Z'
+    shift+ralt, capslock+ralt:          'Z'
+    shift+capslock+ralt:                'z'
 }
 
 key X {
     label:                              '\u0427'
     base:                               '\u0447'
     shift, capslock:                    '\u0427'
+    shift+capslock:                     '\u0447'
     ralt:                               'x'
-    ralt+shift, ralt+capslock:          'X'
+    shift+ralt, capslock+ralt:          'X'
+    shift+capslock+ralt:                'x'
 }
 
 key C {
     label:                              '\u0421'
     base:                               '\u0441'
     shift, capslock:                    '\u0421'
+    shift+capslock:                     '\u0441'
     ralt:                               'c'
-    ralt+shift, ralt+capslock:          'C'
+    shift+ralt, capslock+ralt:          'C'
+    shift+capslock+ralt:                'c'
 }
 
 key V {
     label:                              '\u041c'
     base:                               '\u043c'
     shift, capslock:                    '\u041c'
+    shift+capslock:                     '\u043c'
     ralt:                               'v'
-    ralt+shift, ralt+capslock:          'V'
+    shift+ralt, capslock+ralt:          'V'
+    shift+capslock+ralt:                'v'
 }
 
 key B {
     label:                              '\u0418'
     base:                               '\u0438'
     shift, capslock:                    '\u0418'
+    shift+capslock:                     '\u0438'
     ralt:                               'b'
-    ralt+shift, ralt+capslock:          'B'
+    shift+ralt, capslock+ralt:          'B'
+    shift+capslock+ralt:                'b'
 }
 
 key N {
     label:                              '\u0422'
     base:                               '\u0442'
     shift, capslock:                    '\u0422'
+    shift+capslock:                     '\u0442'
     ralt:                               'n'
-    ralt+shift, ralt+capslock:          'N'
+    shift+ralt, capslock+ralt:          'N'
+    shift+capslock+ralt:                'n'
 }
 
 key M {
     label:                              '\u042c'
     base:                               '\u044c'
     shift, capslock:                    '\u042c'
+    shift+capslock:                     '\u044c'
     ralt:                               'm'
-    ralt+shift, ralt+capslock:          'M'
+    shift+ralt, capslock+ralt:          'M'
+    shift+capslock+ralt:                'm'
 }
 
 key COMMA {
     label:                              '\u0411'
     base:                               '\u0431'
     shift, capslock:                    '\u0411'
+    shift+capslock:                     '\u0431'
     ralt:                               ','
     ralt+shift:                         '<'
 }
@@ -391,6 +450,7 @@
     label:                              '\u042e'
     base:                               '\u044e'
     shift, capslock:                    '\u042e'
+    shift+capslock:                     '\u044e'
     ralt:                               '.'
     ralt+shift:                         '>'
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java b/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java
index bd9e760..c8bcabf 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java
@@ -35,6 +35,7 @@
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
+import java.util.function.Predicate;
 
 /**
  * Class for managing services matching a given intent and requesting a given permission.
@@ -51,12 +52,13 @@
     private final HashSet<ComponentName> mEnabledServices = new HashSet<>();
     private final List<ServiceInfo> mServices = new ArrayList<>();
     private final List<Callback> mCallbacks = new ArrayList<>();
+    private final Predicate mValidator;
 
     private boolean mListening;
 
     private ServiceListing(Context context, String tag,
             String setting, String intentAction, String permission, String noun,
-            boolean addDeviceLockedFlags) {
+            boolean addDeviceLockedFlags, Predicate validator) {
         mContentResolver = context.getContentResolver();
         mContext = context;
         mTag = tag;
@@ -65,6 +67,7 @@
         mPermission = permission;
         mNoun = noun;
         mAddDeviceLockedFlags = addDeviceLockedFlags;
+        mValidator = validator;
     }
 
     public void addCallback(Callback callback) {
@@ -137,7 +140,6 @@
         final PackageManager pmWrapper = mContext.getPackageManager();
         List<ResolveInfo> installedServices = pmWrapper.queryIntentServicesAsUser(
                 new Intent(mIntentAction), flags, user);
-
         for (ResolveInfo resolveInfo : installedServices) {
             ServiceInfo info = resolveInfo.serviceInfo;
 
@@ -148,6 +150,9 @@
                         + mPermission);
                 continue;
             }
+            if (mValidator != null && !mValidator.test(info)) {
+                continue;
+            }
             mServices.add(info);
         }
         for (Callback callback : mCallbacks) {
@@ -194,6 +199,7 @@
         private String mPermission;
         private String mNoun;
         private boolean mAddDeviceLockedFlags = false;
+        private Predicate mValidator;
 
         public Builder(Context context) {
             mContext = context;
@@ -224,6 +230,11 @@
             return this;
         }
 
+        public Builder setValidator(Predicate<ServiceInfo> validator) {
+            mValidator = validator;
+            return this;
+        }
+
         /**
          * Set to true to add support for both MATCH_DIRECT_BOOT_AWARE and
          * MATCH_DIRECT_BOOT_UNAWARE flags when querying PackageManager. Required to get results
@@ -236,7 +247,7 @@
 
         public ServiceListing build() {
             return new ServiceListing(mContext, mTag, mSetting, mIntentAction, mPermission, mNoun,
-                    mAddDeviceLockedFlags);
+                    mAddDeviceLockedFlags, mValidator);
         }
     }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
index 688fc72..c4f09ce 100644
--- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
@@ -31,13 +31,15 @@
 import android.provider.Settings;
 import android.service.dreams.DreamService;
 import android.service.dreams.IDreamManager;
+import android.util.ArraySet;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 import java.util.Set;
@@ -116,7 +118,7 @@
     private final boolean mDreamsActivatedOnSleepByDefault;
     private final boolean mDreamsActivatedOnDockByDefault;
     private final Set<ComponentName> mDisabledDreams;
-    private final Set<Integer> mSupportedComplications;
+    private Set<Integer> mSupportedComplications;
     private static DreamBackend sInstance;
 
     public static DreamBackend getInstance(Context context) {
@@ -281,7 +283,18 @@
 
     /** Gets all complications which have been enabled by the user. */
     public Set<Integer> getEnabledComplications() {
-        return getComplicationsEnabled() ? mSupportedComplications : Collections.emptySet();
+        final Set<Integer> enabledComplications =
+                getComplicationsEnabled()
+                        ? new ArraySet<>(mSupportedComplications) : new ArraySet<>();
+
+        if (!getHomeControlsEnabled()) {
+            enabledComplications.remove(COMPLICATION_TYPE_HOME_CONTROLS);
+        } else if (mSupportedComplications.contains(COMPLICATION_TYPE_HOME_CONTROLS)) {
+            // Add home control type to list of enabled complications, even if other complications
+            // have been disabled.
+            enabledComplications.add(COMPLICATION_TYPE_HOME_CONTROLS);
+        }
+        return enabledComplications;
     }
 
     /** Sets complication enabled state. */
@@ -290,6 +303,18 @@
                 Settings.Secure.SCREENSAVER_COMPLICATIONS_ENABLED, enabled ? 1 : 0);
     }
 
+    /** Sets whether home controls are enabled by the user on the dream */
+    public void setHomeControlsEnabled(boolean enabled) {
+        Settings.Secure.putInt(mContext.getContentResolver(),
+                Settings.Secure.SCREENSAVER_HOME_CONTROLS_ENABLED, enabled ? 1 : 0);
+    }
+
+    /** Gets whether home controls button is enabled on the dream */
+    private boolean getHomeControlsEnabled() {
+        return Settings.Secure.getInt(mContext.getContentResolver(),
+                Settings.Secure.SCREENSAVER_HOME_CONTROLS_ENABLED, 1) == 1;
+    }
+
     /**
      * Gets whether complications are enabled on this device
      */
@@ -304,6 +329,14 @@
         return mSupportedComplications;
     }
 
+    /**
+     * Sets the list of supported complications. Should only be used in tests.
+     */
+    @VisibleForTesting
+    public void setSupportedComplications(Set<Integer> complications) {
+        mSupportedComplications = complications;
+    }
+
     public boolean isEnabled() {
         return getBoolean(Settings.Secure.SCREENSAVER_ENABLED, mDreamsEnabledByDefault);
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 684a9aa..c9e8312 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -31,7 +31,6 @@
 import static android.media.MediaRoute2Info.TYPE_USB_HEADSET;
 import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
 import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
-import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR;
 
 import static com.android.settingslib.media.LocalMediaManager.MediaDeviceState.STATE_SELECTED;
 
@@ -621,9 +620,11 @@
             dispatchConnectedDeviceChanged(id);
         }
 
+        /**
+         * Ignore callback here since we'll also receive {@link onRequestFailed} with reason code.
+         */
         @Override
         public void onTransferFailed(RoutingSessionInfo session, MediaRoute2Info route) {
-            dispatchOnRequestFailed(REASON_UNKNOWN_ERROR);
         }
 
         @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index 6b9866b..071ab27 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -33,7 +33,6 @@
 import static android.media.RouteListingPreference.Item.FLAG_ONGOING_SESSION;
 import static android.media.RouteListingPreference.Item.FLAG_ONGOING_SESSION_MANAGED;
 import static android.media.RouteListingPreference.Item.FLAG_SUGGESTED;
-import static android.media.RouteListingPreference.Item.SELECTION_BEHAVIOR_TRANSFER;
 import static android.media.RouteListingPreference.Item.SUBTEXT_AD_ROUTING_DISALLOWED;
 import static android.media.RouteListingPreference.Item.SUBTEXT_CUSTOM;
 import static android.media.RouteListingPreference.Item.SUBTEXT_DEVICE_LOW_POWER;
@@ -45,6 +44,7 @@
 import static android.media.RouteListingPreference.Item.SUBTEXT_UNAUTHORIZED;
 
 import static com.android.settingslib.media.LocalMediaManager.MediaDeviceState.STATE_SELECTED;
+import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER;
 
 import android.annotation.SuppressLint;
 import android.content.Context;
@@ -95,6 +95,17 @@
         int TYPE_CAST_GROUP_DEVICE = 7;
     }
 
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({SelectionBehavior.SELECTION_BEHAVIOR_NONE,
+            SELECTION_BEHAVIOR_TRANSFER,
+            SelectionBehavior.SELECTION_BEHAVIOR_GO_TO_APP
+    })
+    public @interface SelectionBehavior {
+        int SELECTION_BEHAVIOR_NONE = 0;
+        int SELECTION_BEHAVIOR_TRANSFER = 1;
+        int SELECTION_BEHAVIOR_GO_TO_APP = 2;
+    }
+
     @VisibleForTesting
     int mType;
 
@@ -213,7 +224,7 @@
      *
      * @return selection behavior of device
      */
-    @RouteListingPreference.Item.SubText
+    @SelectionBehavior
     public int getSelectionBehavior() {
         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && mItem != null
                 ? mItem.getSelectionBehavior() : SELECTION_BEHAVIOR_TRANSFER;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java
index f7fd25b..7ff0988 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java
@@ -18,20 +18,35 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyList;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
 import android.provider.Settings;
 
+import androidx.test.core.app.ApplicationProvider;
+
+import com.google.common.collect.ImmutableList;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 
+import java.util.List;
+
 @RunWith(RobolectricTestRunner.class)
 public class ServiceListingTest {
 
@@ -39,10 +54,16 @@
     private static final String TEST_INTENT = "com.example.intent";
 
     private ServiceListing mServiceListing;
+    private Context mContext;
+    private PackageManager mPm;
 
     @Before
     public void setUp() {
-        mServiceListing = new ServiceListing.Builder(RuntimeEnvironment.application)
+        mPm = mock(PackageManager.class);
+        mContext = spy(ApplicationProvider.getApplicationContext());
+        when(mContext.getPackageManager()).thenReturn(mPm);
+
+        mServiceListing = new ServiceListing.Builder(mContext)
                 .setTag("testTag")
                 .setSetting(TEST_SETTING)
                 .setNoun("testNoun")
@@ -52,6 +73,81 @@
     }
 
     @Test
+    public void testValidator() {
+        ServiceInfo s1 = new ServiceInfo();
+        s1.permission = "testPermission";
+        s1.packageName = "pkg";
+        ServiceInfo s2 = new ServiceInfo();
+        s2.permission = "testPermission";
+        s2.packageName = "pkg2";
+        ResolveInfo r1 = new ResolveInfo();
+        r1.serviceInfo = s1;
+        ResolveInfo r2 = new ResolveInfo();
+        r2.serviceInfo = s2;
+
+        when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn(
+                ImmutableList.of(r1, r2));
+
+        mServiceListing = new ServiceListing.Builder(mContext)
+                .setTag("testTag")
+                .setSetting(TEST_SETTING)
+                .setNoun("testNoun")
+                .setIntentAction(TEST_INTENT)
+                .setValidator(info -> {
+                    if (info.packageName.equals("pkg")) {
+                        return true;
+                    }
+                    return false;
+                })
+                .setPermission("testPermission")
+                .build();
+        ServiceListing.Callback callback = mock(ServiceListing.Callback.class);
+        mServiceListing.addCallback(callback);
+        mServiceListing.reload();
+
+        verify(mPm).queryIntentServicesAsUser(any(), anyInt(), anyInt());
+        ArgumentCaptor<List<ServiceInfo>> captor = ArgumentCaptor.forClass(List.class);
+        verify(callback, times(1)).onServicesReloaded(captor.capture());
+
+        assertThat(captor.getValue().size()).isEqualTo(1);
+        assertThat(captor.getValue().get(0)).isEqualTo(s1);
+    }
+
+    @Test
+    public void testNoValidator() {
+        ServiceInfo s1 = new ServiceInfo();
+        s1.permission = "testPermission";
+        s1.packageName = "pkg";
+        ServiceInfo s2 = new ServiceInfo();
+        s2.permission = "testPermission";
+        s2.packageName = "pkg2";
+        ResolveInfo r1 = new ResolveInfo();
+        r1.serviceInfo = s1;
+        ResolveInfo r2 = new ResolveInfo();
+        r2.serviceInfo = s2;
+
+        when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn(
+                ImmutableList.of(r1, r2));
+
+        mServiceListing = new ServiceListing.Builder(mContext)
+                .setTag("testTag")
+                .setSetting(TEST_SETTING)
+                .setNoun("testNoun")
+                .setIntentAction(TEST_INTENT)
+                .setPermission("testPermission")
+                .build();
+        ServiceListing.Callback callback = mock(ServiceListing.Callback.class);
+        mServiceListing.addCallback(callback);
+        mServiceListing.reload();
+
+        verify(mPm).queryIntentServicesAsUser(any(), anyInt(), anyInt());
+        ArgumentCaptor<List<ServiceInfo>> captor = ArgumentCaptor.forClass(List.class);
+        verify(callback, times(1)).onServicesReloaded(captor.capture());
+
+        assertThat(captor.getValue().size()).isEqualTo(2);
+    }
+
+    @Test
     public void testCallback() {
         ServiceListing.Callback callback = mock(ServiceListing.Callback.class);
         mServiceListing.addCallback(callback);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java
index 52b9227..22ec12d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java
@@ -16,6 +16,10 @@
 package com.android.settingslib.dream;
 
 
+import static com.android.settingslib.dream.DreamBackend.COMPLICATION_TYPE_DATE;
+import static com.android.settingslib.dream.DreamBackend.COMPLICATION_TYPE_HOME_CONTROLS;
+import static com.android.settingslib.dream.DreamBackend.COMPLICATION_TYPE_TIME;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.mock;
@@ -36,13 +40,16 @@
 import org.robolectric.shadows.ShadowSettings;
 
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.stream.Collectors;
 
 @RunWith(RobolectricTestRunner.class)
 @Config(shadows = {ShadowSettings.ShadowSecure.class})
 public final class DreamBackendTest {
-    private static final int[] SUPPORTED_DREAM_COMPLICATIONS = {1, 2, 3};
+    private static final int[] SUPPORTED_DREAM_COMPLICATIONS =
+            {COMPLICATION_TYPE_HOME_CONTROLS, COMPLICATION_TYPE_DATE,
+                    COMPLICATION_TYPE_TIME};
     private static final List<Integer> SUPPORTED_DREAM_COMPLICATIONS_LIST = Arrays.stream(
             SUPPORTED_DREAM_COMPLICATIONS).boxed().collect(
             Collectors.toList());
@@ -93,8 +100,52 @@
     @Test
     public void testDisableComplications() {
         mBackend.setComplicationsEnabled(false);
-        assertThat(mBackend.getEnabledComplications()).isEmpty();
+        assertThat(mBackend.getEnabledComplications())
+                .containsExactly(COMPLICATION_TYPE_HOME_CONTROLS);
         assertThat(mBackend.getComplicationsEnabled()).isFalse();
     }
-}
 
+    @Test
+    public void testHomeControlsDisabled_ComplicationsEnabled() {
+        mBackend.setComplicationsEnabled(true);
+        mBackend.setHomeControlsEnabled(false);
+        // Home controls should not be enabled, only date and time.
+        final List<Integer> enabledComplications =
+                Arrays.asList(COMPLICATION_TYPE_DATE, COMPLICATION_TYPE_TIME);
+        assertThat(mBackend.getEnabledComplications())
+                .containsExactlyElementsIn(enabledComplications);
+    }
+
+    @Test
+    public void testHomeControlsDisabled_ComplicationsDisabled() {
+        mBackend.setComplicationsEnabled(false);
+        mBackend.setHomeControlsEnabled(false);
+        assertThat(mBackend.getEnabledComplications()).isEmpty();
+    }
+
+    @Test
+    public void testHomeControlsEnabled_ComplicationsDisabled() {
+        mBackend.setComplicationsEnabled(false);
+        mBackend.setHomeControlsEnabled(true);
+        // Home controls should not be enabled, only date and time.
+        final List<Integer> enabledComplications =
+                Collections.singletonList(COMPLICATION_TYPE_HOME_CONTROLS);
+        assertThat(mBackend.getEnabledComplications())
+                .containsExactlyElementsIn(enabledComplications);
+    }
+
+    @Test
+    public void testHomeControlsEnabled_ComplicationsEnabled() {
+        mBackend.setComplicationsEnabled(true);
+        mBackend.setHomeControlsEnabled(true);
+        // Home controls should not be enabled, only date and time.
+        final List<Integer> enabledComplications =
+                Arrays.asList(
+                        COMPLICATION_TYPE_HOME_CONTROLS,
+                        COMPLICATION_TYPE_DATE,
+                        COMPLICATION_TYPE_TIME
+                );
+        assertThat(mBackend.getEnabledComplications())
+                .containsExactlyElementsIn(enabledComplications);
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index f63c06a..270fda8 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -30,6 +30,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -799,12 +800,12 @@
     }
 
     @Test
-    public void onTransferFailed_shouldDispatchOnRequestFailed() {
+    public void onTransferFailed_notDispatchOnRequestFailed() {
         mInfoMediaManager.registerCallback(mCallback);
 
         mInfoMediaManager.mMediaRouterCallback.onTransferFailed(null, null);
 
-        verify(mCallback).onRequestFailed(REASON_UNKNOWN_ERROR);
+        verify(mCallback, never()).onRequestFailed(REASON_UNKNOWN_ERROR);
     }
 
     @Test
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index 09a1ba2..e50f522 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -49,6 +49,7 @@
         Settings.Global.CHARGING_SOUNDS_ENABLED,
         Settings.Global.USB_MASS_STORAGE_ENABLED,
         Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED,
+        Settings.Global.NETWORK_AVOID_BAD_WIFI,
         Settings.Global.WIFI_WAKEUP_ENABLED,
         Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
         Settings.Global.USE_OPEN_WIFI_PACKAGE,
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index f66fcba..3efb41d 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -139,6 +139,7 @@
         Settings.Secure.SCREENSAVER_COMPONENTS,
         Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
         Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
+        Settings.Secure.SCREENSAVER_HOME_CONTROLS_ENABLED,
         Settings.Secure.SHOW_FIRST_CRASH_DIALOG_DEV_OPTION,
         Settings.Secure.VOLUME_HUSH_GESTURE,
         Settings.Secure.MANUAL_RINGER_TOGGLE_COUNT,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index e57cf3b..8d07fb6 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -20,6 +20,9 @@
 import static android.hardware.display.HdrConversionMode.HDR_CONVERSION_PASSTHROUGH;
 import static android.hardware.display.HdrConversionMode.HDR_CONVERSION_SYSTEM;
 import static android.media.AudioFormat.SURROUND_SOUND_ENCODING;
+import static android.net.ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI_AVOID;
+import static android.net.ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI_IGNORE;
+import static android.net.ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI_PROMPT;
 import static android.provider.settings.validators.SettingsValidators.ANY_INTEGER_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.ANY_STRING_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.BOOLEAN_VALIDATOR;
@@ -103,6 +106,14 @@
         VALIDATORS.put(
                 Global.NETWORK_RECOMMENDATIONS_ENABLED,
                 new DiscreteValueValidator(new String[] {"-1", "0", "1"}));
+        VALIDATORS.put(
+                Global.NETWORK_AVOID_BAD_WIFI,
+                new DiscreteValueValidator(
+                        new String[] {
+                                String.valueOf(NETWORK_AVOID_BAD_WIFI_IGNORE),
+                                String.valueOf(NETWORK_AVOID_BAD_WIFI_PROMPT),
+                                String.valueOf(NETWORK_AVOID_BAD_WIFI_AVOID),
+                        }));
         VALIDATORS.put(Global.WIFI_WAKEUP_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, BOOLEAN_VALIDATOR);
         VALIDATORS.put(
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 558e19f..abd2c75 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -206,6 +206,7 @@
         VALIDATORS.put(Secure.SCREENSAVER_COMPONENTS, COMMA_SEPARATED_COMPONENT_LIST_VALIDATOR);
         VALIDATORS.put(Secure.SCREENSAVER_ACTIVATE_ON_DOCK, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.SCREENSAVER_HOME_CONTROLS_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.SHOW_FIRST_CRASH_DIALOG_DEV_OPTION, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.VOLUME_HUSH_GESTURE, NON_NEGATIVE_INTEGER_VALIDATOR);
         VALIDATORS.put(
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
index db6cc1a..a8eeec3 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.os.Bundle;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.ArrayMap;
 import android.util.MemoryIntArray;
@@ -30,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.
  */
@@ -81,6 +82,10 @@
     }
 
     private void incrementGenerationInternal(int key, @NonNull String indexMapKey) {
+        if (SettingsState.isGlobalSettingsKey(key)) {
+            // Global settings are shared across users, so ignore the userId in the key
+            key = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM);
+        }
         synchronized (mLock) {
             final MemoryIntArray backingStore = getBackingStoreLocked(key,
                     /* createIfNotExist= */ false);
@@ -126,6 +131,10 @@
      *  returning the result.
      */
     public void addGenerationData(Bundle bundle, int key, String indexMapKey) {
+        if (SettingsState.isGlobalSettingsKey(key)) {
+            // Global settings are shared across users, so ignore the userId in the key
+            key = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM);
+        }
         synchronized (mLock) {
             final MemoryIntArray backingStore = getBackingStoreLocked(key,
                     /* createIfNotExist= */ true);
@@ -140,11 +149,9 @@
                     // Should not happen unless having error accessing the backing store
                     return;
                 }
-                bundle.putParcelable(Settings.CALL_METHOD_TRACK_GENERATION_KEY,
-                        backingStore);
+                bundle.putParcelable(Settings.CALL_METHOD_TRACK_GENERATION_KEY, backingStore);
                 bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, index);
-                bundle.putInt(Settings.CALL_METHOD_GENERATION_KEY,
-                        backingStore.get(index));
+                bundle.putInt(Settings.CALL_METHOD_GENERATION_KEY, backingStore.get(index));
                 if (DEBUG) {
                     Slog.i(LOG_TAG, "Exported index:" + index + " for "
                             + (indexMapKey.isEmpty() ? "unset settings" : "setting:" + indexMapKey)
@@ -189,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);
@@ -249,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/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index e6408bf..7607909 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -35,6 +35,13 @@
 import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
 import static com.android.internal.accessibility.util.AccessibilityUtils.ACCESSIBILITY_MENU_IN_SYSTEM;
 import static com.android.providers.settings.SettingsState.FALLBACK_FILE_SUFFIX;
+import static com.android.providers.settings.SettingsState.getTypeFromKey;
+import static com.android.providers.settings.SettingsState.getUserIdFromKey;
+import static com.android.providers.settings.SettingsState.isConfigSettingsKey;
+import static com.android.providers.settings.SettingsState.isGlobalSettingsKey;
+import static com.android.providers.settings.SettingsState.isSecureSettingsKey;
+import static com.android.providers.settings.SettingsState.isSsaidSettingsKey;
+import static com.android.providers.settings.SettingsState.isSystemSettingsKey;
 import static com.android.providers.settings.SettingsState.makeKey;
 
 import android.Manifest;
@@ -376,14 +383,6 @@
     @GuardedBy("mLock")
     private boolean mSyncConfigDisabledUntilReboot;
 
-    public static int getTypeFromKey(int key) {
-        return SettingsState.getTypeFromKey(key);
-    }
-
-    public static int getUserIdFromKey(int key) {
-        return SettingsState.getUserIdFromKey(key);
-    }
-
     @ChangeId
     @EnabledSince(targetSdkVersion=android.os.Build.VERSION_CODES.S)
     private static final long ENFORCE_READ_PERMISSION_FOR_MULTI_SIM_DATA_CALL = 172670679L;
@@ -3620,26 +3619,6 @@
             }
         }
 
-        private boolean isConfigSettingsKey(int key) {
-            return getTypeFromKey(key) == SETTINGS_TYPE_CONFIG;
-        }
-
-        private boolean isGlobalSettingsKey(int key) {
-            return getTypeFromKey(key) == SETTINGS_TYPE_GLOBAL;
-        }
-
-        private boolean isSystemSettingsKey(int key) {
-            return getTypeFromKey(key) == SETTINGS_TYPE_SYSTEM;
-        }
-
-        private boolean isSecureSettingsKey(int key) {
-            return getTypeFromKey(key) == SETTINGS_TYPE_SECURE;
-        }
-
-        private boolean isSsaidSettingsKey(int key) {
-            return getTypeFromKey(key) == SETTINGS_TYPE_SSAID;
-        }
-
         private boolean shouldBan(int type) {
             if (SETTINGS_TYPE_CONFIG != type) {
                 return false;
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 4d8705f..e3153e0 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -255,6 +255,26 @@
         }
     }
 
+    public static boolean isConfigSettingsKey(int key) {
+        return getTypeFromKey(key) == SETTINGS_TYPE_CONFIG;
+    }
+
+    public static boolean isGlobalSettingsKey(int key) {
+        return getTypeFromKey(key) == SETTINGS_TYPE_GLOBAL;
+    }
+
+    public static boolean isSystemSettingsKey(int key) {
+        return getTypeFromKey(key) == SETTINGS_TYPE_SYSTEM;
+    }
+
+    public static boolean isSecureSettingsKey(int key) {
+        return getTypeFromKey(key) == SETTINGS_TYPE_SECURE;
+    }
+
+    public static boolean isSsaidSettingsKey(int key) {
+        return getTypeFromKey(key) == SETTINGS_TYPE_SSAID;
+    }
+
     public static String keyToString(int key) {
         return "Key[user=" + getUserIdFromKey(key) + ";type="
                 + settingTypeToString(getTypeFromKey(key)) + "]";
diff --git a/packages/SettingsProvider/test/AndroidTest.xml b/packages/SettingsProvider/test/AndroidTest.xml
index 9d23526..0bf53cc 100644
--- a/packages/SettingsProvider/test/AndroidTest.xml
+++ b/packages/SettingsProvider/test/AndroidTest.xml
@@ -14,6 +14,12 @@
      limitations under the License.
 -->
 <configuration description="Run Settings Provider Tests.">
+    <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+        <option name="set-global-setting" key="verifier_verify_adb_installs" value="0" />
+        <option name="restore-settings" value="true" />
+        <option name="force-skip-system-props" value="true" />
+    </target_preparer>
+
     <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
         <option name="test-file-name" value="SettingsProviderTest.apk" />
     </target_preparer>
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 278ceb9..1f14723 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -372,7 +372,6 @@
                     Settings.Global.NETPOLICY_QUOTA_FRAC_JOBS,
                     Settings.Global.NETPOLICY_QUOTA_FRAC_MULTIPATH,
                     Settings.Global.NETPOLICY_OVERRIDE_ENABLED,
-                    Settings.Global.NETWORK_AVOID_BAD_WIFI,
                     Settings.Global.NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES,
                     Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE,
                     Settings.Global.NETWORK_WATCHLIST_LAST_REPORT_TIME,
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java
index 6ec8146..586d6f7 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java
@@ -170,6 +170,23 @@
         checkBundle(b, 1, 2, false);
     }
 
+    @Test
+    public void testGlobalSettings() throws IOException {
+        final GenerationRegistry generationRegistry = new GenerationRegistry(new Object());
+        final int globalKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_GLOBAL, 0);
+        final String testGlobalSetting = "test_global_setting";
+        final Bundle b = new Bundle();
+        generationRegistry.addGenerationData(b, globalKey, testGlobalSetting);
+        checkBundle(b, 0, 1, false);
+        final MemoryIntArray array = getArray(b);
+        final int globalKey2 = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_GLOBAL, 10);
+        b.clear();
+        generationRegistry.addGenerationData(b, globalKey2, testGlobalSetting);
+        checkBundle(b, 0, 1, false);
+        final MemoryIntArray array2 = getArray(b);
+        // Check that user10 and user0 use the same array to store global settings' generations
+        assertThat(array).isEqualTo(array2);
+    }
 
     private void checkBundle(Bundle b, int expectedIndex, int expectedGeneration, boolean isNull)
             throws IOException {
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/animation/src/com/android/systemui/animation/FontVariationUtils.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt
new file mode 100644
index 0000000..78ae4af
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt
@@ -0,0 +1,59 @@
+package com.android.systemui.animation
+
+private const val TAG_WGHT = "wght"
+private const val TAG_WDTH = "wdth"
+private const val TAG_OPSZ = "opsz"
+private const val TAG_ROND = "ROND"
+
+class FontVariationUtils {
+    private var mWeight = -1
+    private var mWidth = -1
+    private var mOpticalSize = -1
+    private var mRoundness = -1
+    private var isUpdated = false
+
+    /*
+     * generate fontVariationSettings string, used for key in typefaceCache in TextAnimator
+     * the order of axes should align to the order of parameters
+     * if every axis remains unchanged, return ""
+     */
+    fun updateFontVariation(
+        weight: Int = -1,
+        width: Int = -1,
+        opticalSize: Int = -1,
+        roundness: Int = -1
+    ): String {
+        isUpdated = false
+        if (weight >= 0 && mWeight != weight) {
+            isUpdated = true
+            mWeight = weight
+        }
+        if (width >= 0 && mWidth != width) {
+            isUpdated = true
+            mWidth = width
+        }
+        if (opticalSize >= 0 && mOpticalSize != opticalSize) {
+            isUpdated = true
+            mOpticalSize = opticalSize
+        }
+
+        if (roundness >= 0 && mRoundness != roundness) {
+            isUpdated = true
+            mRoundness = roundness
+        }
+        var resultString = ""
+        if (mWeight >= 0) {
+            resultString += "'$TAG_WGHT' $mWeight"
+        }
+        if (mWidth >= 0) {
+            resultString += (if (resultString.isBlank()) "" else ", ") + "'$TAG_WDTH' $mWidth"
+        }
+        if (mOpticalSize >= 0) {
+            resultString += (if (resultString.isBlank()) "" else ", ") + "'$TAG_OPSZ' $mOpticalSize"
+        }
+        if (mRoundness >= 0) {
+            resultString += (if (resultString.isBlank()) "" else ", ") + "'$TAG_ROND' $mRoundness"
+        }
+        return if (isUpdated) resultString else ""
+    }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
index 7fe94d3..9e9929e 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
@@ -23,11 +23,8 @@
 import android.graphics.Canvas
 import android.graphics.Typeface
 import android.graphics.fonts.Font
-import android.graphics.fonts.FontVariationAxis
 import android.text.Layout
-import android.util.SparseArray
 
-private const val TAG_WGHT = "wght"
 private const val DEFAULT_ANIMATION_DURATION: Long = 300
 
 typealias GlyphCallback = (TextAnimator.PositionedGlyph, Float) -> Unit
@@ -51,7 +48,7 @@
  *
  *         // Change the text size with animation.
  *         fun setTextSize(sizePx: Float, animate: Boolean) {
- *             animator.setTextStyle(-1 /* unchanged weight */, sizePx, animate)
+ *             animator.setTextStyle("" /* unchanged fvar... */, sizePx, animate)
  *         }
  *     }
  * ```
@@ -115,7 +112,9 @@
             protected set
     }
 
-    private val typefaceCache = SparseArray<Typeface?>()
+    private val fontVariationUtils = FontVariationUtils()
+
+    private val typefaceCache = HashMap<String, Typeface?>()
 
     fun updateLayout(layout: Layout) {
         textInterpolator.layout = layout
@@ -186,7 +185,7 @@
      * Bu passing -1 to duration, the default text animation, 1000ms, is used.
      * By passing false to animate, the text will be updated without animation.
      *
-     * @param weight an optional text weight.
+     * @param fvar an optional text fontVariationSettings.
      * @param textSize an optional font size.
      * @param colors an optional colors array that must be the same size as numLines passed to
      *               the TextInterpolator
@@ -199,7 +198,7 @@
      *                     will be used. This is ignored if animate is false.
      */
     fun setTextStyle(
-        weight: Int = -1,
+        fvar: String? = "",
         textSize: Float = -1f,
         color: Int? = null,
         strokeWidth: Float = -1f,
@@ -217,42 +216,16 @@
         if (textSize >= 0) {
             textInterpolator.targetPaint.textSize = textSize
         }
-        if (weight >= 0) {
-            val fontVariationArray =
-                    FontVariationAxis.fromFontVariationSettings(
-                        textInterpolator.targetPaint.fontVariationSettings
-                    )
-            if (fontVariationArray.isNullOrEmpty()) {
-                textInterpolator.targetPaint.typeface =
-                    typefaceCache.getOrElse(weight) {
-                        textInterpolator.targetPaint.fontVariationSettings = "'$TAG_WGHT' $weight"
-                        textInterpolator.targetPaint.typeface
-                    }
-            } else {
-                val idx = fontVariationArray.indexOfFirst { it.tag == "$TAG_WGHT" }
-                if (idx == -1) {
-                    val updatedFontVariation =
-                        textInterpolator.targetPaint.fontVariationSettings + ",'$TAG_WGHT' $weight"
-                    textInterpolator.targetPaint.typeface =
-                        typefaceCache.getOrElse(weight) {
-                            textInterpolator.targetPaint.fontVariationSettings =
-                                    updatedFontVariation
-                            textInterpolator.targetPaint.typeface
-                        }
-                } else {
-                    fontVariationArray[idx] = FontVariationAxis(
-                            "$TAG_WGHT", weight.toFloat())
-                    val updatedFontVariation =
-                            FontVariationAxis.toFontVariationSettings(fontVariationArray)
-                    textInterpolator.targetPaint.typeface =
-                        typefaceCache.getOrElse(weight) {
-                            textInterpolator.targetPaint.fontVariationSettings =
-                                    updatedFontVariation
-                            textInterpolator.targetPaint.typeface
-                        }
+
+        if (!fvar.isNullOrBlank()) {
+            textInterpolator.targetPaint.typeface =
+                typefaceCache.getOrElse(fvar) {
+                    textInterpolator.targetPaint.fontVariationSettings = fvar
+                    typefaceCache.put(fvar, textInterpolator.targetPaint.typeface)
+                    textInterpolator.targetPaint.typeface
                 }
-            }
         }
+
         if (color != null) {
             textInterpolator.targetPaint.color = color
         }
@@ -291,13 +264,56 @@
             invalidateCallback()
         }
     }
+
+    /**
+     * Set text style with animation. Similar as
+     * fun setTextStyle(
+     *      fvar: String? = "",
+     *      textSize: Float = -1f,
+     *      color: Int? = null,
+     *      strokeWidth: Float = -1f,
+     *      animate: Boolean = true,
+     *      duration: Long = -1L,
+     *      interpolator: TimeInterpolator? = null,
+     *      delay: Long = 0,
+     *      onAnimationEnd: Runnable? = null
+     * )
+     *
+     * @param weight an optional style value for `wght` in fontVariationSettings.
+     * @param width an optional style value for `wdth` in fontVariationSettings.
+     * @param opticalSize an optional style value for `opsz` in fontVariationSettings.
+     * @param roundness an optional style value for `ROND` in fontVariationSettings.
+     */
+    fun setTextStyle(
+        weight: Int = -1,
+        width: Int = -1,
+        opticalSize: Int = -1,
+        roundness: Int = -1,
+        textSize: Float = -1f,
+        color: Int? = null,
+        strokeWidth: Float = -1f,
+        animate: Boolean = true,
+        duration: Long = -1L,
+        interpolator: TimeInterpolator? = null,
+        delay: Long = 0,
+        onAnimationEnd: Runnable? = null
+    ) {
+        val fvar = fontVariationUtils.updateFontVariation(
+            weight = weight,
+            width = width,
+            opticalSize = opticalSize,
+            roundness = roundness,)
+        setTextStyle(
+            fvar = fvar,
+            textSize = textSize,
+            color = color,
+            strokeWidth = strokeWidth,
+            animate = animate,
+            duration = duration,
+            interpolator = interpolator,
+            delay = delay,
+            onAnimationEnd = onAnimationEnd,
+        )
+    }
 }
 
-private fun <V> SparseArray<V>.getOrElse(key: Int, defaultValue: () -> V): V {
-    var v = get(key)
-    if (v == null) {
-        v = defaultValue()
-        put(key, v)
-    }
-    return v
-}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DemotingTestWithoutBugDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DemotingTestWithoutBugDetector.kt
index 459a38e..09762b0 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DemotingTestWithoutBugDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DemotingTestWithoutBugDetector.kt
@@ -25,10 +25,12 @@
 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 java.util.EnumSet
 import java.util.regex.Pattern
 import org.jetbrains.uast.UAnnotation
 import org.jetbrains.uast.UElement
 
+@Suppress("UnstableApiUsage") // For linter api
 class DemotingTestWithoutBugDetector : Detector(), SourceCodeScanner {
     override fun getApplicableUastTypes(): List<Class<out UElement>> {
         return listOf(UAnnotation::class.java)
@@ -39,18 +41,15 @@
             override fun visitAnnotation(node: UAnnotation) {
                 // Annotations having int bugId field
                 if (node.qualifiedName in DEMOTING_ANNOTATION_BUG_ID) {
-                    val bugId = node.findAttributeValue("bugId")!!.evaluate() as Int
-                    if (bugId <= 0) {
+                    if (!containsBugId(node)) {
                         val location = context.getLocation(node)
                         val message = "Please attach a bug id to track demoted test"
                         context.report(ISSUE, node, location, message)
                     }
                 }
-                // @Ignore has a String field for reason
+                // @Ignore has a String field for specifying reasons
                 if (node.qualifiedName == DEMOTING_ANNOTATION_IGNORE) {
-                    val reason = node.findAttributeValue("value")!!.evaluate() as String
-                    val bugPattern = Pattern.compile("b/\\d+")
-                    if (!bugPattern.matcher(reason).find()) {
+                    if (!containsBugString(node)) {
                         val location = context.getLocation(node)
                         val message = "Please attach a bug (e.g. b/123) to track demoted test"
                         context.report(ISSUE, node, location, message)
@@ -60,6 +59,17 @@
         }
     }
 
+    private fun containsBugId(node: UAnnotation): Boolean {
+        val bugId = node.findAttributeValue("bugId")?.evaluate() as Int?
+        return bugId != null && bugId > 0
+    }
+
+    private fun containsBugString(node: UAnnotation): Boolean {
+        val reason = node.findAttributeValue("value")?.evaluate() as String?
+        val bugPattern = Pattern.compile("b/\\d+")
+        return reason != null && bugPattern.matcher(reason).find()
+    }
+
     companion object {
         val DEMOTING_ANNOTATION_BUG_ID =
             listOf(
@@ -87,7 +97,7 @@
                 implementation =
                     Implementation(
                         DemotingTestWithoutBugDetector::class.java,
-                        Scope.JAVA_FILE_SCOPE
+                        EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
                     )
             )
     }
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt
index 63eb263..a1e6f92 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt
@@ -20,8 +20,11 @@
 import com.android.tools.lint.checks.infrastructure.TestFiles
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.Scope
+import java.util.EnumSet
 import org.junit.Test
 
+@Suppress("UnstableApiUsage")
 class DemotingTestWithoutBugDetectorTest : SystemUILintDetectorTest() {
 
     override fun getDetector(): Detector = DemotingTestWithoutBugDetector()
@@ -45,6 +48,7 @@
                     .indented(),
                 *stubs
             )
+            .customScope(testScope)
             .issues(DemotingTestWithoutBugDetector.ISSUE)
             .run()
             .expectClean()
@@ -65,6 +69,7 @@
                     .indented(),
                 *stubs
             )
+            .customScope(testScope)
             .issues(DemotingTestWithoutBugDetector.ISSUE)
             .run()
             .expectClean()
@@ -88,6 +93,7 @@
                     .indented(),
                 *stubs
             )
+            .customScope(testScope)
             .issues(DemotingTestWithoutBugDetector.ISSUE)
             .run()
             .expect(
@@ -115,6 +121,7 @@
                     .indented(),
                 *stubs
             )
+            .customScope(testScope)
             .issues(DemotingTestWithoutBugDetector.ISSUE)
             .run()
             .expect(
@@ -145,6 +152,7 @@
                     .indented(),
                 *stubs
             )
+            .customScope(testScope)
             .issues(DemotingTestWithoutBugDetector.ISSUE)
             .run()
             .expectClean()
@@ -168,6 +176,7 @@
                     .indented(),
                 *stubs
             )
+            .customScope(testScope)
             .issues(DemotingTestWithoutBugDetector.ISSUE)
             .run()
             .expect(
@@ -198,6 +207,7 @@
                     .indented(),
                 *stubs
             )
+            .customScope(testScope)
             .issues(DemotingTestWithoutBugDetector.ISSUE)
             .run()
             .expectClean()
@@ -221,6 +231,7 @@
                     .indented(),
                 *stubs
             )
+            .customScope(testScope)
             .issues(DemotingTestWithoutBugDetector.ISSUE)
             .run()
             .expect(
@@ -248,6 +259,7 @@
                     .indented(),
                 *stubs
             )
+            .customScope(testScope)
             .issues(DemotingTestWithoutBugDetector.ISSUE)
             .run()
             .expect(
@@ -260,6 +272,7 @@
             )
     }
 
+    private val testScope = EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
     private val filtersFlakyTestStub: TestFile =
         java(
             """
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/swipeable/Swipeable.kt b/packages/SystemUI/compose/core/src/com/android/compose/swipeable/Swipeable.kt
new file mode 100644
index 0000000..946e779
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/swipeable/Swipeable.kt
@@ -0,0 +1,849 @@
+/*
+ * 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.compose.swipeable
+
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.SpringSpec
+import androidx.compose.foundation.gestures.DraggableState
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.draggable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.Saver
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshotFlow
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.composed
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.debugInspectorInfo
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.Velocity
+import androidx.compose.ui.unit.dp
+import com.android.compose.swipeable.SwipeableDefaults.AnimationSpec
+import com.android.compose.swipeable.SwipeableDefaults.StandardResistanceFactor
+import com.android.compose.swipeable.SwipeableDefaults.VelocityThreshold
+import com.android.compose.swipeable.SwipeableDefaults.resistanceConfig
+import com.android.compose.ui.util.lerp
+import kotlin.math.PI
+import kotlin.math.abs
+import kotlin.math.sign
+import kotlin.math.sin
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.take
+import kotlinx.coroutines.launch
+
+/**
+ * State of the [swipeable] modifier.
+ *
+ * This contains necessary information about any ongoing swipe or animation and provides methods to
+ * change the state either immediately or by starting an animation. To create and remember a
+ * [SwipeableState] with the default animation clock, use [rememberSwipeableState].
+ *
+ * @param initialValue The initial value of the state.
+ * @param animationSpec The default animation that will be used to animate to a new state.
+ * @param confirmStateChange Optional callback invoked to confirm or veto a pending state change.
+ *
+ * TODO(b/272311106): this is a fork from material. Unfork it when Swipeable.kt reaches material3.
+ */
+@Stable
+open class SwipeableState<T>(
+    initialValue: T,
+    internal val animationSpec: AnimationSpec<Float> = AnimationSpec,
+    internal val confirmStateChange: (newValue: T) -> Boolean = { true }
+) {
+    /**
+     * The current value of the state.
+     *
+     * If no swipe or animation is in progress, this corresponds to the anchor at which the
+     * [swipeable] is currently settled. If a swipe or animation is in progress, this corresponds
+     * the last anchor at which the [swipeable] was settled before the swipe or animation started.
+     */
+    var currentValue: T by mutableStateOf(initialValue)
+        private set
+
+    /** Whether the state is currently animating. */
+    var isAnimationRunning: Boolean by mutableStateOf(false)
+        private set
+
+    /**
+     * The current position (in pixels) of the [swipeable].
+     *
+     * You should use this state to offset your content accordingly. The recommended way is to use
+     * `Modifier.offsetPx`. This includes the resistance by default, if resistance is enabled.
+     */
+    val offset: State<Float>
+        get() = offsetState
+
+    /** The amount by which the [swipeable] has been swiped past its bounds. */
+    val overflow: State<Float>
+        get() = overflowState
+
+    // Use `Float.NaN` as a placeholder while the state is uninitialised.
+    private val offsetState = mutableStateOf(0f)
+    private val overflowState = mutableStateOf(0f)
+
+    // the source of truth for the "real"(non ui) position
+    // basically position in bounds + overflow
+    private val absoluteOffset = mutableStateOf(0f)
+
+    // current animation target, if animating, otherwise null
+    private val animationTarget = mutableStateOf<Float?>(null)
+
+    internal var anchors by mutableStateOf(emptyMap<Float, T>())
+
+    private val latestNonEmptyAnchorsFlow: Flow<Map<Float, T>> =
+        snapshotFlow { anchors }.filter { it.isNotEmpty() }.take(1)
+
+    internal var minBound = Float.NEGATIVE_INFINITY
+    internal var maxBound = Float.POSITIVE_INFINITY
+
+    internal fun ensureInit(newAnchors: Map<Float, T>) {
+        if (anchors.isEmpty()) {
+            // need to do initial synchronization synchronously :(
+            val initialOffset = newAnchors.getOffset(currentValue)
+            requireNotNull(initialOffset) { "The initial value must have an associated anchor." }
+            offsetState.value = initialOffset
+            absoluteOffset.value = initialOffset
+        }
+    }
+
+    internal suspend fun processNewAnchors(oldAnchors: Map<Float, T>, newAnchors: Map<Float, T>) {
+        if (oldAnchors.isEmpty()) {
+            // If this is the first time that we receive anchors, then we need to initialise
+            // the state so we snap to the offset associated to the initial value.
+            minBound = newAnchors.keys.minOrNull()!!
+            maxBound = newAnchors.keys.maxOrNull()!!
+            val initialOffset = newAnchors.getOffset(currentValue)
+            requireNotNull(initialOffset) { "The initial value must have an associated anchor." }
+            snapInternalToOffset(initialOffset)
+        } else if (newAnchors != oldAnchors) {
+            // If we have received new anchors, then the offset of the current value might
+            // have changed, so we need to animate to the new offset. If the current value
+            // has been removed from the anchors then we animate to the closest anchor
+            // instead. Note that this stops any ongoing animation.
+            minBound = Float.NEGATIVE_INFINITY
+            maxBound = Float.POSITIVE_INFINITY
+            val animationTargetValue = animationTarget.value
+            // if we're in the animation already, let's find it a new home
+            val targetOffset =
+                if (animationTargetValue != null) {
+                    // first, try to map old state to the new state
+                    val oldState = oldAnchors[animationTargetValue]
+                    val newState = newAnchors.getOffset(oldState)
+                    // return new state if exists, or find the closes one among new anchors
+                    newState ?: newAnchors.keys.minByOrNull { abs(it - animationTargetValue) }!!
+                } else {
+                    // we're not animating, proceed by finding the new anchors for an old value
+                    val actualOldValue = oldAnchors[offset.value]
+                    val value = if (actualOldValue == currentValue) currentValue else actualOldValue
+                    newAnchors.getOffset(value)
+                        ?: newAnchors.keys.minByOrNull { abs(it - offset.value) }!!
+                }
+            try {
+                animateInternalToOffset(targetOffset, animationSpec)
+            } catch (c: CancellationException) {
+                // If the animation was interrupted for any reason, snap as a last resort.
+                snapInternalToOffset(targetOffset)
+            } finally {
+                currentValue = newAnchors.getValue(targetOffset)
+                minBound = newAnchors.keys.minOrNull()!!
+                maxBound = newAnchors.keys.maxOrNull()!!
+            }
+        }
+    }
+
+    internal var thresholds: (Float, Float) -> Float by mutableStateOf({ _, _ -> 0f })
+
+    internal var velocityThreshold by mutableStateOf(0f)
+
+    internal var resistance: ResistanceConfig? by mutableStateOf(null)
+
+    internal val draggableState = DraggableState {
+        val newAbsolute = absoluteOffset.value + it
+        val clamped = newAbsolute.coerceIn(minBound, maxBound)
+        val overflow = newAbsolute - clamped
+        val resistanceDelta = resistance?.computeResistance(overflow) ?: 0f
+        offsetState.value = clamped + resistanceDelta
+        overflowState.value = overflow
+        absoluteOffset.value = newAbsolute
+    }
+
+    private suspend fun snapInternalToOffset(target: Float) {
+        draggableState.drag { dragBy(target - absoluteOffset.value) }
+    }
+
+    private suspend fun animateInternalToOffset(target: Float, spec: AnimationSpec<Float>) {
+        draggableState.drag {
+            var prevValue = absoluteOffset.value
+            animationTarget.value = target
+            isAnimationRunning = true
+            try {
+                Animatable(prevValue).animateTo(target, spec) {
+                    dragBy(this.value - prevValue)
+                    prevValue = this.value
+                }
+            } finally {
+                animationTarget.value = null
+                isAnimationRunning = false
+            }
+        }
+    }
+
+    /**
+     * The target value of the state.
+     *
+     * If a swipe is in progress, this is the value that the [swipeable] would animate to if the
+     * swipe finished. If an animation is running, this is the target value of that animation.
+     * Finally, if no swipe or animation is in progress, this is the same as the [currentValue].
+     */
+    val targetValue: T
+        get() {
+            // TODO(calintat): Track current velocity (b/149549482) and use that here.
+            val target =
+                animationTarget.value
+                    ?: computeTarget(
+                        offset = offset.value,
+                        lastValue = anchors.getOffset(currentValue) ?: offset.value,
+                        anchors = anchors.keys,
+                        thresholds = thresholds,
+                        velocity = 0f,
+                        velocityThreshold = Float.POSITIVE_INFINITY
+                    )
+            return anchors[target] ?: currentValue
+        }
+
+    /**
+     * Information about the ongoing swipe or animation, if any. See [SwipeProgress] for details.
+     *
+     * If no swipe or animation is in progress, this returns `SwipeProgress(value, value, 1f)`.
+     */
+    val progress: SwipeProgress<T>
+        get() {
+            val bounds = findBounds(offset.value, anchors.keys)
+            val from: T
+            val to: T
+            val fraction: Float
+            when (bounds.size) {
+                0 -> {
+                    from = currentValue
+                    to = currentValue
+                    fraction = 1f
+                }
+                1 -> {
+                    from = anchors.getValue(bounds[0])
+                    to = anchors.getValue(bounds[0])
+                    fraction = 1f
+                }
+                else -> {
+                    val (a, b) =
+                        if (direction > 0f) {
+                            bounds[0] to bounds[1]
+                        } else {
+                            bounds[1] to bounds[0]
+                        }
+                    from = anchors.getValue(a)
+                    to = anchors.getValue(b)
+                    fraction = (offset.value - a) / (b - a)
+                }
+            }
+            return SwipeProgress(from, to, fraction)
+        }
+
+    /**
+     * The direction in which the [swipeable] is moving, relative to the current [currentValue].
+     *
+     * This will be either 1f if it is is moving from left to right or top to bottom, -1f if it is
+     * moving from right to left or bottom to top, or 0f if no swipe or animation is in progress.
+     */
+    val direction: Float
+        get() = anchors.getOffset(currentValue)?.let { sign(offset.value - it) } ?: 0f
+
+    /**
+     * Set the state without any animation and suspend until it's set
+     *
+     * @param targetValue The new target value to set [currentValue] to.
+     */
+    suspend fun snapTo(targetValue: T) {
+        latestNonEmptyAnchorsFlow.collect { anchors ->
+            val targetOffset = anchors.getOffset(targetValue)
+            requireNotNull(targetOffset) { "The target value must have an associated anchor." }
+            snapInternalToOffset(targetOffset)
+            currentValue = targetValue
+        }
+    }
+
+    /**
+     * Set the state to the target value by starting an animation.
+     *
+     * @param targetValue The new value to animate to.
+     * @param anim The animation that will be used to animate to the new value.
+     */
+    suspend fun animateTo(targetValue: T, anim: AnimationSpec<Float> = animationSpec) {
+        latestNonEmptyAnchorsFlow.collect { anchors ->
+            try {
+                val targetOffset = anchors.getOffset(targetValue)
+                requireNotNull(targetOffset) { "The target value must have an associated anchor." }
+                animateInternalToOffset(targetOffset, anim)
+            } finally {
+                val endOffset = absoluteOffset.value
+                val endValue =
+                    anchors
+                        // fighting rounding error once again, anchor should be as close as 0.5
+                        // pixels
+                        .filterKeys { anchorOffset -> abs(anchorOffset - endOffset) < 0.5f }
+                        .values
+                        .firstOrNull()
+                        ?: currentValue
+                currentValue = endValue
+            }
+        }
+    }
+
+    /**
+     * Perform fling with settling to one of the anchors which is determined by the given
+     * [velocity]. Fling with settling [swipeable] will always consume all the velocity provided
+     * since it will settle at the anchor.
+     *
+     * In general cases, [swipeable] flings by itself when being swiped. This method is to be used
+     * for nested scroll logic that wraps the [swipeable]. In nested scroll developer may want to
+     * trigger settling fling when the child scroll container reaches the bound.
+     *
+     * @param velocity velocity to fling and settle with
+     * @return the reason fling ended
+     */
+    suspend fun performFling(velocity: Float) {
+        latestNonEmptyAnchorsFlow.collect { anchors ->
+            val lastAnchor = anchors.getOffset(currentValue)!!
+            val targetValue =
+                computeTarget(
+                    offset = offset.value,
+                    lastValue = lastAnchor,
+                    anchors = anchors.keys,
+                    thresholds = thresholds,
+                    velocity = velocity,
+                    velocityThreshold = velocityThreshold
+                )
+            val targetState = anchors[targetValue]
+            if (targetState != null && confirmStateChange(targetState)) animateTo(targetState)
+            // If the user vetoed the state change, rollback to the previous state.
+            else animateInternalToOffset(lastAnchor, animationSpec)
+        }
+    }
+
+    /**
+     * Force [swipeable] to consume drag delta provided from outside of the regular [swipeable]
+     * gesture flow.
+     *
+     * Note: This method performs generic drag and it won't settle to any particular anchor, *
+     * leaving swipeable in between anchors. When done dragging, [performFling] must be called as
+     * well to ensure swipeable will settle at the anchor.
+     *
+     * In general cases, [swipeable] drags by itself when being swiped. This method is to be used
+     * for nested scroll logic that wraps the [swipeable]. In nested scroll developer may want to
+     * force drag when the child scroll container reaches the bound.
+     *
+     * @param delta delta in pixels to drag by
+     * @return the amount of [delta] consumed
+     */
+    fun performDrag(delta: Float): Float {
+        val potentiallyConsumed = absoluteOffset.value + delta
+        val clamped = potentiallyConsumed.coerceIn(minBound, maxBound)
+        val deltaToConsume = clamped - absoluteOffset.value
+        if (abs(deltaToConsume) > 0) {
+            draggableState.dispatchRawDelta(deltaToConsume)
+        }
+        return deltaToConsume
+    }
+
+    companion object {
+        /** The default [Saver] implementation for [SwipeableState]. */
+        fun <T : Any> Saver(
+            animationSpec: AnimationSpec<Float>,
+            confirmStateChange: (T) -> Boolean
+        ) =
+            Saver<SwipeableState<T>, T>(
+                save = { it.currentValue },
+                restore = { SwipeableState(it, animationSpec, confirmStateChange) }
+            )
+    }
+}
+
+/**
+ * Collects information about the ongoing swipe or animation in [swipeable].
+ *
+ * To access this information, use [SwipeableState.progress].
+ *
+ * @param from The state corresponding to the anchor we are moving away from.
+ * @param to The state corresponding to the anchor we are moving towards.
+ * @param fraction The fraction that the current position represents between [from] and [to]. Must
+ *   be between `0` and `1`.
+ */
+@Immutable
+class SwipeProgress<T>(
+    val from: T,
+    val to: T,
+    /*@FloatRange(from = 0.0, to = 1.0)*/
+    val fraction: Float
+) {
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is SwipeProgress<*>) return false
+
+        if (from != other.from) return false
+        if (to != other.to) return false
+        if (fraction != other.fraction) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = from?.hashCode() ?: 0
+        result = 31 * result + (to?.hashCode() ?: 0)
+        result = 31 * result + fraction.hashCode()
+        return result
+    }
+
+    override fun toString(): String {
+        return "SwipeProgress(from=$from, to=$to, fraction=$fraction)"
+    }
+}
+
+/**
+ * Create and [remember] a [SwipeableState] with the default animation clock.
+ *
+ * @param initialValue The initial value of the state.
+ * @param animationSpec The default animation that will be used to animate to a new state.
+ * @param confirmStateChange Optional callback invoked to confirm or veto a pending state change.
+ */
+@Composable
+fun <T : Any> rememberSwipeableState(
+    initialValue: T,
+    animationSpec: AnimationSpec<Float> = AnimationSpec,
+    confirmStateChange: (newValue: T) -> Boolean = { true }
+): SwipeableState<T> {
+    return rememberSaveable(
+        saver =
+            SwipeableState.Saver(
+                animationSpec = animationSpec,
+                confirmStateChange = confirmStateChange
+            )
+    ) {
+        SwipeableState(
+            initialValue = initialValue,
+            animationSpec = animationSpec,
+            confirmStateChange = confirmStateChange
+        )
+    }
+}
+
+/**
+ * Create and [remember] a [SwipeableState] which is kept in sync with another state, i.e.:
+ * 1. Whenever the [value] changes, the [SwipeableState] will be animated to that new value.
+ * 2. Whenever the value of the [SwipeableState] changes (e.g. after a swipe), the owner of the
+ *    [value] will be notified to update their state to the new value of the [SwipeableState] by
+ *    invoking [onValueChange]. If the owner does not update their state to the provided value for
+ *    some reason, then the [SwipeableState] will perform a rollback to the previous, correct value.
+ */
+@Composable
+internal fun <T : Any> rememberSwipeableStateFor(
+    value: T,
+    onValueChange: (T) -> Unit,
+    animationSpec: AnimationSpec<Float> = AnimationSpec
+): SwipeableState<T> {
+    val swipeableState = remember {
+        SwipeableState(
+            initialValue = value,
+            animationSpec = animationSpec,
+            confirmStateChange = { true }
+        )
+    }
+    val forceAnimationCheck = remember { mutableStateOf(false) }
+    LaunchedEffect(value, forceAnimationCheck.value) {
+        if (value != swipeableState.currentValue) {
+            swipeableState.animateTo(value)
+        }
+    }
+    DisposableEffect(swipeableState.currentValue) {
+        if (value != swipeableState.currentValue) {
+            onValueChange(swipeableState.currentValue)
+            forceAnimationCheck.value = !forceAnimationCheck.value
+        }
+        onDispose {}
+    }
+    return swipeableState
+}
+
+/**
+ * Enable swipe gestures between a set of predefined states.
+ *
+ * To use this, you must provide a map of anchors (in pixels) to states (of type [T]). Note that
+ * this map cannot be empty and cannot have two anchors mapped to the same state.
+ *
+ * When a swipe is detected, the offset of the [SwipeableState] will be updated with the swipe
+ * delta. You should use this offset to move your content accordingly (see `Modifier.offsetPx`).
+ * When the swipe ends, the offset will be animated to one of the anchors and when that anchor is
+ * reached, the value of the [SwipeableState] will also be updated to the state corresponding to the
+ * new anchor. The target anchor is calculated based on the provided positional [thresholds].
+ *
+ * Swiping is constrained between the minimum and maximum anchors. If the user attempts to swipe
+ * past these bounds, a resistance effect will be applied by default. The amount of resistance at
+ * each edge is specified by the [resistance] config. To disable all resistance, set it to `null`.
+ *
+ * For an example of a [swipeable] with three states, see:
+ *
+ * @param T The type of the state.
+ * @param state The state of the [swipeable].
+ * @param anchors Pairs of anchors and states, used to map anchors to states and vice versa.
+ * @param thresholds Specifies where the thresholds between the states are. The thresholds will be
+ *   used to determine which state to animate to when swiping stops. This is represented as a lambda
+ *   that takes two states and returns the threshold between them in the form of a
+ *   [ThresholdConfig]. Note that the order of the states corresponds to the swipe direction.
+ * @param orientation The orientation in which the [swipeable] can be swiped.
+ * @param enabled Whether this [swipeable] is enabled and should react to the user's input.
+ * @param reverseDirection Whether to reverse the direction of the swipe, so a top to bottom swipe
+ *   will behave like bottom to top, and a left to right swipe will behave like right to left.
+ * @param interactionSource Optional [MutableInteractionSource] that will passed on to the internal
+ *   [Modifier.draggable].
+ * @param resistance Controls how much resistance will be applied when swiping past the bounds.
+ * @param velocityThreshold The threshold (in dp per second) that the end velocity has to exceed in
+ *   order to animate to the next state, even if the positional [thresholds] have not been reached.
+ * @sample androidx.compose.material.samples.SwipeableSample
+ */
+fun <T> Modifier.swipeable(
+    state: SwipeableState<T>,
+    anchors: Map<Float, T>,
+    orientation: Orientation,
+    enabled: Boolean = true,
+    reverseDirection: Boolean = false,
+    interactionSource: MutableInteractionSource? = null,
+    thresholds: (from: T, to: T) -> ThresholdConfig = { _, _ -> FixedThreshold(56.dp) },
+    resistance: ResistanceConfig? = resistanceConfig(anchors.keys),
+    velocityThreshold: Dp = VelocityThreshold
+) =
+    composed(
+        inspectorInfo =
+            debugInspectorInfo {
+                name = "swipeable"
+                properties["state"] = state
+                properties["anchors"] = anchors
+                properties["orientation"] = orientation
+                properties["enabled"] = enabled
+                properties["reverseDirection"] = reverseDirection
+                properties["interactionSource"] = interactionSource
+                properties["thresholds"] = thresholds
+                properties["resistance"] = resistance
+                properties["velocityThreshold"] = velocityThreshold
+            }
+    ) {
+        require(anchors.isNotEmpty()) { "You must have at least one anchor." }
+        require(anchors.values.distinct().count() == anchors.size) {
+            "You cannot have two anchors mapped to the same state."
+        }
+        val density = LocalDensity.current
+        state.ensureInit(anchors)
+        LaunchedEffect(anchors, state) {
+            val oldAnchors = state.anchors
+            state.anchors = anchors
+            state.resistance = resistance
+            state.thresholds = { a, b ->
+                val from = anchors.getValue(a)
+                val to = anchors.getValue(b)
+                with(thresholds(from, to)) { density.computeThreshold(a, b) }
+            }
+            with(density) { state.velocityThreshold = velocityThreshold.toPx() }
+            state.processNewAnchors(oldAnchors, anchors)
+        }
+
+        Modifier.draggable(
+            orientation = orientation,
+            enabled = enabled,
+            reverseDirection = reverseDirection,
+            interactionSource = interactionSource,
+            startDragImmediately = state.isAnimationRunning,
+            onDragStopped = { velocity -> launch { state.performFling(velocity) } },
+            state = state.draggableState
+        )
+    }
+
+/**
+ * Interface to compute a threshold between two anchors/states in a [swipeable].
+ *
+ * To define a [ThresholdConfig], consider using [FixedThreshold] and [FractionalThreshold].
+ */
+@Stable
+interface ThresholdConfig {
+    /** Compute the value of the threshold (in pixels), once the values of the anchors are known. */
+    fun Density.computeThreshold(fromValue: Float, toValue: Float): Float
+}
+
+/**
+ * A fixed threshold will be at an [offset] away from the first anchor.
+ *
+ * @param offset The offset (in dp) that the threshold will be at.
+ */
+@Immutable
+data class FixedThreshold(private val offset: Dp) : ThresholdConfig {
+    override fun Density.computeThreshold(fromValue: Float, toValue: Float): Float {
+        return fromValue + offset.toPx() * sign(toValue - fromValue)
+    }
+}
+
+/**
+ * A fractional threshold will be at a [fraction] of the way between the two anchors.
+ *
+ * @param fraction The fraction (between 0 and 1) that the threshold will be at.
+ */
+@Immutable
+data class FractionalThreshold(
+    /*@FloatRange(from = 0.0, to = 1.0)*/
+    private val fraction: Float
+) : ThresholdConfig {
+    override fun Density.computeThreshold(fromValue: Float, toValue: Float): Float {
+        return lerp(fromValue, toValue, fraction)
+    }
+}
+
+/**
+ * Specifies how resistance is calculated in [swipeable].
+ *
+ * There are two things needed to calculate resistance: the resistance basis determines how much
+ * overflow will be consumed to achieve maximum resistance, and the resistance factor determines the
+ * amount of resistance (the larger the resistance factor, the stronger the resistance).
+ *
+ * The resistance basis is usually either the size of the component which [swipeable] is applied to,
+ * or the distance between the minimum and maximum anchors. For a constructor in which the
+ * resistance basis defaults to the latter, consider using [resistanceConfig].
+ *
+ * You may specify different resistance factors for each bound. Consider using one of the default
+ * resistance factors in [SwipeableDefaults]: `StandardResistanceFactor` to convey that the user has
+ * run out of things to see, and `StiffResistanceFactor` to convey that the user cannot swipe this
+ * right now. Also, you can set either factor to 0 to disable resistance at that bound.
+ *
+ * @param basis Specifies the maximum amount of overflow that will be consumed. Must be positive.
+ * @param factorAtMin The factor by which to scale the resistance at the minimum bound. Must not be
+ *   negative.
+ * @param factorAtMax The factor by which to scale the resistance at the maximum bound. Must not be
+ *   negative.
+ */
+@Immutable
+class ResistanceConfig(
+    /*@FloatRange(from = 0.0, fromInclusive = false)*/
+    val basis: Float,
+    /*@FloatRange(from = 0.0)*/
+    val factorAtMin: Float = StandardResistanceFactor,
+    /*@FloatRange(from = 0.0)*/
+    val factorAtMax: Float = StandardResistanceFactor
+) {
+    fun computeResistance(overflow: Float): Float {
+        val factor = if (overflow < 0) factorAtMin else factorAtMax
+        if (factor == 0f) return 0f
+        val progress = (overflow / basis).coerceIn(-1f, 1f)
+        return basis / factor * sin(progress * PI.toFloat() / 2)
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is ResistanceConfig) return false
+
+        if (basis != other.basis) return false
+        if (factorAtMin != other.factorAtMin) return false
+        if (factorAtMax != other.factorAtMax) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = basis.hashCode()
+        result = 31 * result + factorAtMin.hashCode()
+        result = 31 * result + factorAtMax.hashCode()
+        return result
+    }
+
+    override fun toString(): String {
+        return "ResistanceConfig(basis=$basis, factorAtMin=$factorAtMin, factorAtMax=$factorAtMax)"
+    }
+}
+
+/**
+ * Given an offset x and a set of anchors, return a list of anchors:
+ * 1. [ ] if the set of anchors is empty,
+ * 2. [ x' ] if x is equal to one of the anchors, accounting for a small rounding error, where x' is
+ *    x rounded to the exact value of the matching anchor,
+ * 3. [ min ] if min is the minimum anchor and x < min,
+ * 4. [ max ] if max is the maximum anchor and x > max, or
+ * 5. [ a , b ] if a and b are anchors such that a < x < b and b - a is minimal.
+ */
+private fun findBounds(offset: Float, anchors: Set<Float>): List<Float> {
+    // Find the anchors the target lies between with a little bit of rounding error.
+    val a = anchors.filter { it <= offset + 0.001 }.maxOrNull()
+    val b = anchors.filter { it >= offset - 0.001 }.minOrNull()
+
+    return when {
+        a == null ->
+            // case 1 or 3
+            listOfNotNull(b)
+        b == null ->
+            // case 4
+            listOf(a)
+        a == b ->
+            // case 2
+            // Can't return offset itself here since it might not be exactly equal
+            // to the anchor, despite being considered an exact match.
+            listOf(a)
+        else ->
+            // case 5
+            listOf(a, b)
+    }
+}
+
+private fun computeTarget(
+    offset: Float,
+    lastValue: Float,
+    anchors: Set<Float>,
+    thresholds: (Float, Float) -> Float,
+    velocity: Float,
+    velocityThreshold: Float
+): Float {
+    val bounds = findBounds(offset, anchors)
+    return when (bounds.size) {
+        0 -> lastValue
+        1 -> bounds[0]
+        else -> {
+            val lower = bounds[0]
+            val upper = bounds[1]
+            if (lastValue <= offset) {
+                // Swiping from lower to upper (positive).
+                if (velocity >= velocityThreshold) {
+                    return upper
+                } else {
+                    val threshold = thresholds(lower, upper)
+                    if (offset < threshold) lower else upper
+                }
+            } else {
+                // Swiping from upper to lower (negative).
+                if (velocity <= -velocityThreshold) {
+                    return lower
+                } else {
+                    val threshold = thresholds(upper, lower)
+                    if (offset > threshold) upper else lower
+                }
+            }
+        }
+    }
+}
+
+private fun <T> Map<Float, T>.getOffset(state: T): Float? {
+    return entries.firstOrNull { it.value == state }?.key
+}
+
+/** Contains useful defaults for [swipeable] and [SwipeableState]. */
+object SwipeableDefaults {
+    /** The default animation used by [SwipeableState]. */
+    val AnimationSpec = SpringSpec<Float>()
+
+    /** The default velocity threshold (1.8 dp per millisecond) used by [swipeable]. */
+    val VelocityThreshold = 125.dp
+
+    /** A stiff resistance factor which indicates that swiping isn't available right now. */
+    const val StiffResistanceFactor = 20f
+
+    /** A standard resistance factor which indicates that the user has run out of things to see. */
+    const val StandardResistanceFactor = 10f
+
+    /**
+     * The default resistance config used by [swipeable].
+     *
+     * This returns `null` if there is one anchor. If there are at least two anchors, it returns a
+     * [ResistanceConfig] with the resistance basis equal to the distance between the two bounds.
+     */
+    fun resistanceConfig(
+        anchors: Set<Float>,
+        factorAtMin: Float = StandardResistanceFactor,
+        factorAtMax: Float = StandardResistanceFactor
+    ): ResistanceConfig? {
+        return if (anchors.size <= 1) {
+            null
+        } else {
+            val basis = anchors.maxOrNull()!! - anchors.minOrNull()!!
+            ResistanceConfig(basis, factorAtMin, factorAtMax)
+        }
+    }
+}
+
+// temp default nested scroll connection for swipeables which desire as an opt in
+// revisit in b/174756744 as all types will have their own specific connection probably
+internal val <T> SwipeableState<T>.PreUpPostDownNestedScrollConnection: NestedScrollConnection
+    get() =
+        object : NestedScrollConnection {
+            override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+                val delta = available.toFloat()
+                return if (delta < 0 && source == NestedScrollSource.Drag) {
+                    performDrag(delta).toOffset()
+                } else {
+                    Offset.Zero
+                }
+            }
+
+            override fun onPostScroll(
+                consumed: Offset,
+                available: Offset,
+                source: NestedScrollSource
+            ): Offset {
+                return if (source == NestedScrollSource.Drag) {
+                    performDrag(available.toFloat()).toOffset()
+                } else {
+                    Offset.Zero
+                }
+            }
+
+            override suspend fun onPreFling(available: Velocity): Velocity {
+                val toFling = Offset(available.x, available.y).toFloat()
+                return if (toFling < 0 && offset.value > minBound) {
+                    performFling(velocity = toFling)
+                    // since we go to the anchor with tween settling, consume all for the best UX
+                    available
+                } else {
+                    Velocity.Zero
+                }
+            }
+
+            override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
+                performFling(velocity = Offset(available.x, available.y).toFloat())
+                return available
+            }
+
+            private fun Float.toOffset(): Offset = Offset(0f, this)
+
+            private fun Offset.toFloat(): Float = this.y
+        }
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/ui/util/MathHelpers.kt b/packages/SystemUI/compose/core/src/com/android/compose/ui/util/MathHelpers.kt
new file mode 100644
index 0000000..c1defb7
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/ui/util/MathHelpers.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.compose.ui.util
+
+import kotlin.math.roundToInt
+import kotlin.math.roundToLong
+
+// TODO(b/272311106): this is a fork from material. Unfork it when MathHelpers.kt reaches material3.
+
+/** Linearly interpolate between [start] and [stop] with [fraction] fraction between them. */
+fun lerp(start: Float, stop: Float, fraction: Float): Float {
+    return (1 - fraction) * start + fraction * stop
+}
+
+/** Linearly interpolate between [start] and [stop] with [fraction] fraction between them. */
+fun lerp(start: Int, stop: Int, fraction: Float): Int {
+    return start + ((stop - start) * fraction.toDouble()).roundToInt()
+}
+
+/** Linearly interpolate between [start] and [stop] with [fraction] fraction between them. */
+fun lerp(start: Long, stop: Long, fraction: Float): Long {
+    return start + ((stop - start) * fraction.toDouble()).roundToLong()
+}
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
index e253fb9..cc33745 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -21,8 +21,10 @@
 import android.view.View
 import androidx.activity.ComponentActivity
 import androidx.lifecycle.LifecycleOwner
+import com.android.systemui.multishade.ui.viewmodel.MultiShadeViewModel
 import com.android.systemui.people.ui.viewmodel.PeopleViewModel
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
+import com.android.systemui.util.time.SystemClock
 
 /** The Compose facade, when Compose is *not* available. */
 object ComposeFacade : BaseComposeFacade {
@@ -48,6 +50,14 @@
         throwComposeUnavailableError()
     }
 
+    override fun createMultiShadeView(
+        context: Context,
+        viewModel: MultiShadeViewModel,
+        clock: SystemClock,
+    ): View {
+        throwComposeUnavailableError()
+    }
+
     private fun throwComposeUnavailableError(): Nothing {
         error(
             "Compose is not available. Make sure to check isComposeAvailable() before calling any" +
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
index 1ea18fe..0e79b18 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -23,10 +23,13 @@
 import androidx.compose.ui.platform.ComposeView
 import androidx.lifecycle.LifecycleOwner
 import com.android.compose.theme.PlatformTheme
+import com.android.systemui.multishade.ui.composable.MultiShade
+import com.android.systemui.multishade.ui.viewmodel.MultiShadeViewModel
 import com.android.systemui.people.ui.compose.PeopleScreen
 import com.android.systemui.people.ui.viewmodel.PeopleViewModel
 import com.android.systemui.qs.footer.ui.compose.FooterActions
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
+import com.android.systemui.util.time.SystemClock
 
 /** The Compose facade, when Compose is available. */
 object ComposeFacade : BaseComposeFacade {
@@ -51,4 +54,21 @@
             setContent { PlatformTheme { FooterActions(viewModel, qsVisibilityLifecycleOwner) } }
         }
     }
+
+    override fun createMultiShadeView(
+        context: Context,
+        viewModel: MultiShadeViewModel,
+        clock: SystemClock,
+    ): View {
+        return ComposeView(context).apply {
+            setContent {
+                PlatformTheme {
+                    MultiShade(
+                        viewModel = viewModel,
+                        clock = clock,
+                    )
+                }
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.kt
index 772c891..fbd7f83 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.kt
@@ -49,14 +49,12 @@
         // initializer are created once, when the process is started.
         val savedStateRegistryOwner =
             object : SavedStateRegistryOwner {
-                private val savedStateRegistry =
+                private val savedStateRegistryController =
                     SavedStateRegistryController.create(this).apply { performRestore(null) }
 
-                override fun getLifecycle(): Lifecycle = lifecycleOwner.lifecycle
+                override val savedStateRegistry = savedStateRegistryController.savedStateRegistry
 
-                override fun getSavedStateRegistry(): SavedStateRegistry {
-                    return savedStateRegistry.savedStateRegistry
-                }
+                override fun getLifecycle(): Lifecycle = lifecycleOwner.lifecycle
             }
 
         // We must call [ViewLifecycleOwner.onCreate] after creating the [SavedStateRegistryOwner]
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/multishade/ui/composable/MultiShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/multishade/ui/composable/MultiShade.kt
new file mode 100644
index 0000000..b9e38cf
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/multishade/ui/composable/MultiShade.kt
@@ -0,0 +1,144 @@
+/*
+ * 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.composable
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.gestures.detectVerticalDragGestures
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxWithConstraints
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.unit.IntSize
+import com.android.systemui.R
+import com.android.systemui.multishade.shared.model.ProxiedInputModel
+import com.android.systemui.multishade.ui.viewmodel.MultiShadeViewModel
+import com.android.systemui.notifications.ui.composable.Notifications
+import com.android.systemui.qs.footer.ui.compose.QuickSettings
+import com.android.systemui.statusbar.ui.composable.StatusBar
+import com.android.systemui.util.time.SystemClock
+
+@Composable
+fun MultiShade(
+    viewModel: MultiShadeViewModel,
+    clock: SystemClock,
+    modifier: Modifier = Modifier,
+) {
+    val isScrimEnabled: Boolean by viewModel.isScrimEnabled.collectAsState()
+
+    // TODO(b/273298030): find a different way to get the height constraint from its parent.
+    BoxWithConstraints(modifier = modifier) {
+        val maxHeightPx = with(LocalDensity.current) { maxHeight.toPx() }
+
+        Scrim(
+            modifier = Modifier.fillMaxSize(),
+            remoteTouch = viewModel::onScrimTouched,
+            alpha = { viewModel.scrimAlpha.value },
+            isScrimEnabled = isScrimEnabled,
+        )
+        Shade(
+            viewModel = viewModel.leftShade,
+            currentTimeMillis = clock::elapsedRealtime,
+            containerHeightPx = maxHeightPx,
+            modifier = Modifier.align(Alignment.TopStart),
+        ) {
+            Column {
+                StatusBar()
+                Notifications()
+            }
+        }
+        Shade(
+            viewModel = viewModel.rightShade,
+            currentTimeMillis = clock::elapsedRealtime,
+            containerHeightPx = maxHeightPx,
+            modifier = Modifier.align(Alignment.TopEnd),
+        ) {
+            Column {
+                StatusBar()
+                QuickSettings()
+            }
+        }
+        Shade(
+            viewModel = viewModel.singleShade,
+            currentTimeMillis = clock::elapsedRealtime,
+            containerHeightPx = maxHeightPx,
+            modifier = Modifier,
+        ) {
+            Column {
+                StatusBar()
+                Notifications()
+                QuickSettings()
+            }
+        }
+    }
+}
+
+@Composable
+private fun Scrim(
+    remoteTouch: (ProxiedInputModel) -> Unit,
+    alpha: () -> Float,
+    isScrimEnabled: Boolean,
+    modifier: Modifier = Modifier,
+) {
+    var size by remember { mutableStateOf(IntSize.Zero) }
+
+    Box(
+        modifier =
+            modifier
+                .graphicsLayer { this.alpha = alpha() }
+                .background(colorResource(R.color.opaque_scrim))
+                .fillMaxSize()
+                .onSizeChanged { size = it }
+                .then(
+                    if (isScrimEnabled) {
+                        Modifier.pointerInput(Unit) {
+                                detectTapGestures(onTap = { remoteTouch(ProxiedInputModel.OnTap) })
+                            }
+                            .pointerInput(Unit) {
+                                detectVerticalDragGestures(
+                                    onVerticalDrag = { change, dragAmount ->
+                                        remoteTouch(
+                                            ProxiedInputModel.OnDrag(
+                                                xFraction = change.position.x / size.width,
+                                                yDragAmountPx = dragAmount,
+                                            )
+                                        )
+                                    },
+                                    onDragEnd = { remoteTouch(ProxiedInputModel.OnDragEnd) },
+                                    onDragCancel = { remoteTouch(ProxiedInputModel.OnDragCancel) }
+                                )
+                            }
+                    } else {
+                        Modifier
+                    }
+                )
+    )
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/multishade/ui/composable/Shade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/multishade/ui/composable/Shade.kt
new file mode 100644
index 0000000..98ef57f9
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/multishade/ui/composable/Shade.kt
@@ -0,0 +1,317 @@
+/*
+ * 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.composable
+
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.interaction.DragInteraction
+import androidx.compose.foundation.interaction.InteractionSource
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Surface
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.snapshotFlow
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.input.pointer.util.VelocityTracker
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import com.android.compose.modifiers.height
+import com.android.compose.swipeable.FixedThreshold
+import com.android.compose.swipeable.SwipeableState
+import com.android.compose.swipeable.ThresholdConfig
+import com.android.compose.swipeable.rememberSwipeableState
+import com.android.compose.swipeable.swipeable
+import com.android.systemui.multishade.shared.model.ProxiedInputModel
+import com.android.systemui.multishade.ui.viewmodel.ShadeViewModel
+import kotlin.math.roundToInt
+import kotlinx.coroutines.launch
+
+/**
+ * Renders a shade (container and content).
+ *
+ * This should be allowed to grow to fill the width and height of its container.
+ *
+ * @param viewModel The view-model for this shade.
+ * @param currentTimeMillis A provider for the current time, in milliseconds.
+ * @param containerHeightPx The height of the container that this shade is being shown in, in
+ *   pixels.
+ * @param modifier The Modifier.
+ * @param content The content of the shade.
+ */
+@Composable
+fun Shade(
+    viewModel: ShadeViewModel,
+    currentTimeMillis: () -> Long,
+    containerHeightPx: Float,
+    modifier: Modifier = Modifier,
+    content: @Composable () -> Unit = {},
+) {
+    val isVisible: Boolean by viewModel.isVisible.collectAsState()
+    if (!isVisible) {
+        return
+    }
+
+    val interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
+    ReportNonProxiedInput(viewModel, interactionSource)
+
+    val swipeableState = rememberSwipeableState(initialValue = ShadeState.FullyCollapsed)
+    HandleForcedCollapse(viewModel, swipeableState)
+    HandleProxiedInput(viewModel, swipeableState, currentTimeMillis)
+    ReportShadeExpansion(viewModel, swipeableState, containerHeightPx)
+
+    val isSwipingEnabled: Boolean by viewModel.isSwipingEnabled.collectAsState()
+    val collapseThreshold: Float by viewModel.swipeCollapseThreshold.collectAsState()
+    val expandThreshold: Float by viewModel.swipeExpandThreshold.collectAsState()
+
+    val width: ShadeViewModel.Size by viewModel.width.collectAsState()
+    val density = LocalDensity.current
+
+    val anchors: Map<Float, ShadeState> =
+        remember(containerHeightPx) { swipeableAnchors(containerHeightPx) }
+
+    ShadeContent(
+        shadeHeightPx = { swipeableState.offset.value },
+        overstretch = { swipeableState.overflow.value / containerHeightPx },
+        isSwipingEnabled = isSwipingEnabled,
+        swipeableState = swipeableState,
+        interactionSource = interactionSource,
+        anchors = anchors,
+        thresholds = { _, to ->
+            swipeableThresholds(
+                to = to,
+                swipeCollapseThreshold = collapseThreshold.fractionToDp(density, containerHeightPx),
+                swipeExpandThreshold = expandThreshold.fractionToDp(density, containerHeightPx),
+            )
+        },
+        modifier = modifier.shadeWidth(width, density),
+        content = content,
+    )
+}
+
+/**
+ * Draws the content of the shade.
+ *
+ * @param shadeHeightPx Provider for the current expansion of the shade, in pixels, where `0` is
+ *   fully collapsed.
+ * @param overstretch Provider for the current amount of vertical "overstretch" that the shade
+ *   should be rendered with. This is `0` or a positive number that is a percentage of the total
+ *   height of the shade when fully expanded. A value of `0` means that the shade is not stretched
+ *   at all.
+ * @param isSwipingEnabled Whether swiping inside the shade is enabled or not.
+ * @param swipeableState The state to use for the [swipeable] modifier, allowing external control in
+ *   addition to direct control (proxied user input in addition to non-proxied/direct user input).
+ * @param anchors A map of [ShadeState] keyed by the vertical position, in pixels, where that state
+ *   occurs; this is used to configure the [swipeable] modifier.
+ * @param thresholds Function that returns the [ThresholdConfig] for going from one [ShadeState] to
+ *   another. This controls how the [swipeable] decides which [ShadeState] to animate to once the
+ *   user lets go of the shade; e.g. does it animate to fully collapsed or fully expanded.
+ * @param content The content to render inside the shade.
+ * @param modifier The [Modifier].
+ */
+@Composable
+private fun ShadeContent(
+    shadeHeightPx: () -> Float,
+    overstretch: () -> Float,
+    isSwipingEnabled: Boolean,
+    swipeableState: SwipeableState<ShadeState>,
+    interactionSource: MutableInteractionSource,
+    anchors: Map<Float, ShadeState>,
+    thresholds: (from: ShadeState, to: ShadeState) -> ThresholdConfig,
+    modifier: Modifier = Modifier,
+    content: @Composable () -> Unit = {},
+) {
+    Surface(
+        shape = RoundedCornerShape(32.dp),
+        modifier =
+            modifier
+                .padding(12.dp)
+                .fillMaxWidth()
+                .height { shadeHeightPx().roundToInt() }
+                .graphicsLayer {
+                    // Applies the vertical over-stretching of the shade content that may happen if
+                    // the user keep dragging down when the shade is already fully-expanded.
+                    transformOrigin = transformOrigin.copy(pivotFractionY = 0f)
+                    this.scaleY = 1 + overstretch().coerceAtLeast(0f)
+                }
+                .swipeable(
+                    enabled = isSwipingEnabled,
+                    state = swipeableState,
+                    interactionSource = interactionSource,
+                    anchors = anchors,
+                    thresholds = thresholds,
+                    orientation = Orientation.Vertical,
+                ),
+        content = content,
+    )
+}
+
+/** Funnels current shade expansion values into the view-model. */
+@Composable
+private fun ReportShadeExpansion(
+    viewModel: ShadeViewModel,
+    swipeableState: SwipeableState<ShadeState>,
+    containerHeightPx: Float,
+) {
+    LaunchedEffect(swipeableState.offset, containerHeightPx) {
+        snapshotFlow { swipeableState.offset.value / containerHeightPx }
+            .collect { expansion -> viewModel.onExpansionChanged(expansion) }
+    }
+}
+
+/** Funnels drag gesture start and end events into the view-model. */
+@Composable
+private fun ReportNonProxiedInput(
+    viewModel: ShadeViewModel,
+    interactionSource: InteractionSource,
+) {
+    LaunchedEffect(interactionSource) {
+        interactionSource.interactions.collect {
+            when (it) {
+                is DragInteraction.Start -> {
+                    viewModel.onDragStarted()
+                }
+                is DragInteraction.Stop -> {
+                    viewModel.onDragEnded()
+                }
+            }
+        }
+    }
+}
+
+/** When told to force collapse, collapses the shade. */
+@Composable
+private fun HandleForcedCollapse(
+    viewModel: ShadeViewModel,
+    swipeableState: SwipeableState<ShadeState>,
+) {
+    LaunchedEffect(viewModel) {
+        viewModel.isForceCollapsed.collect {
+            launch { swipeableState.animateTo(ShadeState.FullyCollapsed) }
+        }
+    }
+}
+
+/**
+ * Handles proxied input (input originating outside of the UI of the shade) by driving the
+ * [SwipeableState] accordingly.
+ */
+@Composable
+private fun HandleProxiedInput(
+    viewModel: ShadeViewModel,
+    swipeableState: SwipeableState<ShadeState>,
+    currentTimeMillis: () -> Long,
+) {
+    val velocityTracker: VelocityTracker = remember { VelocityTracker() }
+    LaunchedEffect(viewModel) {
+        viewModel.proxiedInput.collect {
+            when (it) {
+                is ProxiedInputModel.OnDrag -> {
+                    velocityTracker.addPosition(
+                        timeMillis = currentTimeMillis.invoke(),
+                        position = Offset(0f, it.yDragAmountPx),
+                    )
+                    swipeableState.performDrag(it.yDragAmountPx)
+                }
+                is ProxiedInputModel.OnDragEnd -> {
+                    launch {
+                        val velocity = velocityTracker.calculateVelocity().y
+                        velocityTracker.resetTracking()
+                        // We use a VelocityTracker to keep a record of how fast the pointer was
+                        // moving such that we know how far to fling the shade when the gesture
+                        // ends. Flinging the SwipeableState using performFling is required after
+                        // one or more calls to performDrag such that the swipeable settles into one
+                        // of the states. Without doing that, the shade would remain unmoving in an
+                        // in-between state on the screen.
+                        swipeableState.performFling(velocity)
+                    }
+                }
+                is ProxiedInputModel.OnDragCancel -> {
+                    launch {
+                        velocityTracker.resetTracking()
+                        swipeableState.animateTo(swipeableState.progress.from)
+                    }
+                }
+                else -> Unit
+            }
+        }
+    }
+}
+
+/**
+ * Converts the [Float] (which is assumed to be a fraction between `0` and `1`) to a value in dp.
+ *
+ * @param density The [Density] of the display.
+ * @param wholePx The whole amount that the given [Float] is a fraction of.
+ * @return The dp size that's a fraction of the whole amount.
+ */
+private fun Float.fractionToDp(density: Density, wholePx: Float): Dp {
+    return with(density) { (this@fractionToDp * wholePx).toDp() }
+}
+
+private fun Modifier.shadeWidth(
+    size: ShadeViewModel.Size,
+    density: Density,
+): Modifier {
+    return then(
+        when (size) {
+            is ShadeViewModel.Size.Fraction -> Modifier.fillMaxWidth(size.fraction)
+            is ShadeViewModel.Size.Pixels -> Modifier.width(with(density) { size.pixels.toDp() })
+        }
+    )
+}
+
+/** Returns the pixel positions for each of the supported shade states. */
+private fun swipeableAnchors(containerHeightPx: Float): Map<Float, ShadeState> {
+    return mapOf(
+        0f to ShadeState.FullyCollapsed,
+        containerHeightPx to ShadeState.FullyExpanded,
+    )
+}
+
+/**
+ * Returns the [ThresholdConfig] for how far the shade should be expanded or collapsed such that it
+ * actually completes the expansion or collapse after the user lifts their pointer.
+ */
+private fun swipeableThresholds(
+    to: ShadeState,
+    swipeExpandThreshold: Dp,
+    swipeCollapseThreshold: Dp,
+): ThresholdConfig {
+    return FixedThreshold(
+        when (to) {
+            ShadeState.FullyExpanded -> swipeExpandThreshold
+            ShadeState.FullyCollapsed -> swipeCollapseThreshold
+        }
+    )
+}
+
+/** Enumerates the shade UI states for [SwipeableState]. */
+private enum class ShadeState {
+    FullyCollapsed,
+    FullyExpanded,
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
new file mode 100644
index 0000000..ca91b8a
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.notifications.ui.composable
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun Notifications(
+    modifier: Modifier = Modifier,
+) {
+    // TODO(b/272779828): implement.
+    Column(
+        modifier = modifier.fillMaxWidth().defaultMinSize(minHeight = 300.dp).padding(4.dp),
+    ) {
+        Text("Notifications", modifier = Modifier.align(Alignment.CenterHorizontally))
+        Spacer(modifier = Modifier.weight(1f))
+        Text("Shelf", modifier = Modifier.align(Alignment.CenterHorizontally))
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt
new file mode 100644
index 0000000..665d6dd
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.footer.ui.compose
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun QuickSettings(
+    modifier: Modifier = Modifier,
+) {
+    // TODO(b/272780058): implement.
+    Column(
+        modifier = modifier.fillMaxWidth().defaultMinSize(minHeight = 300.dp).padding(4.dp),
+    ) {
+        Text("Quick settings", modifier = Modifier.align(Alignment.CenterHorizontally))
+        Spacer(modifier = Modifier.weight(1f))
+        Text("QS footer actions", modifier = Modifier.align(Alignment.CenterHorizontally))
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/ui/composable/StatusBar.kt b/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/ui/composable/StatusBar.kt
new file mode 100644
index 0000000..f514ab4
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/ui/composable/StatusBar.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.statusbar.ui.composable
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun StatusBar(
+    modifier: Modifier = Modifier,
+) {
+    // TODO(b/272780101): implement.
+    Row(
+        modifier = modifier.fillMaxWidth().defaultMinSize(minHeight = 48.dp).padding(4.dp),
+        horizontalArrangement = Arrangement.Center,
+        verticalAlignment = Alignment.CenterVertically,
+    ) {
+        Text("Status bar")
+    }
+}
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/res/drawable/ic_important_outline.xml b/packages/SystemUI/res/drawable/ic_important_outline.xml
index 7a628bb..642582c 100644
--- a/packages/SystemUI/res/drawable/ic_important_outline.xml
+++ b/packages/SystemUI/res/drawable/ic_important_outline.xml
@@ -20,6 +20,7 @@
         android:height="24dp"
         android:viewportWidth="24.0"
         android:viewportHeight="24.0"
+        android:autoMirrored="true"
         android:tint="?android:attr/colorControlNormal">
     <path
         android:fillColor="@android:color/white"
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/config.xml b/packages/SystemUI/res/values/config.xml
index 311990c..e5cd0c5 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -844,4 +844,44 @@
 
     <!-- Configuration to set Learn more in device logs as URL link -->
     <bool name="log_access_confirmation_learn_more_as_link">true</bool>
+
+    <!-- [START] MULTI SHADE -->
+    <!-- Whether the device should use dual shade. If false, the device uses single shade. -->
+    <bool name="dual_shade_enabled">true</bool>
+    <!--
+    When in dual shade, where should the horizontal split be on the screen to help determine whether
+    the user is pulling down the left shade or the right shade. Must be between 0.0 and 1.0,
+    inclusive. In other words: how much of the left-hand side of the screen, when pulled down on,
+    would reveal the left-hand side shade.
+
+    More concretely:
+    A value of 0.67 means that the left two-thirds of the screen are dedicated to the left-hand side
+    shade and the remaining one-third of the screen on the right is dedicated to the right-hand side
+    shade.
+    -->
+    <dimen name="dual_shade_split_fraction">0.67</dimen>
+    <!-- Width of the left-hand side shade. -->
+    <dimen name="left_shade_width">436dp</dimen>
+    <!-- Width of the right-hand side shade. -->
+    <dimen name="right_shade_width">436dp</dimen>
+    <!--
+    Opaque version of the scrim that shows up behind dual shades. The alpha channel is driven
+    programmatically.
+    -->
+    <color name="opaque_scrim">#D9D9D9</color>
+    <!-- Maximum opacity when the scrim that shows up behind the dual shades is fully visible. -->
+    <dimen name="dual_shade_scrim_alpha">0.1</dimen>
+    <!--
+    The amount that the user must swipe down when the shade is fully collapsed to automatically
+    expand once the user lets go of the shade. If the user swipes less than this amount, the shade
+    will automatically revert back to fully collapsed once the user stops swiping.
+    -->
+    <dimen name="shade_swipe_expand_threshold">0.5</dimen>
+    <!--
+    The amount that the user must swipe up when the shade is fully expanded to automatically
+    collapse once the user lets go of the shade. If the user swipes less than this amount, the shade
+    will automatically revert back to fully expanded once the user stops swiping.
+    -->
+    <dimen name="shade_swipe_collapse_threshold">0.5</dimen>
+    <!-- [END] MULTI SHADE -->
 </resources>
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/src/com/android/keyguard/KeyguardMessageArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
index 0e5f8c1..553453d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
@@ -18,13 +18,9 @@
 
 import android.content.Context;
 import android.content.res.TypedArray;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.SystemClock;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.TypedValue;
-import android.view.View;
 import android.view.ViewGroup;
 import android.widget.TextView;
 
@@ -33,22 +29,10 @@
 import com.android.internal.policy.SystemBarUtils;
 import com.android.systemui.R;
 
-import java.lang.ref.WeakReference;
-
 /***
  * Manages a number of views inside of the given layout. See below for a list of widgets.
  */
 public abstract class KeyguardMessageArea extends TextView implements SecurityMessageDisplay {
-    /** Handler token posted with accessibility announcement runnables. */
-    private static final Object ANNOUNCE_TOKEN = new Object();
-
-    /**
-     * Delay before speaking an accessibility announcement. Used to prevent
-     * lift-to-type from interrupting itself.
-     */
-    private static final long ANNOUNCEMENT_DELAY = 250;
-
-    private final Handler mHandler;
 
     private CharSequence mMessage;
     private boolean mIsVisible;
@@ -65,7 +49,6 @@
         super(context, attrs);
         setLayerType(LAYER_TYPE_HARDWARE, null); // work around nested unclipped SaveLayer bug
 
-        mHandler = new Handler(Looper.myLooper());
         onThemeChanged();
     }
 
@@ -127,9 +110,6 @@
     private void securityMessageChanged(CharSequence message) {
         mMessage = message;
         update();
-        mHandler.removeCallbacksAndMessages(ANNOUNCE_TOKEN);
-        mHandler.postAtTime(new AnnounceRunnable(this, getText()), ANNOUNCE_TOKEN,
-                (SystemClock.uptimeMillis() + ANNOUNCEMENT_DELAY));
     }
 
     private void clearMessage() {
@@ -156,25 +136,4 @@
 
     /** Set the text color */
     protected abstract void updateTextColor();
-
-    /**
-     * Runnable used to delay accessibility announcements.
-     */
-    private static class AnnounceRunnable implements Runnable {
-        private final WeakReference<View> mHost;
-        private final CharSequence mTextToAnnounce;
-
-        AnnounceRunnable(View host, CharSequence textToAnnounce) {
-            mHost = new WeakReference<View>(host);
-            mTextToAnnounce = textToAnnounce;
-        }
-
-        @Override
-        public void run() {
-            final View host = mHost.get();
-            if (host != null) {
-                host.announceForAccessibility(mTextToAnnounce);
-            }
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
index 6a92162..c1896fc 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
@@ -18,11 +18,17 @@
 
 import android.content.res.ColorStateList;
 import android.content.res.Configuration;
+import android.text.TextUtils;
+import android.view.View;
+
+import androidx.annotation.VisibleForTesting;
 
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
 import com.android.systemui.util.ViewController;
 
+import java.lang.ref.WeakReference;
+
 import javax.inject.Inject;
 
 /**
@@ -31,8 +37,14 @@
  */
 public class KeyguardMessageAreaController<T extends KeyguardMessageArea>
         extends ViewController<T> {
+    /**
+     * Delay before speaking an accessibility announcement. Used to prevent
+     * lift-to-type from interrupting itself.
+     */
+    private static final long ANNOUNCEMENT_DELAY = 250;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final ConfigurationController mConfigurationController;
+    private final AnnounceRunnable mAnnounceRunnable;
 
     private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
         public void onFinishedGoingToSleep(int why) {
@@ -68,6 +80,7 @@
 
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mConfigurationController = configurationController;
+        mAnnounceRunnable = new AnnounceRunnable(mView);
     }
 
     @Override
@@ -100,6 +113,12 @@
      */
     public void setMessage(CharSequence s, boolean animate) {
         mView.setMessage(s, animate);
+        CharSequence msg = mView.getText();
+        if (!TextUtils.isEmpty(msg)) {
+            mView.removeCallbacks(mAnnounceRunnable);
+            mAnnounceRunnable.setTextToAnnounce(msg);
+            mView.postDelayed(mAnnounceRunnable, ANNOUNCEMENT_DELAY);
+        }
     }
 
     public void setMessage(int resId) {
@@ -134,4 +153,30 @@
                     view, mKeyguardUpdateMonitor, mConfigurationController);
         }
     }
+
+    /**
+     * Runnable used to delay accessibility announcements.
+     */
+    @VisibleForTesting
+    public static class AnnounceRunnable implements Runnable {
+        private final WeakReference<View> mHost;
+        private CharSequence mTextToAnnounce;
+
+        AnnounceRunnable(View host) {
+            mHost = new WeakReference<>(host);
+        }
+
+        /** Sets the text to announce. */
+        public void setTextToAnnounce(CharSequence textToAnnounce) {
+            mTextToAnnounce = textToAnnounce;
+        }
+
+        @Override
+        public void run() {
+            final View host = mHost.get();
+            if (host != null) {
+                host.announceForAccessibility(mTextToAnnounce);
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 98866c6..7255383 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -1066,23 +1066,28 @@
     }
 
     private void reloadColors() {
-        reinflateViewFlipper();
-        mView.reloadColors();
+        reinflateViewFlipper(() -> mView.reloadColors());
     }
 
     /** Handles density or font scale changes. */
     private void onDensityOrFontScaleChanged() {
-        reinflateViewFlipper();
-        mView.onDensityOrFontScaleChanged();
+        reinflateViewFlipper(() -> mView.onDensityOrFontScaleChanged());
     }
 
     /**
      * Reinflate the view flipper child view.
      */
-    public void reinflateViewFlipper() {
+    public void reinflateViewFlipper(
+            KeyguardSecurityViewFlipperController.OnViewInflatedListener onViewInflatedListener) {
         mSecurityViewFlipperController.clearViews();
-        mSecurityViewFlipperController.getSecurityView(mCurrentSecurityMode,
-                mKeyguardSecurityCallback);
+        if (mFeatureFlags.isEnabled(Flags.ASYNC_INFLATE_BOUNCER)) {
+            mSecurityViewFlipperController.asynchronouslyInflateView(mCurrentSecurityMode,
+                    mKeyguardSecurityCallback, onViewInflatedListener);
+        } else {
+            mSecurityViewFlipperController.getSecurityView(mCurrentSecurityMode,
+                    mKeyguardSecurityCallback);
+            onViewInflatedListener.onViewInflated();
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
index 39b567f..68e1dd7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
@@ -19,11 +19,16 @@
 import android.util.Log;
 import android.view.LayoutInflater;
 
+import androidx.annotation.Nullable;
+import androidx.asynclayoutinflater.view.AsyncLayoutInflater;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.keyguard.KeyguardInputViewController.Factory;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
 import com.android.keyguard.dagger.KeyguardBouncerScope;
 import com.android.systemui.R;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.util.ViewController;
 
 import java.util.ArrayList;
@@ -44,18 +49,24 @@
     private final List<KeyguardInputViewController<KeyguardInputView>> mChildren =
             new ArrayList<>();
     private final LayoutInflater mLayoutInflater;
+    private final AsyncLayoutInflater mAsyncLayoutInflater;
     private final EmergencyButtonController.Factory mEmergencyButtonControllerFactory;
     private final Factory mKeyguardSecurityViewControllerFactory;
+    private final FeatureFlags mFeatureFlags;
 
     @Inject
     protected KeyguardSecurityViewFlipperController(KeyguardSecurityViewFlipper view,
             LayoutInflater layoutInflater,
+            AsyncLayoutInflater asyncLayoutInflater,
             KeyguardInputViewController.Factory keyguardSecurityViewControllerFactory,
-            EmergencyButtonController.Factory emergencyButtonControllerFactory) {
+            EmergencyButtonController.Factory emergencyButtonControllerFactory,
+            FeatureFlags featureFlags) {
         super(view);
         mKeyguardSecurityViewControllerFactory = keyguardSecurityViewControllerFactory;
         mLayoutInflater = layoutInflater;
         mEmergencyButtonControllerFactory = emergencyButtonControllerFactory;
+        mAsyncLayoutInflater = asyncLayoutInflater;
+        mFeatureFlags = featureFlags;
     }
 
     @Override
@@ -92,13 +103,12 @@
             }
         }
 
-        if (childController == null
+        if (!mFeatureFlags.isEnabled(Flags.ASYNC_INFLATE_BOUNCER) && childController == null
                 && securityMode != SecurityMode.None && securityMode != SecurityMode.Invalid) {
-
             int layoutId = getLayoutIdFor(securityMode);
             KeyguardInputView view = null;
             if (layoutId != 0) {
-                if (DEBUG) Log.v(TAG, "inflating id = " + layoutId);
+                if (DEBUG) Log.v(TAG, "inflating on main thread id = " + layoutId);
                 view = (KeyguardInputView) mLayoutInflater.inflate(
                         layoutId, mView, false);
                 mView.addView(view);
@@ -119,6 +129,36 @@
         return childController;
     }
 
+    /**
+     * Asynchronously inflate view and then add it to view flipper on the main thread when complete.
+     *
+     * OnInflateFinishedListener will be called on the main thread.
+     *
+     * @param securityMode
+     * @param keyguardSecurityCallback
+     */
+    public void asynchronouslyInflateView(SecurityMode securityMode,
+            KeyguardSecurityCallback keyguardSecurityCallback,
+            @Nullable OnViewInflatedListener onViewInflatedListener) {
+        int layoutId = getLayoutIdFor(securityMode);
+        if (layoutId != 0) {
+            if (DEBUG) Log.v(TAG, "inflating on bg thread id = " + layoutId);
+            mAsyncLayoutInflater.inflate(layoutId, mView,
+                    (view, resId, parent) -> {
+                        mView.addView(view);
+                        KeyguardInputViewController<KeyguardInputView> childController =
+                                mKeyguardSecurityViewControllerFactory.create(
+                                        (KeyguardInputView) view, securityMode,
+                                        keyguardSecurityCallback);
+                        childController.init();
+                        mChildren.add(childController);
+                        if (onViewInflatedListener != null) {
+                            onViewInflatedListener.onViewInflated();
+                        }
+                    });
+        }
+    }
+
     private int getLayoutIdFor(SecurityMode securityMode) {
         switch (securityMode) {
             case Pattern: return R.layout.keyguard_pattern_view;
@@ -162,4 +202,10 @@
             return 0;
         }
     }
+
+    /** Listener to when view has finished inflation. */
+    public interface OnViewInflatedListener {
+        /** Notifies that view has been inflated */
+        void onViewInflated();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags b/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags
index 9c847be..08236b7 100644
--- a/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags
+++ b/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags
@@ -61,3 +61,8 @@
 ##       4: SYSTEM_REGISTER_USER     System sysui registers user's callbacks
 ##       5: SYSTEM_UNREGISTER_USER   System sysui unregisters user's callbacks (after death)
 36060 sysui_recents_connection (type|1),(user|1)
+
+# ---------------------------
+# KeyguardViewMediator.java
+# ---------------------------
+36080 sysui_keyguard (isOccluded|1),(animate|1)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index c31d45f..4aa985b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -198,32 +198,36 @@
     final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            if (mCurrentDialog != null
-                    && Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
+            if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
                 String reason = intent.getStringExtra("reason");
                 reason = (reason != null) ? reason : "unknown";
-                Log.d(TAG, "ACTION_CLOSE_SYSTEM_DIALOGS received, reason: " + reason);
-
-                mCurrentDialog.dismissWithoutCallback(true /* animate */);
-                mCurrentDialog = null;
-
-                for (Callback cb : mCallbacks) {
-                    cb.onBiometricPromptDismissed();
-                }
-
-                try {
-                    if (mReceiver != null) {
-                        mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL,
-                                null /* credentialAttestation */);
-                        mReceiver = null;
-                    }
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Remote exception", e);
-                }
+                closeDioalog(reason);
             }
         }
     };
 
+    private void closeDioalog(String reason) {
+        if (isShowing()) {
+            Log.i(TAG, "Close BP, reason :" + reason);
+            mCurrentDialog.dismissWithoutCallback(true /* animate */);
+            mCurrentDialog = null;
+
+            for (Callback cb : mCallbacks) {
+                cb.onBiometricPromptDismissed();
+            }
+
+            try {
+                if (mReceiver != null) {
+                    mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL,
+                            null /* credentialAttestation */);
+                    mReceiver = null;
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "Remote exception", e);
+            }
+        }
+    }
+
     private void cancelIfOwnerIsNotInForeground() {
         mExecution.assertIsMainThread();
         if (mCurrentDialog != null) {
@@ -546,6 +550,11 @@
         }
     }
 
+    @Override
+    public void handleShowGlobalActionsMenu() {
+        closeDioalog("PowerMenu shown");
+    }
+
     /**
      * @return where the UDFPS exists on the screen in pixels in portrait mode.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
index c0f8549..4173bdc 100644
--- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
+++ b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
@@ -21,8 +21,10 @@
 import android.view.View
 import androidx.activity.ComponentActivity
 import androidx.lifecycle.LifecycleOwner
+import com.android.systemui.multishade.ui.viewmodel.MultiShadeViewModel
 import com.android.systemui.people.ui.viewmodel.PeopleViewModel
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
+import com.android.systemui.util.time.SystemClock
 
 /**
  * A facade to interact with Compose, when it is available.
@@ -57,4 +59,11 @@
         viewModel: FooterActionsViewModel,
         qsVisibilityLifecycleOwner: LifecycleOwner,
     ): View
+
+    /** Create a [View] to represent [viewModel] on screen. */
+    fun createMultiShadeView(
+        context: Context,
+        viewModel: MultiShadeViewModel,
+        clock: SystemClock,
+    ): View
 }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt
index 06d4a08..ce0f2e9 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt
@@ -155,17 +155,18 @@
         d.show()
     }
 
-    private fun turnOnSettingSecurely(settings: List<String>) {
+    private fun turnOnSettingSecurely(settings: List<String>, onComplete: () -> Unit) {
         val action =
             ActivityStarter.OnDismissAction {
                 settings.forEach { setting ->
                     secureSettings.putIntForUser(setting, 1, userTracker.userId)
                 }
+                onComplete()
                 true
             }
         activityStarter.dismissKeyguardThenExecute(
             action,
-            /* cancel */ null,
+            /* cancel */ onComplete,
             /* afterKeyguardGone */ true
         )
     }
@@ -186,7 +187,11 @@
                 if (!showDeviceControlsInLockscreen) {
                     settings.add(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS)
                 }
-                turnOnSettingSecurely(settings)
+                // If we are toggling the flag, we want to call onComplete after the keyguard is
+                // dismissed (and the setting is turned on), to pass the correct value.
+                turnOnSettingSecurely(settings, onComplete)
+            } else {
+                onComplete()
             }
             if (attempts != MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG) {
                 prefs
@@ -194,7 +199,6 @@
                     .putInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG)
                     .apply()
             }
-            onComplete()
         }
 
         override fun onCancel(dialog: DialogInterface?) {
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/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index b054c7e..0be3bb6 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -98,6 +98,7 @@
 import android.view.inputmethod.InputMethodManager;
 import android.view.textclassifier.TextClassificationManager;
 
+import androidx.asynclayoutinflater.view.AsyncLayoutInflater;
 import androidx.core.app.NotificationManagerCompat;
 
 import com.android.internal.app.IBatteryStats;
@@ -395,6 +396,13 @@
         return LayoutInflater.from(context);
     }
 
+    /** */
+    @Provides
+    @Singleton
+    public AsyncLayoutInflater provideAsyncLayoutInflater(Context context) {
+        return new AsyncLayoutInflater(context);
+    }
+
     @Provides
     static MediaProjectionManager provideMediaProjectionManager(Context context) {
         return context.getSystemService(MediaProjectionManager.class);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java
index 244212b..1702eac 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java
@@ -75,6 +75,10 @@
                 Settings.Secure.SCREENSAVER_COMPLICATIONS_ENABLED,
                 settingsObserver,
                 UserHandle.myUserId());
+        mSecureSettings.registerContentObserverForUser(
+                Settings.Secure.SCREENSAVER_HOME_CONTROLS_ENABLED,
+                settingsObserver,
+                UserHandle.myUserId());
         settingsObserver.onChange(false);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
index 9b954f5f..616bd81 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
@@ -23,6 +23,8 @@
 import com.android.systemui.dagger.SystemUIBinder;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dreams.complication.ComplicationLayoutParams;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 
 import javax.inject.Named;
 
@@ -47,17 +49,27 @@
     String DREAM_MEDIA_ENTRY_LAYOUT_PARAMS = "media_entry_layout_params";
 
     int DREAM_CLOCK_TIME_COMPLICATION_WEIGHT = 1;
+    int DREAM_CLOCK_TIME_COMPLICATION_WEIGHT_NO_SMARTSPACE = 2;
     int DREAM_SMARTSPACE_COMPLICATION_WEIGHT = 2;
     int DREAM_MEDIA_COMPLICATION_WEIGHT = 0;
     int DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT = 4;
     int DREAM_MEDIA_ENTRY_COMPLICATION_WEIGHT = 3;
+    int DREAM_WEATHER_COMPLICATION_WEIGHT = 0;
 
     /**
      * Provides layout parameters for the clock time complication.
      */
     @Provides
     @Named(DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS)
-    static ComplicationLayoutParams provideClockTimeLayoutParams() {
+    static ComplicationLayoutParams provideClockTimeLayoutParams(FeatureFlags featureFlags) {
+        if (featureFlags.isEnabled(Flags.HIDE_SMARTSPACE_ON_DREAM_OVERLAY)) {
+            return new ComplicationLayoutParams(0,
+                    ViewGroup.LayoutParams.WRAP_CONTENT,
+                    ComplicationLayoutParams.POSITION_BOTTOM
+                            | ComplicationLayoutParams.POSITION_START,
+                    ComplicationLayoutParams.DIRECTION_END,
+                    DREAM_CLOCK_TIME_COMPLICATION_WEIGHT_NO_SMARTSPACE);
+        }
         return new ComplicationLayoutParams(0,
                 ViewGroup.LayoutParams.WRAP_CONTENT,
                 ComplicationLayoutParams.POSITION_BOTTOM
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
index 63f63a5..78e132f 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
@@ -30,14 +30,15 @@
 import com.android.systemui.plugins.BcSmartspaceDataPlugin
 import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener
 import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
+import com.android.systemui.plugins.BcSmartspaceDataPlugin.UI_SURFACE_DREAM
 import com.android.systemui.smartspace.SmartspacePrecondition
 import com.android.systemui.smartspace.SmartspaceTargetFilter
+import com.android.systemui.smartspace.dagger.SmartspaceModule
 import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_DATA_PLUGIN
 import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_PRECONDITION
 import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_TARGET_FILTER
 import com.android.systemui.smartspace.dagger.SmartspaceViewComponent
 import com.android.systemui.util.concurrency.Execution
-import java.lang.RuntimeException
 import java.util.Optional
 import java.util.concurrent.Executor
 import javax.inject.Inject
@@ -56,13 +57,16 @@
     @Named(DREAM_SMARTSPACE_PRECONDITION) private val precondition: SmartspacePrecondition,
     @Named(DREAM_SMARTSPACE_TARGET_FILTER)
     private val optionalTargetFilter: Optional<SmartspaceTargetFilter>,
-    @Named(DREAM_SMARTSPACE_DATA_PLUGIN) optionalPlugin: Optional<BcSmartspaceDataPlugin>
+    @Named(DREAM_SMARTSPACE_DATA_PLUGIN) optionalPlugin: Optional<BcSmartspaceDataPlugin>,
+    @Named(SmartspaceModule.WEATHER_SMARTSPACE_DATA_PLUGIN)
+    optionalWeatherPlugin: Optional<BcSmartspaceDataPlugin>,
 ) {
     companion object {
         private const val TAG = "DreamSmartspaceCtrlr"
     }
 
     private var session: SmartspaceSession? = null
+    private val weatherPlugin: BcSmartspaceDataPlugin? = optionalWeatherPlugin.orElse(null)
     private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null)
     private var targetFilter: SmartspaceTargetFilter? = optionalTargetFilter.orElse(null)
 
@@ -116,31 +120,54 @@
     private val sessionListener = SmartspaceSession.OnTargetsAvailableListener { targets ->
         execution.assertIsMainThread()
 
+        // The weather data plugin takes unfiltered targets and performs the filtering internally.
+        weatherPlugin?.onTargetsAvailable(targets)
+
         onTargetsAvailableUnfiltered(targets)
         val filteredTargets = targets.filter { targetFilter?.filterSmartspaceTarget(it) ?: true }
         plugin?.onTargetsAvailable(filteredTargets)
     }
 
     /**
+     * Constructs the weather view with custom layout and connects it to the weather plugin.
+     */
+    fun buildAndConnectWeatherView(parent: ViewGroup, customView: View?): View? {
+        return buildAndConnectViewWithPlugin(parent, weatherPlugin, customView)
+    }
+
+    /**
      * Constructs the smartspace view and connects it to the smartspace service.
      */
     fun buildAndConnectView(parent: ViewGroup): View? {
+        return buildAndConnectViewWithPlugin(parent, plugin, null)
+    }
+
+    private fun buildAndConnectViewWithPlugin(
+        parent: ViewGroup,
+        smartspaceDataPlugin: BcSmartspaceDataPlugin?,
+        customView: View?
+    ): View? {
         execution.assertIsMainThread()
 
         if (!precondition.conditionsMet()) {
             throw RuntimeException("Cannot build view when not enabled")
         }
 
-        val view = buildView(parent)
+        val view = buildView(parent, smartspaceDataPlugin, customView)
 
         connectSession()
 
         return view
     }
 
-    private fun buildView(parent: ViewGroup): View? {
-        return if (plugin != null) {
-            var view = smartspaceViewComponentFactory.create(parent, plugin, stateChangeListener)
+    private fun buildView(
+        parent: ViewGroup,
+        smartspaceDataPlugin: BcSmartspaceDataPlugin?,
+        customView: View?
+    ): View? {
+        return if (smartspaceDataPlugin != null) {
+            val view = smartspaceViewComponentFactory.create(parent, smartspaceDataPlugin,
+                stateChangeListener, customView)
                 .getView()
             if (view !is View) {
                 return null
@@ -157,7 +184,10 @@
     }
 
     private fun connectSession() {
-        if (plugin == null || session != null || !hasActiveSessionListeners()) {
+        if (plugin == null && weatherPlugin == null) {
+            return
+        }
+        if (session != null || !hasActiveSessionListeners()) {
             return
         }
 
@@ -166,13 +196,14 @@
         }
 
         val newSession = smartspaceManager.createSmartspaceSession(
-            SmartspaceConfig.Builder(context, "dream").build()
+            SmartspaceConfig.Builder(context, UI_SURFACE_DREAM).build()
         )
         Log.d(TAG, "Starting smartspace session for dream")
         newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener)
         this.session = newSession
 
-        plugin.registerSmartspaceEventNotifier {
+        weatherPlugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
+        plugin?.registerSmartspaceEventNotifier {
                 e ->
             session?.notifySmartspaceEvent(e)
         }
@@ -199,22 +230,47 @@
 
         session = null
 
+        weatherPlugin?.registerSmartspaceEventNotifier(null)
+        weatherPlugin?.onTargetsAvailable(emptyList())
+
         plugin?.registerSmartspaceEventNotifier(null)
         plugin?.onTargetsAvailable(emptyList())
         Log.d(TAG, "Ending smartspace session for dream")
     }
 
     fun addListener(listener: SmartspaceTargetListener) {
+        addAndRegisterListener(listener, plugin)
+    }
+
+    fun removeListener(listener: SmartspaceTargetListener) {
+        removeAndUnregisterListener(listener, plugin)
+    }
+
+    fun addListenerForWeatherPlugin(listener: SmartspaceTargetListener) {
+        addAndRegisterListener(listener, weatherPlugin)
+    }
+
+    fun removeListenerForWeatherPlugin(listener: SmartspaceTargetListener) {
+        removeAndUnregisterListener(listener, weatherPlugin)
+    }
+
+    private fun addAndRegisterListener(
+        listener: SmartspaceTargetListener,
+        smartspaceDataPlugin: BcSmartspaceDataPlugin?
+    ) {
         execution.assertIsMainThread()
-        plugin?.registerListener(listener)
+        smartspaceDataPlugin?.registerListener(listener)
         listeners.add(listener)
 
         connectSession()
     }
 
-    fun removeListener(listener: SmartspaceTargetListener) {
+    private fun removeAndUnregisterListener(
+        listener: SmartspaceTargetListener,
+        smartspaceDataPlugin: BcSmartspaceDataPlugin?
+    ) {
         execution.assertIsMainThread()
-        plugin?.unregisterListener(listener)
+        smartspaceDataPlugin?.unregisterListener(listener)
         listeners.remove(listener)
         disconnect()
     }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
index 5c310c3..2c11d78 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
@@ -86,9 +86,34 @@
     private final ServerFlagReader.ChangeListener mOnPropertiesChanged =
             new ServerFlagReader.ChangeListener() {
                 @Override
-                public void onChange(Flag<?> flag) {
-                    mRestarter.restartSystemUI(
-                            "Server flag change: " + flag.getNamespace() + "." + flag.getName());
+                public void onChange(Flag<?> flag, String value) {
+                    boolean shouldRestart = false;
+                    if (mBooleanFlagCache.containsKey(flag.getName())) {
+                        boolean newValue = value == null ? false : Boolean.parseBoolean(value);
+                        if (mBooleanFlagCache.get(flag.getName()) != newValue) {
+                            shouldRestart = true;
+                        }
+                    } else if (mStringFlagCache.containsKey(flag.getName())) {
+                        String newValue = value == null ? "" : value;
+                        if (mStringFlagCache.get(flag.getName()) != value) {
+                            shouldRestart = true;
+                        }
+                    } else if (mIntFlagCache.containsKey(flag.getName())) {
+                        int newValue = 0;
+                        try {
+                            newValue = value == null ? 0 : Integer.parseInt(value);
+                        } catch (NumberFormatException e) {
+                        }
+                        if (mIntFlagCache.get(flag.getName()) != newValue) {
+                            shouldRestart = true;
+                        }
+                    }
+                    if (shouldRestart) {
+                        mRestarter.restartSystemUI(
+                                "Server flag change: " + flag.getNamespace() + "."
+                                        + flag.getName());
+
+                    }
                 }
             };
 
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
index 9859ff6..9d19a7d 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
@@ -53,13 +53,38 @@
     private final Map<String, Flag<?>> mAllFlags;
     private final Map<String, Boolean> mBooleanCache = new HashMap<>();
     private final Map<String, String> mStringCache = new HashMap<>();
+    private final Map<String, Integer> mIntCache = new HashMap<>();
 
     private final ServerFlagReader.ChangeListener mOnPropertiesChanged =
             new ServerFlagReader.ChangeListener() {
                 @Override
-                public void onChange(Flag<?> flag) {
-                    mRestarter.restartSystemUI(
-                            "Server flag change: " + flag.getNamespace() + "." + flag.getName());
+                public void onChange(Flag<?> flag, String value) {
+                    boolean shouldRestart = false;
+                    if (mBooleanCache.containsKey(flag.getName())) {
+                        boolean newValue = value == null ? false : Boolean.parseBoolean(value);
+                        if (mBooleanCache.get(flag.getName()) != newValue) {
+                            shouldRestart = true;
+                        }
+                    } else if (mStringCache.containsKey(flag.getName())) {
+                        String newValue = value == null ? "" : value;
+                        if (mStringCache.get(flag.getName()) != newValue) {
+                            shouldRestart = true;
+                        }
+                    } else if (mIntCache.containsKey(flag.getName())) {
+                        int newValue = 0;
+                        try {
+                            newValue = value == null ? 0 : Integer.parseInt(value);
+                        } catch (NumberFormatException e) {
+                        }
+                        if (mIntCache.get(flag.getName()) != newValue) {
+                            shouldRestart = true;
+                        }
+                    }
+                    if (shouldRestart) {
+                        mRestarter.restartSystemUI(
+                                "Server flag change: " + flag.getNamespace() + "."
+                                        + flag.getName());
+                    }
                 }
             };
 
@@ -97,68 +122,97 @@
 
     @Override
     public boolean isEnabled(@NotNull ReleasedFlag flag) {
-        return mServerFlagReader.readServerOverride(flag.getNamespace(), flag.getName(), true);
+        // Fill the cache.
+        return isEnabledInternal(flag.getName(),
+                mServerFlagReader.readServerOverride(flag.getNamespace(), flag.getName(), true));
     }
 
     @Override
     public boolean isEnabled(ResourceBooleanFlag flag) {
-        if (!mBooleanCache.containsKey(flag.getName())) {
-            return isEnabled(flag.getName(), mResources.getBoolean(flag.getResourceId()));
-        }
-
-        return mBooleanCache.get(flag.getName());
+        // Fill the cache.
+        return isEnabledInternal(flag.getName(), mResources.getBoolean(flag.getResourceId()));
     }
 
     @Override
     public boolean isEnabled(SysPropBooleanFlag flag) {
-        if (!mBooleanCache.containsKey(flag.getName())) {
-            return isEnabled(
-                    flag.getName(),
-                    mSystemProperties.getBoolean(flag.getName(), flag.getDefault()));
-        }
-
-        return mBooleanCache.get(flag.getName());
+        // Fill the cache.
+        return isEnabledInternal(
+                flag.getName(),
+                mSystemProperties.getBoolean(flag.getName(), flag.getDefault()));
     }
 
-    private boolean isEnabled(String name, boolean defaultValue) {
-        mBooleanCache.put(name, defaultValue);
-        return defaultValue;
+    /**
+     * Checks and fills the boolean cache. This is important, Always call through to this method!
+     *
+     * We use the cache as a way to decide if we need to restart the process when server-side
+     * changes occur.
+     */
+    private boolean isEnabledInternal(String name, boolean defaultValue) {
+        // Fill the cache.
+        if (!mBooleanCache.containsKey(name)) {
+            mBooleanCache.put(name, defaultValue);
+        }
+
+        return mBooleanCache.get(name);
     }
 
     @NonNull
     @Override
     public String getString(@NonNull StringFlag flag) {
-        return getString(flag.getName(), flag.getDefault());
+        // Fill the cache.
+        return getStringInternal(flag.getName(), flag.getDefault());
     }
 
     @NonNull
     @Override
     public String getString(@NonNull ResourceStringFlag flag) {
-        if (!mStringCache.containsKey(flag.getName())) {
-            return getString(flag.getName(),
-                    requireNonNull(mResources.getString(flag.getResourceId())));
-        }
-
-        return mStringCache.get(flag.getName());
+        // Fill the cache.
+        return getStringInternal(flag.getName(),
+                requireNonNull(mResources.getString(flag.getResourceId())));
     }
 
-    private String getString(String name, String defaultValue) {
-        mStringCache.put(name, defaultValue);
-        return defaultValue;
+    /**
+     * Checks and fills the String cache. This is important, Always call through to this method!
+     *
+     * We use the cache as a way to decide if we need to restart the process when server-side
+     * changes occur.
+     */
+    private String getStringInternal(String name, String defaultValue) {
+        if (!mStringCache.containsKey(name)) {
+            mStringCache.put(name, defaultValue);
+        }
+
+        return mStringCache.get(name);
     }
 
     @NonNull
     @Override
     public int getInt(@NonNull IntFlag flag) {
-        return flag.getDefault();
+        // Fill the cache.
+        return getIntInternal(flag.getName(), flag.getDefault());
     }
 
     @NonNull
     @Override
     public int getInt(@NonNull ResourceIntFlag flag) {
+        // Fill the cache.
         return mResources.getInteger(flag.getResourceId());
     }
 
+    /**
+     * Checks and fills the integer cache. This is important, Always call through to this method!
+     *
+     * We use the cache as a way to decide if we need to restart the process when server-side
+     * changes occur.
+     */
+    private int getIntInternal(String name, int defaultValue) {
+        if (!mIntCache.containsKey(name)) {
+            mIntCache.put(name, defaultValue);
+        }
+
+        return mIntCache.get(name);
+    }
+
     @Override
     public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
         pw.println("can override: false");
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index b611f46..661b2b6 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -244,6 +244,11 @@
     @JvmField
     val HIDE_SMARTSPACE_ON_DREAM_OVERLAY = unreleasedFlag(404, "hide_smartspace_on_dream_overlay")
 
+    // TODO(b/271460958): Tracking Bug
+    @JvmField
+    val SHOW_WEATHER_COMPLICATION_ON_DREAM_OVERLAY = unreleasedFlag(405,
+        "show_weather_complication_on_dream_overlay")
+
     // 500 - quick settings
 
     val PEOPLE_TILE = resourceBooleanFlag(502, R.bool.flag_conversations, "people_tile")
@@ -591,8 +596,7 @@
     // 1700 - clipboard
     @JvmField val CLIPBOARD_REMOTE_BEHAVIOR = releasedFlag(1701, "clipboard_remote_behavior")
     // TODO(b/267162944): Tracking bug
-    @JvmField
-    val CLIPBOARD_MINIMIZED_LAYOUT = unreleasedFlag(1702, "clipboard_data_model", teamfood = true)
+    @JvmField val CLIPBOARD_MINIMIZED_LAYOUT = releasedFlag(1702, "clipboard_data_model")
 
     // 1800 - shade container
     @JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt b/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
index 9b748d0..eaf5eac 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
@@ -37,7 +37,7 @@
     fun listenForChanges(values: Collection<Flag<*>>, listener: ChangeListener)
 
     interface ChangeListener {
-        fun onChange(flag: Flag<*>)
+        fun onChange(flag: Flag<*>, value: String?)
     }
 }
 
@@ -67,7 +67,7 @@
                 propLoop@ for (propName in properties.keyset) {
                     for (flag in flags) {
                         if (propName == flag.name) {
-                            listener.onChange(flag)
+                            listener.onChange(flag, properties.getString(propName, null))
                             break@propLoop
                         }
                     }
@@ -144,7 +144,7 @@
         for ((listener, flags) in listeners) {
             flagLoop@ for (flag in flags) {
                 if (name == flag.name) {
-                    listener.onChange(flag)
+                    listener.onChange(flag, if (value) "true" else "false")
                     break@flagLoop
                 }
             }
@@ -159,5 +159,6 @@
         flags: Collection<Flag<*>>,
         listener: ServerFlagReader.ChangeListener
     ) {
+        listeners.add(Pair(listener, flags))
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 377a136..5ecc00f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -118,6 +118,7 @@
 import com.android.systemui.CoreStartable;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.Dumpable;
+import com.android.systemui.EventLogTags;
 import com.android.systemui.R;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.animation.Interpolators;
@@ -1849,6 +1850,8 @@
     private void handleSetOccluded(boolean isOccluded, boolean animate) {
         Trace.beginSection("KeyguardViewMediator#handleSetOccluded");
         Log.d(TAG, "handleSetOccluded(" + isOccluded + ")");
+        EventLogTags.writeSysuiKeyguard(isOccluded ? 1 : 0, animate ? 1 : 0);
+
         mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_TRANSITION_FROM_AOD);
 
         synchronized (KeyguardViewMediator.this) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
index 450fa14..82be009 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
@@ -176,10 +176,10 @@
             return;
         }
 
-        final Intent credential = getKeyguardManager()
+        final Intent confirmCredentialIntent = getKeyguardManager()
                 .createConfirmDeviceCredentialIntent(null, null, getTargetUserId(),
                 true /* disallowBiometricsIfPolicyExists */);
-        if (credential == null) {
+        if (confirmCredentialIntent == null) {
             return;
         }
 
@@ -193,14 +193,18 @@
                 PendingIntent.FLAG_IMMUTABLE, options.toBundle());
 
         if (target != null) {
-            credential.putExtra(Intent.EXTRA_INTENT, target.getIntentSender());
+            confirmCredentialIntent.putExtra(Intent.EXTRA_INTENT, target.getIntentSender());
         }
 
+        // WorkLockActivity is started as a task overlay, so unless credential confirmation is also
+        // started as an overlay, it won't be visible.
         final ActivityOptions launchOptions = ActivityOptions.makeBasic();
         launchOptions.setLaunchTaskId(getTaskId());
         launchOptions.setTaskOverlay(true /* taskOverlay */, true /* canResume */);
+        // Propagate it in case more than one activity is launched.
+        confirmCredentialIntent.putExtra(KeyguardManager.EXTRA_FORCE_TASK_OVERLAY, true);
 
-        startActivityForResult(credential, REQUEST_CODE_CONFIRM_CREDENTIALS,
+        startActivityForResult(confirmCredentialIntent, REQUEST_CODE_CONFIRM_CREDENTIALS,
                 launchOptions.toBundle());
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
index d716784..5fcf105 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
@@ -34,7 +34,6 @@
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.ActivityStarter
 import kotlinx.coroutines.awaitCancellation
-import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.launch
 
@@ -112,16 +111,18 @@
                     launch {
                         viewModel.show.collect {
                             // Reset Security Container entirely.
-                            view.visibility = View.VISIBLE
-                            securityContainerController.onBouncerVisibilityChanged(
-                                /* isVisible= */ true
-                            )
-                            securityContainerController.reinflateViewFlipper()
-                            securityContainerController.showPrimarySecurityScreen(
-                                /* turningOff= */ false
-                            )
-                            securityContainerController.appear()
-                            securityContainerController.onResume(KeyguardSecurityView.SCREEN_ON)
+                            securityContainerController.reinflateViewFlipper {
+                                // Reset Security Container entirely.
+                                view.visibility = View.VISIBLE
+                                securityContainerController.onBouncerVisibilityChanged(
+                                    /* isVisible= */ true
+                                )
+                                securityContainerController.showPrimarySecurityScreen(
+                                    /* turningOff= */ false
+                                )
+                                securityContainerController.appear()
+                                securityContainerController.onResume(KeyguardSecurityView.SCREEN_ON)
+                            }
                         }
                     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
index 68910c6..0656c9b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
@@ -70,7 +70,7 @@
     /** Observe whether we should update fps is showing. */
     val shouldUpdateSideFps: Flow<Unit> =
         merge(
-            interactor.startingToHide,
+            interactor.hide,
             interactor.show,
             interactor.startingDisappearAnimation.filterNotNull().map {}
         )
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index d246b35e..889adc7 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -136,6 +136,14 @@
         return factory.create("NotifSectionLog", 1000 /* maxSize */, false /* systrace */);
     }
 
+    /** Provides a logging buffer for all logs related to remote input controller. */
+    @Provides
+    @SysUISingleton
+    @NotificationRemoteInputLog
+    public static LogBuffer provideNotificationRemoteInputLogBuffer(LogBufferFactory factory) {
+        return factory.create("NotifRemoteInputLog", 50 /* maxSize */, false /* systrace */);
+    }
+
     /** Provides a logging buffer for all logs related to the data layer of notifications. */
     @Provides
     @SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRemoteInputLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRemoteInputLog.kt
new file mode 100644
index 0000000..3a639a0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRemoteInputLog.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.log.dagger
+
+import javax.inject.Qualifier
+
+/** A [com.android.systemui.log.LogBuffer] for NotificationRemoteInput. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class NotificationRemoteInputLog
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index a31c1e5..00e5aac 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -519,16 +519,15 @@
                 mLogger.logTapContentView(mUid, mPackageName, mInstanceId);
                 logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT);
 
-                // See StatusBarNotificationActivityStarter#onNotificationClicked
                 boolean showOverLockscreen = mKeyguardStateController.isShowing()
-                        && mActivityIntentHelper.wouldShowOverLockscreen(clickIntent.getIntent(),
+                        && mActivityIntentHelper.wouldPendingShowOverLockscreen(clickIntent,
                         mLockscreenUserManager.getCurrentUserId());
-
                 if (showOverLockscreen) {
-                    mActivityStarter.startActivity(clickIntent.getIntent(),
-                            /* dismissShade */ true,
-                            /* animationController */ null,
-                            /* showOverLockscreenWhenLocked */ true);
+                    try {
+                        clickIntent.send();
+                    } catch (PendingIntent.CanceledException e) {
+                        Log.e(TAG, "Pending intent for " + key + " was cancelled");
+                    }
                 } else {
                     mActivityStarter.postStartActivityDismissingKeyguard(clickIntent,
                             buildLaunchAnimatorController(mMediaViewHolder.getPlayer()));
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index 00e9a79..b71a9193 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -16,17 +16,16 @@
 
 package com.android.systemui.media.dialog;
 
-import static android.media.RouteListingPreference.Item.SELECTION_BEHAVIOR_GO_TO_APP;
-import static android.media.RouteListingPreference.Item.SELECTION_BEHAVIOR_NONE;
-import static android.media.RouteListingPreference.Item.SELECTION_BEHAVIOR_TRANSFER;
 import static android.media.RouteListingPreference.Item.SUBTEXT_AD_ROUTING_DISALLOWED;
 import static android.media.RouteListingPreference.Item.SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED;
 import static android.media.RouteListingPreference.Item.SUBTEXT_SUBSCRIPTION_REQUIRED;
 
+import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_GO_TO_APP;
+import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_NONE;
+import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER;
+
 import android.content.Context;
 import android.content.res.ColorStateList;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffColorFilter;
 import android.graphics.drawable.AnimatedVectorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
@@ -296,6 +295,8 @@
                             && mController.isAdvancedLayoutSupported()) {
                         //If device is connected and there's other selectable devices, layout as
                         // one of selected devices.
+                        updateTitleIcon(R.drawable.media_output_icon_volume,
+                                mController.getColorItemContent());
                         boolean isDeviceDeselectable = isDeviceIncluded(
                                 mController.getDeselectableMediaDevice(), device);
                         updateGroupableCheckBox(true, isDeviceDeselectable, device);
@@ -371,7 +372,8 @@
             mEndClickIcon.setOnClickListener(null);
             mEndTouchArea.setOnClickListener(null);
             updateEndClickAreaColor(mController.getColorSeekbarProgress());
-            mEndClickIcon.setColorFilter(mController.getColorItemContent());
+            mEndClickIcon.setImageTintList(
+                    ColorStateList.valueOf(mController.getColorItemContent()));
             mEndClickIcon.setOnClickListener(
                     v -> mController.tryToLaunchInAppRoutingIntent(device.getId(), v));
             mEndTouchArea.setOnClickListener(v -> mCheckBox.performClick());
@@ -379,8 +381,8 @@
 
         public void updateEndClickAreaColor(int color) {
             if (mController.isAdvancedLayoutSupported()) {
-                mEndTouchArea.getBackground().setColorFilter(
-                        new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN));
+                mEndTouchArea.setBackgroundTintList(
+                        ColorStateList.valueOf(color));
             }
         }
 
@@ -394,22 +396,22 @@
         private void updateConnectionFailedStatusIcon() {
             mStatusIcon.setImageDrawable(
                     mContext.getDrawable(R.drawable.media_output_status_failed));
-            mStatusIcon.setColorFilter(mController.getColorItemContent());
+            mStatusIcon.setImageTintList(
+                    ColorStateList.valueOf(mController.getColorItemContent()));
         }
 
         private void updateDeviceStatusIcon(Drawable drawable) {
             mStatusIcon.setImageDrawable(drawable);
-            mStatusIcon.setColorFilter(mController.getColorItemContent());
+            mStatusIcon.setImageTintList(
+                    ColorStateList.valueOf(mController.getColorItemContent()));
             if (drawable instanceof AnimatedVectorDrawable) {
                 ((AnimatedVectorDrawable) drawable).start();
             }
         }
 
         private void updateProgressBarColor() {
-            mProgressBar.getIndeterminateDrawable().setColorFilter(
-                    new PorterDuffColorFilter(
-                            mController.getColorItemContent(),
-                            PorterDuff.Mode.SRC_IN));
+            mProgressBar.getIndeterminateDrawable().setTintList(
+                    ColorStateList.valueOf(mController.getColorItemContent()));
         }
 
         public void updateEndClickArea(MediaDevice device, boolean isDeviceDeselectable) {
@@ -419,9 +421,8 @@
             mEndTouchArea.setImportantForAccessibility(
                     View.IMPORTANT_FOR_ACCESSIBILITY_YES);
             if (mController.isAdvancedLayoutSupported()) {
-                mEndTouchArea.getBackground().setColorFilter(
-                        new PorterDuffColorFilter(mController.getColorItemBackground(),
-                                PorterDuff.Mode.SRC_IN));
+                mEndTouchArea.setBackgroundTintList(
+                        ColorStateList.valueOf(mController.getColorItemBackground()));
             }
             setUpContentDescriptionForView(mEndTouchArea, true, device);
         }
@@ -450,11 +451,11 @@
                 setSingleLineLayout(mContext.getText(R.string.media_output_dialog_pairing_new));
                 final Drawable addDrawable = mContext.getDrawable(R.drawable.ic_add);
                 mTitleIcon.setImageDrawable(addDrawable);
-                mTitleIcon.setColorFilter(mController.getColorItemContent());
+                mTitleIcon.setImageTintList(
+                        ColorStateList.valueOf(mController.getColorItemContent()));
                 if (mController.isAdvancedLayoutSupported()) {
-                    mIconAreaLayout.getBackground().setColorFilter(
-                            new PorterDuffColorFilter(mController.getColorItemBackground(),
-                                    PorterDuff.Mode.SRC_IN));
+                    mIconAreaLayout.setBackgroundTintList(
+                            ColorStateList.valueOf(mController.getColorItemBackground()));
                 }
                 mContainerLayout.setOnClickListener(mController::launchBluetoothPairing);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index 2a2cf63..f76f049 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -23,8 +23,7 @@
 import android.annotation.DrawableRes;
 import android.app.WallpaperColors;
 import android.content.Context;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffColorFilter;
+import android.content.res.ColorStateList;
 import android.graphics.Typeface;
 import android.graphics.drawable.ClipDrawable;
 import android.graphics.drawable.Drawable;
@@ -196,9 +195,8 @@
                 mIconAreaLayout.setOnClickListener(null);
                 mVolumeValueText.setTextColor(mController.getColorItemContent());
             }
-            mSeekBar.getProgressDrawable().setColorFilter(
-                    new PorterDuffColorFilter(mController.getColorSeekbarProgress(),
-                            PorterDuff.Mode.SRC_IN));
+            mSeekBar.setProgressTintList(
+                    ColorStateList.valueOf(mController.getColorSeekbarProgress()));
         }
 
         abstract void onBind(int customizedItem);
@@ -224,16 +222,14 @@
                     updateSeekbarProgressBackground();
                 }
             }
-            mItemLayout.getBackground().setColorFilter(new PorterDuffColorFilter(
-                    isActive ? mController.getColorConnectedItemBackground()
-                            : mController.getColorItemBackground(),
-                    PorterDuff.Mode.SRC_IN));
+            mItemLayout.setBackgroundTintList(
+                    ColorStateList.valueOf(isActive ? mController.getColorConnectedItemBackground()
+                            : mController.getColorItemBackground()));
             if (mController.isAdvancedLayoutSupported()) {
-                mIconAreaLayout.getBackground().setColorFilter(new PorterDuffColorFilter(
-                        showSeekBar ? mController.getColorSeekbarProgress()
+                mIconAreaLayout.setBackgroundTintList(
+                        ColorStateList.valueOf(showSeekBar ? mController.getColorSeekbarProgress()
                                 : showProgressBar ? mController.getColorConnectedItemBackground()
-                                        : mController.getColorItemBackground(),
-                        PorterDuff.Mode.SRC_IN));
+                                        : mController.getColorItemBackground()));
             }
             mProgressBar.setVisibility(showProgressBar ? View.VISIBLE : View.GONE);
             mSeekBar.setAlpha(1);
@@ -251,7 +247,8 @@
                 params.rightMargin = showEndTouchArea ? mController.getItemMarginEndSelectable()
                         : mController.getItemMarginEndDefault();
             }
-            mTitleIcon.setColorFilter(mController.getColorItemContent());
+            mTitleIcon.setBackgroundTintList(
+                    ColorStateList.valueOf(mController.getColorItemContent()));
         }
 
         void setTwoLineLayout(MediaDevice device, boolean bFocused, boolean showSeekBar,
@@ -274,15 +271,14 @@
                 backgroundDrawable = mContext.getDrawable(
                         showSeekBar ? R.drawable.media_output_item_background_active
                                 : R.drawable.media_output_item_background).mutate();
-                backgroundDrawable.setColorFilter(new PorterDuffColorFilter(
+                backgroundDrawable.setTint(
                         showSeekBar ? mController.getColorConnectedItemBackground()
-                                : mController.getColorItemBackground(), PorterDuff.Mode.SRC_IN));
-                mIconAreaLayout.getBackground().setColorFilter(new PorterDuffColorFilter(
-                        showProgressBar || isFakeActive
+                                : mController.getColorItemBackground());
+                mIconAreaLayout.setBackgroundTintList(
+                        ColorStateList.valueOf(showProgressBar || isFakeActive
                                 ? mController.getColorConnectedItemBackground()
                                 : showSeekBar ? mController.getColorSeekbarProgress()
-                                        : mController.getColorItemBackground(),
-                        PorterDuff.Mode.SRC_IN));
+                                        : mController.getColorItemBackground()));
                 if (showSeekBar) {
                     updateSeekbarProgressBackground();
                 }
@@ -297,9 +293,7 @@
                 backgroundDrawable = mContext.getDrawable(
                                 R.drawable.media_output_item_background)
                         .mutate();
-                backgroundDrawable.setColorFilter(new PorterDuffColorFilter(
-                        mController.getColorItemBackground(),
-                        PorterDuff.Mode.SRC_IN));
+                backgroundDrawable.setTint(mController.getColorItemBackground());
             }
             mItemLayout.setBackground(backgroundDrawable);
             mProgressBar.setVisibility(showProgressBar ? View.VISIBLE : View.GONE);
@@ -442,11 +436,10 @@
 
         void updateTitleIcon(@DrawableRes int id, int color) {
             mTitleIcon.setImageDrawable(mContext.getDrawable(id));
-            mTitleIcon.setColorFilter(color);
+            mTitleIcon.setImageTintList(ColorStateList.valueOf(color));
             if (mController.isAdvancedLayoutSupported()) {
-                mIconAreaLayout.getBackground().setColorFilter(
-                        new PorterDuffColorFilter(mController.getColorSeekbarProgress(),
-                                PorterDuff.Mode.SRC_IN));
+                mIconAreaLayout.setBackgroundTintList(
+                        ColorStateList.valueOf(mController.getColorSeekbarProgress()));
             }
         }
 
@@ -462,9 +455,7 @@
             final Drawable backgroundDrawable = mContext.getDrawable(
                                     R.drawable.media_output_item_background_active)
                             .mutate();
-            backgroundDrawable.setColorFilter(
-                    new PorterDuffColorFilter(mController.getColorConnectedItemBackground(),
-                            PorterDuff.Mode.SRC_IN));
+            backgroundDrawable.setTint(mController.getColorConnectedItemBackground());
             mItemLayout.setBackground(backgroundDrawable);
         }
 
@@ -539,10 +530,8 @@
         Drawable getSpeakerDrawable() {
             final Drawable drawable = mContext.getDrawable(R.drawable.ic_speaker_group_black_24dp)
                     .mutate();
-            drawable.setColorFilter(
-                    new PorterDuffColorFilter(Utils.getColorStateListDefaultColor(mContext,
-                            R.color.media_dialog_item_main_content),
-                            PorterDuff.Mode.SRC_IN));
+            drawable.setTint(Utils.getColorStateListDefaultColor(mContext,
+                    R.color.media_dialog_item_main_content));
             return drawable;
         }
 
@@ -574,7 +563,9 @@
                         return;
                     }
                     mTitleIcon.setImageIcon(icon);
-                    mTitleIcon.setColorFilter(mController.getColorItemContent());
+                    icon.setTint(mController.getColorItemContent());
+                    mTitleIcon.setImageTintList(
+                            ColorStateList.valueOf(mController.getColorItemContent()));
                 });
             });
         }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java
index 2250d72..39d4e6e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java
@@ -80,6 +80,10 @@
             Log.d(TAG, "logOutputSuccess - selected device: " + selectedDeviceType);
         }
 
+        if (mSourceDevice == null && mTargetDevice == null) {
+            return;
+        }
+
         updateLoggingDeviceCount(deviceList);
 
         SysUiStatsLog.write(
@@ -105,6 +109,10 @@
             Log.d(TAG, "logOutputSuccess - selected device: " + selectedDeviceType);
         }
 
+        if (mSourceDevice == null && mTargetDevice == null) {
+            return;
+        }
+
         updateLoggingMediaItemCount(deviceItemList);
 
         SysUiStatsLog.write(
@@ -176,6 +184,10 @@
             Log.e(TAG, "logRequestFailed - " + reason);
         }
 
+        if (mSourceDevice == null && mTargetDevice == null) {
+            return;
+        }
+
         updateLoggingDeviceCount(deviceList);
 
         SysUiStatsLog.write(
@@ -201,6 +213,10 @@
             Log.e(TAG, "logRequestFailed - " + reason);
         }
 
+        if (mSourceDevice == null && mTargetDevice == null) {
+            return;
+        }
+
         updateLoggingMediaItemCount(deviceItemList);
 
         SysUiStatsLog.write(
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/data/model/MultiShadeInteractionModel.kt b/packages/SystemUI/src/com/android/systemui/multishade/data/model/MultiShadeInteractionModel.kt
new file mode 100644
index 0000000..c48028c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/multishade/data/model/MultiShadeInteractionModel.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.data.model
+
+import com.android.systemui.multishade.shared.model.ShadeId
+
+/** Models the current interaction with one of the shades. */
+data class MultiShadeInteractionModel(
+    /** The ID of the shade that the user is currently interacting with. */
+    val shadeId: ShadeId,
+    /** Whether the interaction is proxied (as in: coming from an external app or different UI). */
+    val isProxied: Boolean,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/data/remoteproxy/MultiShadeInputProxy.kt b/packages/SystemUI/src/com/android/systemui/multishade/data/remoteproxy/MultiShadeInputProxy.kt
new file mode 100644
index 0000000..86f0c0d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/multishade/data/remoteproxy/MultiShadeInputProxy.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.systemui.multishade.data.remoteproxy
+
+import com.android.systemui.multishade.shared.model.ProxiedInputModel
+import javax.inject.Inject
+import javax.inject.Singleton
+import kotlinx.coroutines.channels.BufferOverflow
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.asSharedFlow
+
+/**
+ * Acts as a hub for routing proxied user input into the multi shade system.
+ *
+ * "Proxied" user input is coming through a proxy; typically from an external app or different UI.
+ * In other words: it's not user input that's occurring directly on the shade UI itself. This class
+ * is that proxy.
+ */
+@Singleton
+class MultiShadeInputProxy @Inject constructor() {
+    private val _proxiedTouch =
+        MutableSharedFlow<ProxiedInputModel>(
+            replay = 1,
+            onBufferOverflow = BufferOverflow.DROP_OLDEST,
+        )
+    val proxiedInput: Flow<ProxiedInputModel> = _proxiedTouch.asSharedFlow()
+
+    fun onProxiedInput(proxiedInput: ProxiedInputModel) {
+        _proxiedTouch.tryEmit(proxiedInput)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/data/repository/MultiShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/multishade/data/repository/MultiShadeRepository.kt
new file mode 100644
index 0000000..1172030
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/multishade/data/repository/MultiShadeRepository.kt
@@ -0,0 +1,157 @@
+/*
+ * 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.data.repository
+
+import android.content.Context
+import androidx.annotation.FloatRange
+import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.multishade.data.model.MultiShadeInteractionModel
+import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
+import com.android.systemui.multishade.shared.model.ProxiedInputModel
+import com.android.systemui.multishade.shared.model.ShadeConfig
+import com.android.systemui.multishade.shared.model.ShadeId
+import com.android.systemui.multishade.shared.model.ShadeModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** Encapsulates application state for all shades. */
+@SysUISingleton
+class MultiShadeRepository
+@Inject
+constructor(
+    @Application private val applicationContext: Context,
+    inputProxy: MultiShadeInputProxy,
+) {
+    /**
+     * Remote input coming from sources outside of system UI (for example, swiping down on the
+     * Launcher or from the status bar).
+     */
+    val proxiedInput: Flow<ProxiedInputModel> = inputProxy.proxiedInput
+
+    /** Width of the left-hand side shade, in pixels. */
+    private val leftShadeWidthPx =
+        applicationContext.resources.getDimensionPixelSize(R.dimen.left_shade_width)
+
+    /** Width of the right-hand side shade, in pixels. */
+    private val rightShadeWidthPx =
+        applicationContext.resources.getDimensionPixelSize(R.dimen.right_shade_width)
+
+    /**
+     * The amount that the user must swipe up when the shade is fully expanded to automatically
+     * collapse once the user lets go of the shade. If the user swipes less than this amount, the
+     * shade will automatically revert back to fully expanded once the user stops swiping.
+     *
+     * This is a fraction between `0` and `1`.
+     */
+    private val swipeCollapseThreshold =
+        checkInBounds(applicationContext.resources.getFloat(R.dimen.shade_swipe_collapse_threshold))
+
+    /**
+     * The amount that the user must swipe down when the shade is fully collapsed to automatically
+     * expand once the user lets go of the shade. If the user swipes less than this amount, the
+     * shade will automatically revert back to fully collapsed once the user stops swiping.
+     *
+     * This is a fraction between `0` and `1`.
+     */
+    private val swipeExpandThreshold =
+        checkInBounds(applicationContext.resources.getFloat(R.dimen.shade_swipe_expand_threshold))
+
+    /**
+     * Maximum opacity when the scrim that shows up behind the dual shades is fully visible.
+     *
+     * This is a fraction between `0` and `1`.
+     */
+    private val dualShadeScrimAlpha =
+        checkInBounds(applicationContext.resources.getFloat(R.dimen.dual_shade_scrim_alpha))
+
+    /** The current configuration of the shade system. */
+    val shadeConfig: StateFlow<ShadeConfig> =
+        MutableStateFlow(
+                if (applicationContext.resources.getBoolean(R.bool.dual_shade_enabled)) {
+                    ShadeConfig.DualShadeConfig(
+                        leftShadeWidthPx = leftShadeWidthPx,
+                        rightShadeWidthPx = rightShadeWidthPx,
+                        swipeCollapseThreshold = swipeCollapseThreshold,
+                        swipeExpandThreshold = swipeExpandThreshold,
+                        splitFraction =
+                            applicationContext.resources.getFloat(
+                                R.dimen.dual_shade_split_fraction
+                            ),
+                        scrimAlpha = dualShadeScrimAlpha,
+                    )
+                } else {
+                    ShadeConfig.SingleShadeConfig(
+                        swipeCollapseThreshold = swipeCollapseThreshold,
+                        swipeExpandThreshold = swipeExpandThreshold,
+                    )
+                }
+            )
+            .asStateFlow()
+
+    private val _forceCollapseAll = MutableStateFlow(false)
+    /** Whether all shades should be collapsed. */
+    val forceCollapseAll: StateFlow<Boolean> = _forceCollapseAll.asStateFlow()
+
+    private val _shadeInteraction = MutableStateFlow<MultiShadeInteractionModel?>(null)
+    /** The current shade interaction or `null` if no shade is interacted with currently. */
+    val shadeInteraction: StateFlow<MultiShadeInteractionModel?> = _shadeInteraction.asStateFlow()
+
+    private val stateByShade = mutableMapOf<ShadeId, MutableStateFlow<ShadeModel>>()
+
+    /** The model for the shade with the given ID. */
+    fun getShade(
+        shadeId: ShadeId,
+    ): StateFlow<ShadeModel> {
+        return getMutableShade(shadeId).asStateFlow()
+    }
+
+    /** Sets the expansion amount for the shade with the given ID. */
+    fun setExpansion(
+        shadeId: ShadeId,
+        @FloatRange(from = 0.0, to = 1.0) expansion: Float,
+    ) {
+        getMutableShade(shadeId).let { mutableState ->
+            mutableState.value = mutableState.value.copy(expansion = expansion)
+        }
+    }
+
+    /** Sets whether all shades should be immediately forced to collapse. */
+    fun setForceCollapseAll(isForced: Boolean) {
+        _forceCollapseAll.value = isForced
+    }
+
+    /** Sets the current shade interaction; use `null` if no shade is interacted with currently. */
+    fun setShadeInteraction(shadeInteraction: MultiShadeInteractionModel?) {
+        _shadeInteraction.value = shadeInteraction
+    }
+
+    private fun getMutableShade(id: ShadeId): MutableStateFlow<ShadeModel> {
+        return stateByShade.getOrPut(id) { MutableStateFlow(ShadeModel(id)) }
+    }
+
+    /** Asserts that the given [Float] is in the range of `0` and `1`, inclusive. */
+    private fun checkInBounds(float: Float): Float {
+        check(float in 0f..1f) { "$float isn't between 0 and 1." }
+        return float
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractor.kt
new file mode 100644
index 0000000..b9f6d83
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractor.kt
@@ -0,0 +1,322 @@
+/*
+ * 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.domain.interactor
+
+import androidx.annotation.FloatRange
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.multishade.data.model.MultiShadeInteractionModel
+import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
+import com.android.systemui.multishade.data.repository.MultiShadeRepository
+import com.android.systemui.multishade.shared.model.ProxiedInputModel
+import com.android.systemui.multishade.shared.model.ShadeConfig
+import com.android.systemui.multishade.shared.model.ShadeId
+import com.android.systemui.multishade.shared.model.ShadeModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.yield
+
+/** Encapsulates business logic related to interactions with the multi-shade system. */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class MultiShadeInteractor
+@Inject
+constructor(
+    @Application private val applicationScope: CoroutineScope,
+    private val repository: MultiShadeRepository,
+    private val inputProxy: MultiShadeInputProxy,
+) {
+    /** The current configuration of the shade system. */
+    val shadeConfig: StateFlow<ShadeConfig> = repository.shadeConfig
+
+    /** The expansion of the shade that's most expanded. */
+    val maxShadeExpansion: Flow<Float> =
+        repository.shadeConfig.flatMapLatest { shadeConfig ->
+            combine(allShades(shadeConfig)) { shadeModels ->
+                shadeModels.maxOfOrNull { it.expansion } ?: 0f
+            }
+        }
+
+    /**
+     * A _processed_ version of the proxied input flow.
+     *
+     * All internal dependencies on the proxied input flow *must* use this one for two reasons:
+     * 1. It's a [SharedFlow] so we only do the upstream work once, no matter how many usages we
+     *    actually have.
+     * 2. It actually does some preprocessing as the proxied input events stream through, handling
+     *    common things like recording the current state of the system based on incoming input
+     *    events.
+     */
+    private val processedProxiedInput: SharedFlow<ProxiedInputModel> =
+        combine(
+                repository.shadeConfig,
+                repository.proxiedInput.distinctUntilChanged(),
+                ::Pair,
+            )
+            .map { (shadeConfig, proxiedInput) ->
+                if (proxiedInput !is ProxiedInputModel.OnTap) {
+                    // If the user is interacting with any other gesture type (for instance,
+                    // dragging),
+                    // we no longer want to force collapse all shades.
+                    repository.setForceCollapseAll(false)
+                }
+
+                when (proxiedInput) {
+                    is ProxiedInputModel.OnDrag -> {
+                        val affectedShadeId = affectedShadeId(shadeConfig, proxiedInput.xFraction)
+                        // This might be the start of a new drag gesture, let's update our
+                        // application
+                        // state to record that fact.
+                        onUserInteractionStarted(
+                            shadeId = affectedShadeId,
+                            isProxied = true,
+                        )
+                    }
+                    is ProxiedInputModel.OnTap -> {
+                        // Tapping outside any shade collapses all shades. This code path is not hit
+                        // for
+                        // taps that happen _inside_ a shade as that input event is directly applied
+                        // through the UI and is, hence, not a proxied input.
+                        collapseAll()
+                    }
+                    else -> Unit
+                }
+
+                proxiedInput
+            }
+            .shareIn(
+                scope = applicationScope,
+                started = SharingStarted.Eagerly,
+                replay = 1,
+            )
+
+    /** Whether the shade with the given ID should be visible. */
+    fun isVisible(shadeId: ShadeId): Flow<Boolean> {
+        return repository.shadeConfig.map { shadeConfig -> shadeConfig.shadeIds.contains(shadeId) }
+    }
+
+    /** Whether direct user input is allowed on the shade with the given ID. */
+    fun isNonProxiedInputAllowed(shadeId: ShadeId): Flow<Boolean> {
+        return combine(
+                isForceCollapsed(shadeId),
+                repository.shadeInteraction,
+                ::Pair,
+            )
+            .map { (isForceCollapsed, shadeInteraction) ->
+                !isForceCollapsed && shadeInteraction?.isProxied != true
+            }
+    }
+
+    /** Whether the shade with the given ID is forced to collapse. */
+    fun isForceCollapsed(shadeId: ShadeId): Flow<Boolean> {
+        return combine(
+                repository.forceCollapseAll,
+                repository.shadeInteraction.map { it?.shadeId },
+                ::Pair,
+            )
+            .map { (collapseAll, userInteractedShadeIdOrNull) ->
+                val counterpartShadeIdOrNull =
+                    when (shadeId) {
+                        ShadeId.SINGLE -> null
+                        ShadeId.LEFT -> ShadeId.RIGHT
+                        ShadeId.RIGHT -> ShadeId.LEFT
+                    }
+
+                when {
+                    // If all shades have been told to collapse (by a tap outside, for example),
+                    // then this shade is collapsed.
+                    collapseAll -> true
+                    // A shade that doesn't have a counterpart shade cannot be force-collapsed by
+                    // interactions on the counterpart shade.
+                    counterpartShadeIdOrNull == null -> false
+                    // If the current user interaction is on the counterpart shade, then this shade
+                    // should be force-collapsed.
+                    else -> userInteractedShadeIdOrNull == counterpartShadeIdOrNull
+                }
+            }
+    }
+
+    /**
+     * Proxied input affecting the shade with the given ID. This is input coming from sources
+     * outside of system UI (for example, swiping down on the Launcher or from the status bar) or
+     * outside the UI of any shade (for example, the scrim that's shown behind the shades).
+     */
+    fun proxiedInput(shadeId: ShadeId): Flow<ProxiedInputModel?> {
+        return combine(
+                processedProxiedInput,
+                isForceCollapsed(shadeId).distinctUntilChanged(),
+                repository.shadeInteraction,
+                ::Triple,
+            )
+            .map { (proxiedInput, isForceCollapsed, shadeInteraction) ->
+                when {
+                    // If the shade is force-collapsed, we ignored proxied input on it.
+                    isForceCollapsed -> null
+                    // If the proxied input does not belong to this shade, ignore it.
+                    shadeInteraction?.shadeId != shadeId -> null
+                    // If there is ongoing non-proxied user input on any shade, ignore the
+                    // proxied input.
+                    !shadeInteraction.isProxied -> null
+                    // Otherwise, send the proxied input downstream.
+                    else -> proxiedInput
+                }
+            }
+            .onEach { proxiedInput ->
+                // We use yield() to make sure that the following block of code happens _after_
+                // downstream collectors had a chance to process the proxied input. Otherwise, we
+                // might change our state to clear the current UserInteraction _before_ those
+                // downstream collectors get a chance to process the proxied input, which will make
+                // them ignore it (since they ignore proxied input when the current user interaction
+                // doesn't match their shade).
+                yield()
+
+                if (
+                    proxiedInput is ProxiedInputModel.OnDragEnd ||
+                        proxiedInput is ProxiedInputModel.OnDragCancel
+                ) {
+                    onUserInteractionEnded(shadeId = shadeId, isProxied = true)
+                }
+            }
+    }
+
+    /** Sets the expansion amount for the shade with the given ID. */
+    fun setExpansion(
+        shadeId: ShadeId,
+        @FloatRange(from = 0.0, to = 1.0) expansion: Float,
+    ) {
+        repository.setExpansion(shadeId, expansion)
+    }
+
+    /** Collapses all shades. */
+    fun collapseAll() {
+        repository.setForceCollapseAll(true)
+    }
+
+    /**
+     * Notifies that a new non-proxied interaction may have started. Safe to call multiple times for
+     * the same interaction as it won't overwrite an existing interaction.
+     *
+     * Existing interactions can be cleared by calling [onUserInteractionEnded].
+     */
+    fun onUserInteractionStarted(shadeId: ShadeId) {
+        onUserInteractionStarted(
+            shadeId = shadeId,
+            isProxied = false,
+        )
+    }
+
+    /**
+     * Notifies that the current non-proxied interaction has ended.
+     *
+     * Safe to call multiple times, even if there's no current interaction or even if the current
+     * interaction doesn't belong to the given shade or is proxied as the code is a no-op unless
+     * there's a match between the parameters and the current interaction.
+     */
+    fun onUserInteractionEnded(
+        shadeId: ShadeId,
+    ) {
+        onUserInteractionEnded(
+            shadeId = shadeId,
+            isProxied = false,
+        )
+    }
+
+    fun sendProxiedInput(proxiedInput: ProxiedInputModel) {
+        inputProxy.onProxiedInput(proxiedInput)
+    }
+
+    /**
+     * Notifies that a new interaction may have started. Safe to call multiple times for the same
+     * interaction as it won't overwrite an existing interaction.
+     *
+     * Existing interactions can be cleared by calling [onUserInteractionEnded].
+     */
+    private fun onUserInteractionStarted(
+        shadeId: ShadeId,
+        isProxied: Boolean,
+    ) {
+        if (repository.shadeInteraction.value != null) {
+            return
+        }
+
+        repository.setShadeInteraction(
+            MultiShadeInteractionModel(
+                shadeId = shadeId,
+                isProxied = isProxied,
+            )
+        )
+    }
+
+    /**
+     * Notifies that the current interaction has ended.
+     *
+     * Safe to call multiple times, even if there's no current interaction or even if the current
+     * interaction doesn't belong to the given shade or [isProxied] value as the code is a no-op
+     * unless there's a match between the parameters and the current interaction.
+     */
+    private fun onUserInteractionEnded(
+        shadeId: ShadeId,
+        isProxied: Boolean,
+    ) {
+        repository.shadeInteraction.value?.let { (interactionShadeId, isInteractionProxied) ->
+            if (shadeId == interactionShadeId && isProxied == isInteractionProxied) {
+                repository.setShadeInteraction(null)
+            }
+        }
+    }
+
+    /**
+     * Returns the ID of the shade that's affected by user input at a given coordinate.
+     *
+     * @param config The shade configuration being used.
+     * @param xFraction The horizontal position of the user input as a fraction along the width of
+     *   its container where `0` is all the way to the left and `1` is all the way to the right.
+     */
+    private fun affectedShadeId(
+        config: ShadeConfig,
+        @FloatRange(from = 0.0, to = 1.0) xFraction: Float,
+    ): ShadeId {
+        return if (config is ShadeConfig.DualShadeConfig) {
+            if (xFraction <= config.splitFraction) {
+                ShadeId.LEFT
+            } else {
+                ShadeId.RIGHT
+            }
+        } else {
+            ShadeId.SINGLE
+        }
+    }
+
+    /** Returns the list of flows of all the shades in the given configuration. */
+    private fun allShades(
+        config: ShadeConfig,
+    ): List<Flow<ShadeModel>> {
+        return config.shadeIds.map { shadeId -> repository.getShade(shadeId) }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ProxiedInputModel.kt b/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ProxiedInputModel.kt
new file mode 100644
index 0000000..ee1dd65
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ProxiedInputModel.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.multishade.shared.model
+
+import androidx.annotation.FloatRange
+
+/**
+ * Models a part of an ongoing proxied user input gesture.
+ *
+ * "Proxied" user input is coming through a proxy; typically from an external app or different UI.
+ * In other words: it's not user input that's occurring directly on the shade UI itself.
+ */
+sealed class ProxiedInputModel {
+    /** The user is dragging their pointer. */
+    data class OnDrag(
+        /**
+         * The relative position of the pointer as a fraction of its container width where `0` is
+         * all the way to the left and `1` is all the way to the right.
+         */
+        @FloatRange(from = 0.0, to = 1.0) val xFraction: Float,
+        /** The amount that the pointer was dragged, in pixels. */
+        val yDragAmountPx: Float,
+    ) : ProxiedInputModel()
+
+    /** The user finished dragging by lifting up their pointer. */
+    object OnDragEnd : ProxiedInputModel()
+
+    /**
+     * The drag gesture has been canceled. Usually because the pointer exited the draggable area.
+     */
+    object OnDragCancel : ProxiedInputModel()
+
+    /** The user has tapped (clicked). */
+    object OnTap : ProxiedInputModel()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeConfig.kt b/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeConfig.kt
new file mode 100644
index 0000000..a4cd35c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeConfig.kt
@@ -0,0 +1,79 @@
+/*
+ * 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.shared.model
+
+import androidx.annotation.FloatRange
+
+/** Enumerates the various possible configurations of the shade system. */
+sealed class ShadeConfig(
+
+    /** IDs of the shade(s) in this configuration. */
+    open val shadeIds: List<ShadeId>,
+
+    /**
+     * The amount that the user must swipe up when the shade is fully expanded to automatically
+     * collapse once the user lets go of the shade. If the user swipes less than this amount, the
+     * shade will automatically revert back to fully expanded once the user stops swiping.
+     */
+    @FloatRange(from = 0.0, to = 1.0) open val swipeCollapseThreshold: Float,
+
+    /**
+     * The amount that the user must swipe down when the shade is fully collapsed to automatically
+     * expand once the user lets go of the shade. If the user swipes less than this amount, the
+     * shade will automatically revert back to fully collapsed once the user stops swiping.
+     */
+    @FloatRange(from = 0.0, to = 1.0) open val swipeExpandThreshold: Float,
+) {
+
+    /** There is a single shade. */
+    data class SingleShadeConfig(
+        @FloatRange(from = 0.0, to = 1.0) override val swipeCollapseThreshold: Float,
+        @FloatRange(from = 0.0, to = 1.0) override val swipeExpandThreshold: Float,
+    ) :
+        ShadeConfig(
+            shadeIds = listOf(ShadeId.SINGLE),
+            swipeCollapseThreshold = swipeCollapseThreshold,
+            swipeExpandThreshold = swipeExpandThreshold,
+        )
+
+    /** There are two shades arranged side-by-side. */
+    data class DualShadeConfig(
+        /** Width of the left-hand side shade. */
+        val leftShadeWidthPx: Int,
+        /** Width of the right-hand side shade. */
+        val rightShadeWidthPx: Int,
+        @FloatRange(from = 0.0, to = 1.0) override val swipeCollapseThreshold: Float,
+        @FloatRange(from = 0.0, to = 1.0) override val swipeExpandThreshold: Float,
+        /**
+         * The position of the "split" between interaction areas for each of the shades, as a
+         * fraction of the width of the container.
+         *
+         * Interactions that occur on the start-side (left-hand side in left-to-right languages like
+         * English) affect the start-side shade. Interactions that occur on the end-side (right-hand
+         * side in left-to-right languages like English) affect the end-side shade.
+         */
+        @FloatRange(from = 0.0, to = 1.0) val splitFraction: Float,
+        /** Maximum opacity when the scrim that shows up behind the dual shades is fully visible. */
+        @FloatRange(from = 0.0, to = 1.0) val scrimAlpha: Float,
+    ) :
+        ShadeConfig(
+            shadeIds = listOf(ShadeId.LEFT, ShadeId.RIGHT),
+            swipeCollapseThreshold = swipeCollapseThreshold,
+            swipeExpandThreshold = swipeExpandThreshold,
+        )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeId.kt b/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeId.kt
new file mode 100644
index 0000000..9e02657
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeId.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.shared.model
+
+/** Enumerates all known shade IDs. */
+enum class ShadeId {
+    /** ID of the shade on the left in dual shade configurations. */
+    LEFT,
+    /** ID of the shade on the right in dual shade configurations. */
+    RIGHT,
+    /** ID of the single shade in single shade configurations. */
+    SINGLE,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeModel.kt b/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeModel.kt
new file mode 100644
index 0000000..49ac64c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeModel.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.shared.model
+
+import androidx.annotation.FloatRange
+
+/** Models the current state of a shade. */
+data class ShadeModel(
+    val id: ShadeId,
+    @FloatRange(from = 0.0, to = 1.0) val expansion: Float = 0f,
+)
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/multishade/ui/viewmodel/MultiShadeViewModel.kt b/packages/SystemUI/src/com/android/systemui/multishade/ui/viewmodel/MultiShadeViewModel.kt
new file mode 100644
index 0000000..ce6ab97
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/multishade/ui/viewmodel/MultiShadeViewModel.kt
@@ -0,0 +1,112 @@
+/*
+ * 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.viewmodel
+
+import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor
+import com.android.systemui.multishade.shared.model.ProxiedInputModel
+import com.android.systemui.multishade.shared.model.ShadeConfig
+import com.android.systemui.multishade.shared.model.ShadeId
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/** Models UI state for UI that supports multi (or single) shade. */
+@OptIn(ExperimentalCoroutinesApi::class)
+class MultiShadeViewModel(
+    viewModelScope: CoroutineScope,
+    private val interactor: MultiShadeInteractor,
+) {
+    /** Models UI state for the single shade. */
+    val singleShade =
+        ShadeViewModel(
+            viewModelScope,
+            ShadeId.SINGLE,
+            interactor,
+        )
+
+    /** Models UI state for the shade on the left-hand side. */
+    val leftShade =
+        ShadeViewModel(
+            viewModelScope,
+            ShadeId.LEFT,
+            interactor,
+        )
+
+    /** Models UI state for the shade on the right-hand side. */
+    val rightShade =
+        ShadeViewModel(
+            viewModelScope,
+            ShadeId.RIGHT,
+            interactor,
+        )
+
+    /** The amount of alpha that the scrim should have. This is a value between `0` and `1`. */
+    val scrimAlpha: StateFlow<Float> =
+        combine(
+                interactor.maxShadeExpansion,
+                interactor.shadeConfig
+                    .map { it as? ShadeConfig.DualShadeConfig }
+                    .map { dualShadeConfigOrNull -> dualShadeConfigOrNull?.scrimAlpha ?: 0f },
+                ::Pair,
+            )
+            .map { (anyShadeExpansion, scrimAlpha) ->
+                (anyShadeExpansion * scrimAlpha).coerceIn(0f, 1f)
+            }
+            .stateIn(
+                scope = viewModelScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = 0f,
+            )
+
+    /** Whether the scrim should accept touch events. */
+    val isScrimEnabled: StateFlow<Boolean> =
+        interactor.shadeConfig
+            .flatMapLatest { shadeConfig ->
+                when (shadeConfig) {
+                    // In the dual shade configuration, the scrim is enabled when the expansion is
+                    // greater than zero on any one of the shades.
+                    is ShadeConfig.DualShadeConfig ->
+                        interactor.maxShadeExpansion
+                            .map { expansion -> expansion > 0 }
+                            .distinctUntilChanged()
+                    // No scrim in the single shade configuration.
+                    is ShadeConfig.SingleShadeConfig -> flowOf(false)
+                }
+            }
+            .stateIn(
+                scope = viewModelScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = false,
+            )
+
+    /** Notifies that the scrim has been touched. */
+    fun onScrimTouched(proxiedInput: ProxiedInputModel) {
+        if (!isScrimEnabled.value) {
+            return
+        }
+
+        interactor.sendProxiedInput(proxiedInput)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/ui/viewmodel/ShadeViewModel.kt b/packages/SystemUI/src/com/android/systemui/multishade/ui/viewmodel/ShadeViewModel.kt
new file mode 100644
index 0000000..e828dbd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/multishade/ui/viewmodel/ShadeViewModel.kt
@@ -0,0 +1,150 @@
+/*
+ * 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.viewmodel
+
+import androidx.annotation.FloatRange
+import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor
+import com.android.systemui.multishade.shared.model.ProxiedInputModel
+import com.android.systemui.multishade.shared.model.ShadeConfig
+import com.android.systemui.multishade.shared.model.ShadeId
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/** Models UI state for a single shade. */
+class ShadeViewModel(
+    viewModelScope: CoroutineScope,
+    private val shadeId: ShadeId,
+    private val interactor: MultiShadeInteractor,
+) {
+    /** Whether the shade is visible. */
+    val isVisible: StateFlow<Boolean> =
+        interactor
+            .isVisible(shadeId)
+            .stateIn(
+                scope = viewModelScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = false,
+            )
+
+    /** Whether swiping on the shade UI is currently enabled. */
+    val isSwipingEnabled: StateFlow<Boolean> =
+        interactor
+            .isNonProxiedInputAllowed(shadeId)
+            .stateIn(
+                scope = viewModelScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = false,
+            )
+
+    /** Whether the shade must be collapsed immediately. */
+    val isForceCollapsed: Flow<Boolean> =
+        interactor.isForceCollapsed(shadeId).distinctUntilChanged()
+
+    /** The width of the shade. */
+    val width: StateFlow<Size> =
+        interactor.shadeConfig
+            .map { shadeWidth(it) }
+            .stateIn(
+                scope = viewModelScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = shadeWidth(interactor.shadeConfig.value),
+            )
+
+    /**
+     * The amount that the user must swipe up when the shade is fully expanded to automatically
+     * collapse once the user lets go of the shade. If the user swipes less than this amount, the
+     * shade will automatically revert back to fully expanded once the user stops swiping.
+     */
+    val swipeCollapseThreshold: StateFlow<Float> =
+        interactor.shadeConfig
+            .map { it.swipeCollapseThreshold }
+            .stateIn(
+                scope = viewModelScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = interactor.shadeConfig.value.swipeCollapseThreshold,
+            )
+
+    /**
+     * The amount that the user must swipe down when the shade is fully collapsed to automatically
+     * expand once the user lets go of the shade. If the user swipes less than this amount, the
+     * shade will automatically revert back to fully collapsed once the user stops swiping.
+     */
+    val swipeExpandThreshold: StateFlow<Float> =
+        interactor.shadeConfig
+            .map { it.swipeExpandThreshold }
+            .stateIn(
+                scope = viewModelScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = interactor.shadeConfig.value.swipeExpandThreshold,
+            )
+
+    /**
+     * Proxied input affecting the shade. This is input coming from sources outside of system UI
+     * (for example, swiping down on the Launcher or from the status bar) or outside the UI of any
+     * shade (for example, the scrim that's shown behind the shades).
+     */
+    val proxiedInput: Flow<ProxiedInputModel?> =
+        interactor
+            .proxiedInput(shadeId)
+            .stateIn(
+                scope = viewModelScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = null,
+            )
+
+    /** Notifies that the expansion amount for the shade has changed. */
+    fun onExpansionChanged(
+        expansion: Float,
+    ) {
+        interactor.setExpansion(shadeId, expansion.coerceIn(0f, 1f))
+    }
+
+    /** Notifies that a drag gesture has started. */
+    fun onDragStarted() {
+        interactor.onUserInteractionStarted(shadeId)
+    }
+
+    /** Notifies that a drag gesture has ended. */
+    fun onDragEnded() {
+        interactor.onUserInteractionEnded(shadeId = shadeId)
+    }
+
+    private fun shadeWidth(shadeConfig: ShadeConfig): Size {
+        return when (shadeId) {
+            ShadeId.LEFT ->
+                Size.Pixels((shadeConfig as? ShadeConfig.DualShadeConfig)?.leftShadeWidthPx ?: 0)
+            ShadeId.RIGHT ->
+                Size.Pixels((shadeConfig as? ShadeConfig.DualShadeConfig)?.rightShadeWidthPx ?: 0)
+            ShadeId.SINGLE -> Size.Fraction(1f)
+        }
+    }
+
+    sealed class Size {
+        data class Fraction(
+            @FloatRange(from = 0.0, to = 1.0) val fraction: Float,
+        ) : Size()
+        data class Pixels(
+            val pixels: Int,
+        ) : Size()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index ac22b7c..779f1d8 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -129,8 +129,7 @@
             logDebug { "onShowNoteTask - start: $info" }
             when (info.launchMode) {
                 is NoteTaskLaunchMode.AppBubble -> {
-                    // TODO(b/267634412, b/268351693): Should use `showOrHideAppBubbleAsUser`
-                    bubbles.showOrHideAppBubble(intent)
+                    bubbles.showOrHideAppBubble(intent, userTracker.userHandle)
                     // App bubble logging happens on `onBubbleExpandChanged`.
                     logDebug { "onShowNoteTask - opened as app bubble: $info" }
                 }
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/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
index ca8e101..02a60ad 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
@@ -481,7 +481,6 @@
             mCropView.setExtraPadding(extraPadding + mPreview.getPaddingTop(),
                     extraPadding + mPreview.getPaddingBottom());
             imageTop += (previewHeight - imageHeight) / 2;
-            mCropView.setExtraPadding(extraPadding, extraPadding);
             mCropView.setImageWidth(previewWidth);
             scale = previewWidth / (float) mPreview.getDrawable().getIntrinsicWidth();
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index e2f31e8..a716a6e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -16,13 +16,13 @@
 
 package com.android.systemui.shade;
 
+import static com.android.systemui.flags.Flags.TRACKPAD_GESTURE_BACK;
 import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
 
 import android.app.StatusBarManager;
 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;
@@ -30,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;
@@ -38,7 +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;
@@ -47,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;
@@ -61,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}.
@@ -88,10 +96,12 @@
     private final NotificationInsetsController mNotificationInsetsController;
     private final AlternateBouncerInteractor mAlternateBouncerInteractor;
     private final UdfpsOverlayInteractor mUdfpsOverlayInteractor;
+    private final boolean mIsTrackpadGestureBackEnabled;
     private GestureDetector mPulsingWakeupGestureHandler;
     private View mBrightnessMirror;
     private boolean mTouchActive;
     private boolean mTouchCancelled;
+    private MotionEvent mDownEvent;
     private boolean mExpandAnimationRunning;
     private NotificationStackScrollLayout mStackScrollLayout;
     private PhoneStatusBarViewController mStatusBarViewController;
@@ -111,6 +121,7 @@
                 mIsOcclusionTransitionRunning =
                     step.getTransitionState() == TransitionState.RUNNING;
             };
+    private final SystemClock mClock;
 
     @Inject
     public NotificationShadeWindowViewController(
@@ -137,8 +148,10 @@
             AlternateBouncerInteractor alternateBouncerInteractor,
             UdfpsOverlayInteractor udfpsOverlayInteractor,
             KeyguardTransitionInteractor keyguardTransitionInteractor,
-            PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel
-    ) {
+            PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
+            FeatureFlags featureFlags,
+            Provider<MultiShadeInteractor> multiShadeInteractorProvider,
+            SystemClock clock) {
         mLockscreenShadeTransitionController = transitionController;
         mFalsingCollector = falsingCollector;
         mStatusBarStateController = statusBarStateController;
@@ -159,6 +172,7 @@
         mNotificationInsetsController = notificationInsetsController;
         mAlternateBouncerInteractor = alternateBouncerInteractor;
         mUdfpsOverlayInteractor = udfpsOverlayInteractor;
+        mIsTrackpadGestureBackEnabled = featureFlags.isEnabled(TRACKPAD_GESTURE_BACK);
 
         // This view is not part of the newly inflated expanded status bar.
         mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container);
@@ -170,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);
+            }
+        }
     }
 
     /**
@@ -219,9 +243,11 @@
                 if (isDown) {
                     mTouchActive = true;
                     mTouchCancelled = false;
+                    mDownEvent = ev;
                 } else if (ev.getActionMasked() == MotionEvent.ACTION_UP
                         || ev.getActionMasked() == MotionEvent.ACTION_CANCEL) {
                     mTouchActive = false;
+                    mDownEvent = null;
                 }
                 if (mTouchCancelled || mExpandAnimationRunning) {
                     return false;
@@ -262,7 +288,7 @@
                 mLockIconViewController.onTouchEvent(
                         ev,
                         () -> mService.wakeUpIfDozing(
-                                SystemClock.uptimeMillis(),
+                                mClock.uptimeMillis(),
                                 mView,
                                 "LOCK_ICON_TOUCH",
                                 PowerManager.WAKE_REASON_GESTURE)
@@ -446,10 +472,18 @@
 
     public void cancelCurrentTouch() {
         if (mTouchActive) {
-            final long now = SystemClock.uptimeMillis();
-            MotionEvent event = MotionEvent.obtain(now, now,
-                    MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
-            event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+            final long now = mClock.uptimeMillis();
+            final MotionEvent event;
+            if (mIsTrackpadGestureBackEnabled) {
+                event = MotionEvent.obtain(mDownEvent);
+                event.setDownTime(now);
+                event.setAction(MotionEvent.ACTION_CANCEL);
+                event.setLocation(0.0f, 0.0f);
+            } else {
+                event = MotionEvent.obtain(now, now,
+                        MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
+                event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+            }
             mView.dispatchTouchEvent(event);
             event.recycle();
             mTouchCancelled = true;
diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt
index 5736a5c..26149321 100644
--- a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt
@@ -37,7 +37,8 @@
         fun create(
             @BindsInstance parent: ViewGroup,
             @BindsInstance @Named(PLUGIN) plugin: BcSmartspaceDataPlugin,
-            @BindsInstance onAttachListener: View.OnAttachStateChangeListener
+            @BindsInstance onAttachListener: View.OnAttachStateChangeListener,
+            @BindsInstance viewWithCustomLayout: View? = null
         ): SmartspaceViewComponent
     }
 
@@ -53,10 +54,13 @@
             falsingManager: FalsingManager,
             parent: ViewGroup,
             @Named(PLUGIN) plugin: BcSmartspaceDataPlugin,
+            viewWithCustomLayout: View?,
             onAttachListener: View.OnAttachStateChangeListener
         ):
                 BcSmartspaceDataPlugin.SmartspaceView {
-            val ssView = plugin.getView(parent)
+            val ssView = viewWithCustomLayout
+                    as? BcSmartspaceDataPlugin.SmartspaceView
+                    ?: plugin.getView(parent)
             // Currently, this is only used to provide SmartspaceView on Dream surface.
             ssView.setUiSurface(UI_SURFACE_DREAM)
             ssView.registerDataProvider(plugin)
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/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index 99081e9..9e2a07e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -54,6 +54,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.dagger.CentralSurfacesDependenciesModule;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
+import com.android.systemui.statusbar.notification.RemoteInputControllerLogger;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry.EditedSuggestionInfo;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
@@ -65,6 +66,8 @@
 import com.android.systemui.util.DumpUtilsKt;
 import com.android.systemui.util.ListenerSet;
 
+import dagger.Lazy;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
@@ -72,8 +75,6 @@
 import java.util.Optional;
 import java.util.function.Consumer;
 
-import dagger.Lazy;
-
 /**
  * Class for handling remote input state over a set of notifications. This class handles things
  * like keeping notifications temporarily that were cancelled as a response to a remote input
@@ -104,6 +105,8 @@
     private final KeyguardManager mKeyguardManager;
     private final StatusBarStateController mStatusBarStateController;
     private final RemoteInputUriController mRemoteInputUriController;
+
+    private final RemoteInputControllerLogger mRemoteInputControllerLogger;
     private final NotificationClickNotifier mClickNotifier;
 
     protected RemoteInputController mRemoteInputController;
@@ -259,6 +262,7 @@
             Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
             StatusBarStateController statusBarStateController,
             RemoteInputUriController remoteInputUriController,
+            RemoteInputControllerLogger remoteInputControllerLogger,
             NotificationClickNotifier clickNotifier,
             ActionClickLogger logger,
             DumpManager dumpManager) {
@@ -275,6 +279,7 @@
         mKeyguardManager = context.getSystemService(KeyguardManager.class);
         mStatusBarStateController = statusBarStateController;
         mRemoteInputUriController = remoteInputUriController;
+        mRemoteInputControllerLogger = remoteInputControllerLogger;
         mClickNotifier = clickNotifier;
 
         dumpManager.registerDumpable(this);
@@ -294,7 +299,8 @@
     /** Initializes this component with the provided dependencies. */
     public void setUpWithCallback(Callback callback, RemoteInputController.Delegate delegate) {
         mCallback = callback;
-        mRemoteInputController = new RemoteInputController(delegate, mRemoteInputUriController);
+        mRemoteInputController = new RemoteInputController(delegate,
+                mRemoteInputUriController, mRemoteInputControllerLogger);
         if (mRemoteInputListener != null) {
             mRemoteInputListener.setRemoteInputController(mRemoteInputController);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
index f44f598..a37b2a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
@@ -28,6 +28,7 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.systemui.statusbar.notification.RemoteInputControllerLogger;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.policy.RemoteInputUriController;
 import com.android.systemui.statusbar.policy.RemoteInputView;
@@ -52,10 +53,14 @@
     private final Delegate mDelegate;
     private final RemoteInputUriController mRemoteInputUriController;
 
+    private final RemoteInputControllerLogger mLogger;
+
     public RemoteInputController(Delegate delegate,
-            RemoteInputUriController remoteInputUriController) {
+            RemoteInputUriController remoteInputUriController,
+            RemoteInputControllerLogger logger) {
         mDelegate = delegate;
         mRemoteInputUriController = remoteInputUriController;
+        mLogger = logger;
     }
 
     /**
@@ -117,6 +122,9 @@
         boolean isActive = isRemoteInputActive(entry);
         boolean found = pruneWeakThenRemoveAndContains(
                 entry /* contains */, null /* remove */, token /* removeToken */);
+        mLogger.logAddRemoteInput(entry.getKey()/* entryKey */,
+                isActive /* isRemoteInputAlreadyActive */,
+                found /* isRemoteInputFound */);
         if (!found) {
             mOpen.add(new Pair<>(new WeakReference<>(entry), token));
         }
@@ -137,9 +145,22 @@
      */
     public void removeRemoteInput(NotificationEntry entry, Object token) {
         Objects.requireNonNull(entry);
-        if (entry.mRemoteEditImeVisible && entry.mRemoteEditImeAnimatingAway) return;
+        if (entry.mRemoteEditImeVisible && entry.mRemoteEditImeAnimatingAway) {
+            mLogger.logRemoveRemoteInput(
+                    entry.getKey() /* entryKey*/,
+                    true /* remoteEditImeVisible */,
+                    true /* remoteEditImeAnimatingAway */);
+            return;
+        }
         // If the view is being removed, this may be called even though we're not active
-        if (!isRemoteInputActive(entry)) return;
+        boolean remoteInputActive = isRemoteInputActive(entry);
+        mLogger.logRemoveRemoteInput(
+                entry.getKey() /* entryKey*/,
+                entry.mRemoteEditImeVisible /* remoteEditImeVisible */,
+                entry.mRemoteEditImeAnimatingAway /* remoteEditImeAnimatingAway */,
+                remoteInputActive /* isRemoteInputActive */);
+
+        if (!remoteInputActive) return;
 
         pruneWeakThenRemoveAndContains(null /* contains */, entry /* remove */, token);
 
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/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index d7568a9..565c0a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -54,6 +54,7 @@
 import com.android.systemui.statusbar.commandline.CommandRegistry;
 import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
+import com.android.systemui.statusbar.notification.RemoteInputControllerLogger;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
@@ -77,14 +78,14 @@
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.time.SystemClock;
 
-import java.util.Optional;
-import java.util.concurrent.Executor;
-
 import dagger.Binds;
 import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
 
+import java.util.Optional;
+import java.util.concurrent.Executor;
+
 /**
  * This module provides instances needed to construct {@link CentralSurfacesImpl}. These are moved to
  * this separate from {@link CentralSurfacesModule} module so that components that wish to build
@@ -105,6 +106,7 @@
             Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
             StatusBarStateController statusBarStateController,
             RemoteInputUriController remoteInputUriController,
+            RemoteInputControllerLogger remoteInputControllerLogger,
             NotificationClickNotifier clickNotifier,
             ActionClickLogger actionClickLogger,
             DumpManager dumpManager) {
@@ -117,6 +119,7 @@
                 centralSurfacesOptionalLazy,
                 statusBarStateController,
                 remoteInputUriController,
+                remoteInputControllerLogger,
                 clickNotifier,
                 actionClickLogger,
                 dumpManager);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
new file mode 100644
index 0000000..9582dfad
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.statusbar.notification
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.dagger.NotificationRemoteInputLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+import javax.inject.Inject
+
+/** Logger class for [RemoteInputController]. */
+@SysUISingleton
+class RemoteInputControllerLogger
+@Inject
+constructor(@NotificationRemoteInputLog private val logBuffer: LogBuffer) {
+
+    /** logs addRemoteInput invocation of [RemoteInputController] */
+    fun logAddRemoteInput(
+        entryKey: String,
+        isRemoteInputAlreadyActive: Boolean,
+        isRemoteInputFound: Boolean
+    ) =
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            {
+                str1 = entryKey
+                bool1 = isRemoteInputAlreadyActive
+                bool2 = isRemoteInputFound
+            },
+            { "addRemoteInput entry: $str1, isAlreadyActive: $bool1, isFound:$bool2" }
+        )
+
+    /** logs removeRemoteInput invocation of [RemoteInputController] */
+    @JvmOverloads
+    fun logRemoveRemoteInput(
+        entryKey: String,
+        remoteEditImeVisible: Boolean,
+        remoteEditImeAnimatingAway: Boolean,
+        isRemoteInputActive: Boolean? = null
+    ) =
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            {
+                str1 = entryKey
+                bool1 = remoteEditImeVisible
+                bool2 = remoteEditImeAnimatingAway
+                str2 = isRemoteInputActive?.toString() ?: "N/A"
+            },
+            {
+                "removeRemoteInput entry: $str1, remoteEditImeVisible: $bool1" +
+                    ", remoteEditImeAnimatingAway: $bool2, isActive: $str2"
+            }
+        )
+
+    private companion object {
+        private const val TAG = "RemoteInputControllerLog"
+    }
+}
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/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index a5b7e94..33cbf06 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -184,7 +184,6 @@
     private int mMaxSmallHeight;
     private int mMaxSmallHeightLarge;
     private int mMaxExpandedHeight;
-    private int mIncreasedPaddingBetweenElements;
     private int mNotificationLaunchHeight;
     private boolean mMustStayOnScreen;
 
@@ -3065,14 +3064,6 @@
         return showingLayout != null && showingLayout.requireRowToHaveOverlappingRendering();
     }
 
-    @Override
-    public int getExtraBottomPadding() {
-        if (mIsSummaryWithChildren && isGroupExpanded()) {
-            return mIncreasedPaddingBetweenElements;
-        }
-        return 0;
-    }
-
     public void setInlineReplyAnimationFlagEnabled(boolean isEnabled) {
         mIsInlineReplyAnimationFlagEnabled = isEnabled;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 25c7264..9df6ba9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -451,7 +451,7 @@
     protected void updateClipping() {
         if (mClipToActualHeight && shouldClipToActualHeight()) {
             int top = getClipTopAmount();
-            int bottom = Math.max(Math.max(getActualHeight() + getExtraBottomPadding()
+            int bottom = Math.max(Math.max(getActualHeight()
                     - mClipBottomAmount, top), mMinimumHeightForClipping);
             mClipRect.set(Integer.MIN_VALUE, top, Integer.MAX_VALUE, bottom);
             setClipBounds(mClipRect);
@@ -592,13 +592,6 @@
     }
 
     /**
-     * @return padding used to alter how much of the view is clipped.
-     */
-    public int getExtraBottomPadding() {
-        return 0;
-    }
-
-    /**
      * @return true if the group's expansion state is changing, false otherwise.
      */
     public boolean isGroupExpansionChanging() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index d93c12b..5834dcb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -2055,6 +2055,23 @@
             pw.print("null");
         }
         pw.println();
+
+        pw.print("RemoteInputViews { ");
+        pw.print(" visibleType: " + mVisibleType);
+        if (mHeadsUpRemoteInputController != null) {
+            pw.print(", headsUpRemoteInputController.isActive: "
+                    + mHeadsUpRemoteInputController.isActive());
+        } else {
+            pw.print(", headsUpRemoteInputController: null");
+        }
+
+        if (mExpandedRemoteInputController != null) {
+            pw.print(", expandedRemoteInputController.isActive: "
+                    + mExpandedRemoteInputController.isActive());
+        } else {
+            pw.print(", expandedRemoteInputController: null");
+        }
+        pw.println(" }");
     }
 
     /** Add any existing SmartReplyView to the dump */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index e2e2a23..c0aed7a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -732,19 +732,28 @@
             return;
         }
         // TODO: move this logic to controller, which will invoke updateFooterView directly
-        boolean showDismissView = mClearAllEnabled &&
-                mController.hasActiveClearableNotifications(ROWS_ALL);
-        boolean showFooterView = (showDismissView || mController.getVisibleNotificationCount() > 0)
-                && mIsCurrentUserSetup  // see: b/193149550
+        final boolean showHistory = mController.isHistoryEnabled();
+        final boolean showDismissView = shouldShowDismissView();
+
+        updateFooterView(shouldShowFooterView(showDismissView)/* visible */,
+                showDismissView /* showDismissView */,
+                showHistory/* showHistory */);
+    }
+
+    private boolean shouldShowDismissView() {
+        return mClearAllEnabled
+                && mController.hasActiveClearableNotifications(ROWS_ALL);
+    }
+
+    private boolean shouldShowFooterView(boolean showDismissView) {
+        return (showDismissView || mController.getVisibleNotificationCount() > 0)
+                && mIsCurrentUserSetup // see: b/193149550
                 && !onKeyguard()
                 && mUpcomingStatusBarState != StatusBarState.KEYGUARD
                 // quick settings don't affect notifications when not in full screen
                 && (mQsExpansionFraction != 1 || !mQsFullScreen)
                 && !mScreenOffAnimationController.shouldHideNotificationsFooter()
                 && !mIsRemoteInputActive;
-        boolean showHistory = mController.isHistoryEnabled();
-
-        updateFooterView(showFooterView, showDismissView, showHistory);
     }
 
     /**
@@ -4499,7 +4508,7 @@
                 expandableView.setFakeShadowIntensity(0.0f, 0.0f, 0, 0);
             } else {
                 float yLocation = previous.getTranslationY() + previous.getActualHeight() -
-                        expandableView.getTranslationY() - previous.getExtraBottomPadding();
+                        expandableView.getTranslationY();
                 expandableView.setFakeShadowIntensity(
                         diff / FakeShadowView.SHADOW_SIBLING_TRESHOLD,
                         previous.getOutlineAlpha(), (int) yLocation,
@@ -5278,29 +5287,71 @@
         });
         pw.println();
         pw.println("Contents:");
-        DumpUtilsKt.withIncreasedIndent(pw, () -> {
-            int childCount = getChildCount();
-            pw.println("Number of children: " + childCount);
-            pw.println();
+        DumpUtilsKt.withIncreasedIndent(
+                pw,
+                () -> {
+                    int childCount = getChildCount();
+                    pw.println("Number of children: " + childCount);
+                    pw.println();
 
-            for (int i = 0; i < childCount; i++) {
-                ExpandableView child = getChildAtIndex(i);
-                child.dump(pw, args);
-                pw.println();
-            }
-            int transientViewCount = getTransientViewCount();
-            pw.println("Transient Views: " + transientViewCount);
-            for (int i = 0; i < transientViewCount; i++) {
-                ExpandableView child = (ExpandableView) getTransientView(i);
-                child.dump(pw, args);
-            }
-            View swipedView = mSwipeHelper.getSwipedView();
-            pw.println("Swiped view: " + swipedView);
-            if (swipedView instanceof ExpandableView) {
-                ExpandableView expandableView = (ExpandableView) swipedView;
-                expandableView.dump(pw, args);
-            }
-        });
+                    for (int i = 0; i < childCount; i++) {
+                        ExpandableView child = getChildAtIndex(i);
+                        child.dump(pw, args);
+                        if (child instanceof FooterView) {
+                            DumpUtilsKt.withIncreasedIndent(pw, () -> dumpFooterViewVisibility(pw));
+                        }
+                        pw.println();
+                    }
+                    int transientViewCount = getTransientViewCount();
+                    pw.println("Transient Views: " + transientViewCount);
+                    for (int i = 0; i < transientViewCount; i++) {
+                        ExpandableView child = (ExpandableView) getTransientView(i);
+                        child.dump(pw, args);
+                    }
+                    View swipedView = mSwipeHelper.getSwipedView();
+                    pw.println("Swiped view: " + swipedView);
+                    if (swipedView instanceof ExpandableView) {
+                        ExpandableView expandableView = (ExpandableView) swipedView;
+                        expandableView.dump(pw, args);
+                    }
+                });
+    }
+
+    private void dumpFooterViewVisibility(IndentingPrintWriter pw) {
+        final boolean showDismissView = shouldShowDismissView();
+
+        pw.println("showFooterView: " + shouldShowFooterView(showDismissView));
+        DumpUtilsKt.withIncreasedIndent(
+                pw,
+                () -> {
+                    pw.println("showDismissView: " + showDismissView);
+                    DumpUtilsKt.withIncreasedIndent(
+                            pw,
+                            () -> {
+                                pw.println("mClearAllEnabled: " + mClearAllEnabled);
+                                pw.println(
+                                        "hasActiveClearableNotifications: "
+                                                + mController.hasActiveClearableNotifications(
+                                                        ROWS_ALL));
+                            });
+                    pw.println();
+                    pw.println("showHistory: " + mController.isHistoryEnabled());
+                    pw.println();
+                    pw.println(
+                            "visibleNotificationCount: "
+                                    + mController.getVisibleNotificationCount());
+                    pw.println("mIsCurrentUserSetup: " + mIsCurrentUserSetup);
+                    pw.println("onKeyguard: " + onKeyguard());
+                    pw.println("mUpcomingStatusBarState: " + mUpcomingStatusBarState);
+                    pw.println("mQsExpansionFraction: " + mQsExpansionFraction);
+                    pw.println("mQsFullScreen: " + mQsFullScreen);
+                    pw.println(
+                            "mScreenOffAnimationController"
+                                    + ".shouldHideNotificationsFooter: "
+                                    + mScreenOffAnimationController
+                                            .shouldHideNotificationsFooter());
+                    pw.println("mIsRemoteInputActive: " + mIsRemoteInputActive);
+                });
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
index 0e837d2..a35e5b5 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
@@ -18,12 +18,15 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -37,6 +40,7 @@
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
+@TestableLooper.RunWithLooper
 @RunWith(AndroidTestingRunner.class)
 public class KeyguardMessageAreaControllerTest extends SysuiTestCase {
     @Mock
@@ -45,14 +49,14 @@
     private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @Mock
     private KeyguardMessageArea mKeyguardMessageArea;
-
     private KeyguardMessageAreaController mMessageAreaController;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mMessageAreaController = new KeyguardMessageAreaController.Factory(
-                mKeyguardUpdateMonitor, mConfigurationController).create(mKeyguardMessageArea);
+                mKeyguardUpdateMonitor, mConfigurationController).create(
+                mKeyguardMessageArea);
     }
 
     @Test
@@ -89,6 +93,19 @@
     }
 
     @Test
+    public void testSetMessage_AnnounceForAccessibility() {
+        ArgumentCaptor<Runnable> argumentCaptor = ArgumentCaptor.forClass(Runnable.class);
+        when(mKeyguardMessageArea.getText()).thenReturn("abc");
+        mMessageAreaController.setMessage("abc");
+
+        verify(mKeyguardMessageArea).setMessage("abc", /* animate= */ true);
+        verify(mKeyguardMessageArea).removeCallbacks(any(Runnable.class));
+        verify(mKeyguardMessageArea).postDelayed(argumentCaptor.capture(), anyLong());
+        argumentCaptor.getValue().run();
+        verify(mKeyguardMessageArea).announceForAccessibility("abc");
+    }
+
+    @Test
     public void testSetBouncerVisible() {
         mMessageAreaController.setIsVisible(true);
         verify(mKeyguardMessageArea).setIsVisible(true);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 38d3a3e..f966eb3 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -66,6 +66,7 @@
 import com.android.systemui.classifier.FalsingA11yDelegate;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
@@ -619,13 +620,26 @@
 
     @Test
     public void testReinflateViewFlipper() {
-        mKeyguardSecurityContainerController.reinflateViewFlipper();
+        mKeyguardSecurityContainerController.reinflateViewFlipper(() -> {});
         verify(mKeyguardSecurityViewFlipperController).clearViews();
         verify(mKeyguardSecurityViewFlipperController).getSecurityView(any(SecurityMode.class),
                 any(KeyguardSecurityCallback.class));
     }
 
     @Test
+    public void testReinflateViewFlipper_asyncBouncerFlagOn() {
+        when(mFeatureFlags.isEnabled(Flags.ASYNC_INFLATE_BOUNCER)).thenReturn(true);
+        KeyguardSecurityViewFlipperController.OnViewInflatedListener onViewInflatedListener =
+                () -> {
+                };
+        mKeyguardSecurityContainerController.reinflateViewFlipper(onViewInflatedListener);
+        verify(mKeyguardSecurityViewFlipperController).clearViews();
+        verify(mKeyguardSecurityViewFlipperController).asynchronouslyInflateView(
+                any(SecurityMode.class),
+                any(KeyguardSecurityCallback.class), eq(onViewInflatedListener));
+    }
+
+    @Test
     public void testSideFpsControllerShow() {
         mKeyguardSecurityContainerController.updateSideFpsVisibility(/* isVisible= */ true);
         verify(mSideFpsController).show(
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
index 1614b57..afb54d2 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
@@ -31,10 +31,12 @@
 import android.view.ViewGroup;
 import android.view.WindowInsetsController;
 
+import androidx.asynclayoutinflater.view.AsyncLayoutInflater;
 import androidx.test.filters.SmallTest;
 
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FeatureFlags;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -57,6 +59,8 @@
     @Mock
     private LayoutInflater mLayoutInflater;
     @Mock
+    private AsyncLayoutInflater mAsyncLayoutInflater;
+    @Mock
     private KeyguardInputViewController.Factory mKeyguardSecurityViewControllerFactory;
     @Mock
     private EmergencyButtonController.Factory mEmergencyButtonControllerFactory;
@@ -70,6 +74,8 @@
     private WindowInsetsController mWindowInsetsController;
     @Mock
     private KeyguardSecurityCallback mKeyguardSecurityCallback;
+    @Mock
+    private FeatureFlags mFeatureFlags;
 
     private KeyguardSecurityViewFlipperController mKeyguardSecurityViewFlipperController;
 
@@ -82,10 +88,11 @@
         when(mView.getWindowInsetsController()).thenReturn(mWindowInsetsController);
         when(mEmergencyButtonControllerFactory.create(any(EmergencyButton.class)))
                 .thenReturn(mEmergencyButtonController);
+        when(mView.getContext()).thenReturn(getContext());
 
         mKeyguardSecurityViewFlipperController = new KeyguardSecurityViewFlipperController(mView,
-                mLayoutInflater, mKeyguardSecurityViewControllerFactory,
-                mEmergencyButtonControllerFactory);
+                mLayoutInflater, mAsyncLayoutInflater, mKeyguardSecurityViewControllerFactory,
+                mEmergencyButtonControllerFactory, mFeatureFlags);
     }
 
     @Test
@@ -108,6 +115,14 @@
     }
 
     @Test
+    public void asynchronouslyInflateView() {
+        mKeyguardSecurityViewFlipperController.asynchronouslyInflateView(SecurityMode.PIN,
+                mKeyguardSecurityCallback, null);
+        verify(mAsyncLayoutInflater).inflate(anyInt(), eq(mView), any(
+                AsyncLayoutInflater.OnInflateFinishedListener.class));
+    }
+
+    @Test
     public void onDensityOrFontScaleChanged() {
         mKeyguardSecurityViewFlipperController.clearViews();
         verify(mView).removeAllViews();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/FontVariationUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/FontVariationUtilsTest.kt
new file mode 100644
index 0000000..070cad7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/FontVariationUtilsTest.kt
@@ -0,0 +1,62 @@
+package com.android.systemui.animation
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import junit.framework.Assert
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private const val TAG_WGHT = "wght"
+private const val TAG_WDTH = "wdth"
+private const val TAG_OPSZ = "opsz"
+private const val TAG_ROND = "ROND"
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class FontVariationUtilsTest : SysuiTestCase() {
+    @Test
+    fun testUpdateFontVariation_getCorrectFvarStr() {
+        val fontVariationUtils = FontVariationUtils()
+        val initFvar =
+            fontVariationUtils.updateFontVariation(
+                weight = 100,
+                width = 100,
+                opticalSize = -1,
+                roundness = 100
+            )
+        Assert.assertEquals("'$TAG_WGHT' 100, '$TAG_WDTH' 100, '$TAG_ROND' 100", initFvar)
+        val updatedFvar =
+            fontVariationUtils.updateFontVariation(
+                weight = 200,
+                width = 100,
+                opticalSize = 0,
+                roundness = 100
+            )
+        Assert.assertEquals(
+            "'$TAG_WGHT' 200, '$TAG_WDTH' 100, '$TAG_OPSZ' 0, '$TAG_ROND' 100",
+            updatedFvar
+        )
+    }
+
+    @Test
+    fun testStyleValueUnchange_getBlankStr() {
+        val fontVariationUtils = FontVariationUtils()
+        fontVariationUtils.updateFontVariation(
+            weight = 100,
+            width = 100,
+            opticalSize = 0,
+            roundness = 100
+        )
+        val updatedFvar1 =
+            fontVariationUtils.updateFontVariation(
+                weight = 100,
+                width = 100,
+                opticalSize = 0,
+                roundness = 100
+            )
+        Assert.assertEquals("", updatedFvar1)
+        val updatedFvar2 = fontVariationUtils.updateFontVariation()
+        Assert.assertEquals("", updatedFvar2)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt
index b389558..d7aa6e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt
@@ -19,7 +19,6 @@
 import android.animation.AnimatorListenerAdapter
 import android.animation.ValueAnimator
 import android.graphics.Typeface
-import android.graphics.fonts.FontVariationAxis
 import android.testing.AndroidTestingRunner
 import android.text.Layout
 import android.text.StaticLayout
@@ -180,71 +179,4 @@
 
         assertThat(paint.typeface).isSameInstanceAs(prevTypeface)
     }
-
-    @Test
-    fun testSetTextStyle_addWeight() {
-        testWeightChange("", 100, FontVariationAxis.fromFontVariationSettings("'wght' 100")!!)
-    }
-
-    @Test
-    fun testSetTextStyle_changeWeight() {
-        testWeightChange(
-                "'wght' 500",
-                100,
-                FontVariationAxis.fromFontVariationSettings("'wght' 100")!!
-        )
-    }
-
-    @Test
-    fun testSetTextStyle_addWeightWithOtherAxis() {
-        testWeightChange(
-                "'wdth' 100",
-                100,
-                FontVariationAxis.fromFontVariationSettings("'wght' 100, 'wdth' 100")!!
-        )
-    }
-
-    @Test
-    fun testSetTextStyle_changeWeightWithOtherAxis() {
-        testWeightChange(
-                "'wght' 500, 'wdth' 100",
-                100,
-                FontVariationAxis.fromFontVariationSettings("'wght' 100, 'wdth' 100")!!
-        )
-    }
-
-    private fun testWeightChange(
-            initialFontVariationSettings: String,
-            weight: Int,
-            expectedFontVariationSettings: Array<FontVariationAxis>
-    ) {
-        val layout = makeLayout("Hello, World", PAINT)
-        val valueAnimator = mock(ValueAnimator::class.java)
-        val textInterpolator = mock(TextInterpolator::class.java)
-        val paint =
-                TextPaint().apply {
-                    typeface = Typeface.createFromFile("/system/fonts/Roboto-Regular.ttf")
-                    fontVariationSettings = initialFontVariationSettings
-                }
-        `when`(textInterpolator.targetPaint).thenReturn(paint)
-
-        val textAnimator =
-                TextAnimator(layout, {}).apply {
-                    this.textInterpolator = textInterpolator
-                    this.animator = valueAnimator
-                }
-        textAnimator.setTextStyle(weight = weight, animate = false)
-
-        val resultFontVariationList =
-                FontVariationAxis.fromFontVariationSettings(
-                        textInterpolator.targetPaint.fontVariationSettings
-                )
-        expectedFontVariationSettings.forEach { expectedAxis ->
-            val resultAxis = resultFontVariationList?.filter { it.tag == expectedAxis.tag }?.get(0)
-            assertThat(resultAxis).isNotNull()
-            if (resultAxis != null) {
-                assertThat(resultAxis.styleValue).isEqualTo(expectedAxis.styleValue)
-            }
-        }
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index e1c7417..c068efb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -35,7 +35,6 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
@@ -930,6 +929,15 @@
         assertNotSame(firstFpLocation, mAuthController.getFingerprintSensorLocation());
     }
 
+    @Test
+    public void testCloseDialog_whenGlobalActionsMenuShown() throws Exception {
+        showDialog(new int[]{1} /* sensorIds */, false /* credentialAllowed */);
+        mAuthController.handleShowGlobalActionsMenu();
+        verify(mReceiver).onDialogDismissed(
+                eq(BiometricPrompt.DISMISSED_REASON_USER_CANCEL),
+                eq(null) /* credentialAttestation */);
+    }
+
     private void showDialog(int[] sensorIds, boolean credentialAllowed) {
         mAuthController.showAuthenticationDialog(createTestPromptInfo(),
                 mReceiver /* receiver */,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt
index 5a613aa..590989d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt
@@ -45,6 +45,7 @@
 import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.Mock
 import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.doAnswer
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
@@ -234,6 +235,36 @@
     }
 
     @Test
+    fun dialogPositiveButtonWhenCalledOnCompleteSettingIsTrue() {
+        sharedPreferences.putAttempts(0)
+        secureSettings.putBool(SETTING_SHOW, true)
+        secureSettings.putBool(SETTING_ACTION, false)
+
+        doAnswer { assertThat(secureSettings.getBool(SETTING_ACTION, false)).isTrue() }
+            .`when`(completedRunnable)
+            .invoke()
+
+        underTest.maybeShowDialog(context, completedRunnable)
+        clickButton(DialogInterface.BUTTON_POSITIVE)
+
+        verify(completedRunnable).invoke()
+    }
+
+    @Test
+    fun dialogPositiveCancelKeyguardStillCallsOnComplete() {
+        `when`(activityStarter.dismissKeyguardThenExecute(any(), nullable(), anyBoolean()))
+            .thenAnswer { (it.arguments[1] as Runnable).run() }
+        sharedPreferences.putAttempts(0)
+        secureSettings.putBool(SETTING_SHOW, true)
+        secureSettings.putBool(SETTING_ACTION, false)
+
+        underTest.maybeShowDialog(context, completedRunnable)
+        clickButton(DialogInterface.BUTTON_POSITIVE)
+
+        verify(completedRunnable).invoke()
+    }
+
+    @Test
     fun dialogCancelDoesntChangeSetting() {
         sharedPreferences.putAttempts(0)
         secureSettings.putBool(SETTING_SHOW, true)
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/dreams/complication/ComplicationTypesUpdaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java
index b3329eb..0e16b47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java
@@ -17,7 +17,6 @@
 package com.android.systemui.dreams.complication;
 
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -36,7 +35,7 @@
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.shared.condition.Monitor;
 import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.settings.SecureSettings;
+import com.android.systemui.util.settings.FakeSettings;
 import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
@@ -57,8 +56,7 @@
     private Context mContext;
     @Mock
     private DreamBackend mDreamBackend;
-    @Mock
-    private SecureSettings mSecureSettings;
+    private FakeSettings mSecureSettings;
     @Mock
     private DreamOverlayStateController mDreamOverlayStateController;
     @Captor
@@ -74,6 +72,7 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         when(mDreamBackend.getEnabledComplications()).thenReturn(new HashSet<>());
+        mSecureSettings = new FakeSettings();
 
         mMonitor = SelfExecutingMonitor.createInstance();
         mController = new ComplicationTypesUpdater(mDreamBackend, mExecutor,
@@ -100,19 +99,15 @@
         when(mDreamBackend.getEnabledComplications()).thenReturn(new HashSet<>(Arrays.asList(
                 DreamBackend.COMPLICATION_TYPE_TIME, DreamBackend.COMPLICATION_TYPE_WEATHER,
                 DreamBackend.COMPLICATION_TYPE_AIR_QUALITY)));
-        final ContentObserver settingsObserver = captureSettingsObserver();
-        settingsObserver.onChange(false);
+
+        // Update the setting to trigger any content observers
+        mSecureSettings.putBoolForUser(
+                Settings.Secure.SCREENSAVER_COMPLICATIONS_ENABLED, true,
+                UserHandle.myUserId());
         mExecutor.runAllReady();
 
         verify(mDreamOverlayStateController).setAvailableComplicationTypes(
                 Complication.COMPLICATION_TYPE_TIME | Complication.COMPLICATION_TYPE_WEATHER
                         | Complication.COMPLICATION_TYPE_AIR_QUALITY);
     }
-
-    private ContentObserver captureSettingsObserver() {
-        verify(mSecureSettings).registerContentObserverForUser(
-                eq(Settings.Secure.SCREENSAVER_COMPLICATIONS_ENABLED),
-                mSettingsObserverCaptor.capture(), eq(UserHandle.myUserId()));
-        return mSettingsObserverCaptor.getValue();
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
index 2bcd75b..18f7db1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
@@ -37,6 +37,7 @@
 import org.mockito.Mockito.anyBoolean
 import org.mockito.Mockito.anyString
 import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.never
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyNoMoreInteractions
@@ -53,7 +54,7 @@
  */
 @SmallTest
 class FeatureFlagsDebugTest : SysuiTestCase() {
-    private lateinit var mFeatureFlagsDebug: FeatureFlagsDebug
+    private lateinit var featureFlagsDebug: FeatureFlagsDebug
 
     @Mock
     private lateinit var flagManager: FlagManager
@@ -85,7 +86,7 @@
         flagMap.put(Flags.TEAMFOOD.name, Flags.TEAMFOOD)
         flagMap.put(teamfoodableFlagA.name, teamfoodableFlagA)
         flagMap.put(teamfoodableFlagB.name, teamfoodableFlagB)
-        mFeatureFlagsDebug = FeatureFlagsDebug(
+        featureFlagsDebug = FeatureFlagsDebug(
             flagManager,
             mockContext,
             globalSettings,
@@ -95,7 +96,7 @@
             flagMap,
             restarter
         )
-        mFeatureFlagsDebug.init()
+        featureFlagsDebug.init()
         verify(flagManager).onSettingsChangedAction = any()
         broadcastReceiver = withArgCaptor {
             verify(mockContext).registerReceiver(
@@ -116,7 +117,7 @@
         whenever(flagManager.readFlagValue<Boolean>(eq("4"), any())).thenReturn(false)
 
         assertThat(
-            mFeatureFlagsDebug.isEnabled(
+            featureFlagsDebug.isEnabled(
                 ReleasedFlag(
                     2,
                     name = "2",
@@ -125,7 +126,7 @@
             )
         ).isTrue()
         assertThat(
-            mFeatureFlagsDebug.isEnabled(
+            featureFlagsDebug.isEnabled(
                 UnreleasedFlag(
                     3,
                     name = "3",
@@ -134,7 +135,7 @@
             )
         ).isTrue()
         assertThat(
-            mFeatureFlagsDebug.isEnabled(
+            featureFlagsDebug.isEnabled(
                 ReleasedFlag(
                     4,
                     name = "4",
@@ -143,7 +144,7 @@
             )
         ).isFalse()
         assertThat(
-            mFeatureFlagsDebug.isEnabled(
+            featureFlagsDebug.isEnabled(
                 UnreleasedFlag(
                     5,
                     name = "5",
@@ -157,8 +158,8 @@
     fun teamFoodFlag_False() {
         whenever(flagManager.readFlagValue<Boolean>(
             eq(Flags.TEAMFOOD.name), any())).thenReturn(false)
-        assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagA)).isFalse()
-        assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagB)).isTrue()
+        assertThat(featureFlagsDebug.isEnabled(teamfoodableFlagA)).isFalse()
+        assertThat(featureFlagsDebug.isEnabled(teamfoodableFlagB)).isTrue()
 
         // Regular boolean flags should still test the same.
         // Only our teamfoodableFlag should change.
@@ -169,8 +170,8 @@
     fun teamFoodFlag_True() {
         whenever(flagManager.readFlagValue<Boolean>(
             eq(Flags.TEAMFOOD.name), any())).thenReturn(true)
-        assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagA)).isTrue()
-        assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagB)).isTrue()
+        assertThat(featureFlagsDebug.isEnabled(teamfoodableFlagA)).isTrue()
+        assertThat(featureFlagsDebug.isEnabled(teamfoodableFlagB)).isTrue()
 
         // Regular boolean flags should still test the same.
         // Only our teamfoodableFlag should change.
@@ -185,8 +186,8 @@
             .thenReturn(false)
         whenever(flagManager.readFlagValue<Boolean>(
             eq(Flags.TEAMFOOD.name), any())).thenReturn(true)
-        assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagA)).isTrue()
-        assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagB)).isFalse()
+        assertThat(featureFlagsDebug.isEnabled(teamfoodableFlagA)).isTrue()
+        assertThat(featureFlagsDebug.isEnabled(teamfoodableFlagB)).isFalse()
 
         // Regular boolean flags should still test the same.
         // Only our teamfoodableFlag should change.
@@ -205,7 +206,7 @@
         whenever(flagManager.readFlagValue<Boolean>(eq("5"), any())).thenReturn(false)
 
         assertThat(
-            mFeatureFlagsDebug.isEnabled(
+            featureFlagsDebug.isEnabled(
                 ResourceBooleanFlag(
                     1,
                     "1",
@@ -214,16 +215,16 @@
                 )
             )
         ).isFalse()
-        assertThat(mFeatureFlagsDebug.isEnabled(ResourceBooleanFlag(2, "2", "test", 1002))).isTrue()
-        assertThat(mFeatureFlagsDebug.isEnabled(ResourceBooleanFlag(3, "3", "test", 1003))).isTrue()
+        assertThat(featureFlagsDebug.isEnabled(ResourceBooleanFlag(2, "2", "test", 1002))).isTrue()
+        assertThat(featureFlagsDebug.isEnabled(ResourceBooleanFlag(3, "3", "test", 1003))).isTrue()
 
         Assert.assertThrows(NameNotFoundException::class.java) {
-            mFeatureFlagsDebug.isEnabled(ResourceBooleanFlag(4, "4", "test", 1004))
+            featureFlagsDebug.isEnabled(ResourceBooleanFlag(4, "4", "test", 1004))
         }
         // Test that resource is loaded (and validated) even when the setting is set.
         //  This prevents developers from not noticing when they reference an invalid resource.
         Assert.assertThrows(NameNotFoundException::class.java) {
-            mFeatureFlagsDebug.isEnabled(ResourceBooleanFlag(5, "5", "test", 1005))
+            featureFlagsDebug.isEnabled(ResourceBooleanFlag(5, "5", "test", 1005))
         }
     }
 
@@ -236,11 +237,11 @@
             return@thenAnswer it.getArgument(1)
         }
 
-        assertThat(mFeatureFlagsDebug.isEnabled(SysPropBooleanFlag(1, "a", "test"))).isFalse()
-        assertThat(mFeatureFlagsDebug.isEnabled(SysPropBooleanFlag(2, "b", "test"))).isTrue()
-        assertThat(mFeatureFlagsDebug.isEnabled(SysPropBooleanFlag(3, "c", "test", true))).isTrue()
+        assertThat(featureFlagsDebug.isEnabled(SysPropBooleanFlag(1, "a", "test"))).isFalse()
+        assertThat(featureFlagsDebug.isEnabled(SysPropBooleanFlag(2, "b", "test"))).isTrue()
+        assertThat(featureFlagsDebug.isEnabled(SysPropBooleanFlag(3, "c", "test", true))).isTrue()
         assertThat(
-            mFeatureFlagsDebug.isEnabled(
+            featureFlagsDebug.isEnabled(
                 SysPropBooleanFlag(
                     4,
                     "d",
@@ -249,17 +250,17 @@
                 )
             )
         ).isFalse()
-        assertThat(mFeatureFlagsDebug.isEnabled(SysPropBooleanFlag(5, "e", "test"))).isFalse()
+        assertThat(featureFlagsDebug.isEnabled(SysPropBooleanFlag(5, "e", "test"))).isFalse()
     }
 
     @Test
     fun readStringFlag() {
         whenever(flagManager.readFlagValue<String>(eq("3"), any())).thenReturn("foo")
         whenever(flagManager.readFlagValue<String>(eq("4"), any())).thenReturn("bar")
-        assertThat(mFeatureFlagsDebug.getString(StringFlag(1, "1", "test", "biz"))).isEqualTo("biz")
-        assertThat(mFeatureFlagsDebug.getString(StringFlag(2, "2", "test", "baz"))).isEqualTo("baz")
-        assertThat(mFeatureFlagsDebug.getString(StringFlag(3, "3", "test", "buz"))).isEqualTo("foo")
-        assertThat(mFeatureFlagsDebug.getString(StringFlag(4, "4", "test", "buz"))).isEqualTo("bar")
+        assertThat(featureFlagsDebug.getString(StringFlag(1, "1", "test", "biz"))).isEqualTo("biz")
+        assertThat(featureFlagsDebug.getString(StringFlag(2, "2", "test", "baz"))).isEqualTo("baz")
+        assertThat(featureFlagsDebug.getString(StringFlag(3, "3", "test", "buz"))).isEqualTo("foo")
+        assertThat(featureFlagsDebug.getString(StringFlag(4, "4", "test", "buz"))).isEqualTo("bar")
     }
 
     @Test
@@ -276,7 +277,7 @@
         whenever(flagManager.readFlagValue<String>(eq("6"), any())).thenReturn("override6")
 
         assertThat(
-            mFeatureFlagsDebug.getString(
+            featureFlagsDebug.getString(
                 ResourceStringFlag(
                     1,
                     "1",
@@ -286,7 +287,7 @@
             )
         ).isEqualTo("")
         assertThat(
-            mFeatureFlagsDebug.getString(
+            featureFlagsDebug.getString(
                 ResourceStringFlag(
                     2,
                     "2",
@@ -296,7 +297,7 @@
             )
         ).isEqualTo("resource2")
         assertThat(
-            mFeatureFlagsDebug.getString(
+            featureFlagsDebug.getString(
                 ResourceStringFlag(
                     3,
                     "3",
@@ -307,15 +308,15 @@
         ).isEqualTo("override3")
 
         Assert.assertThrows(NullPointerException::class.java) {
-            mFeatureFlagsDebug.getString(ResourceStringFlag(4, "4", "test", 1004))
+            featureFlagsDebug.getString(ResourceStringFlag(4, "4", "test", 1004))
         }
         Assert.assertThrows(NameNotFoundException::class.java) {
-            mFeatureFlagsDebug.getString(ResourceStringFlag(5, "5", "test", 1005))
+            featureFlagsDebug.getString(ResourceStringFlag(5, "5", "test", 1005))
         }
         // Test that resource is loaded (and validated) even when the setting is set.
         //  This prevents developers from not noticing when they reference an invalid resource.
         Assert.assertThrows(NameNotFoundException::class.java) {
-            mFeatureFlagsDebug.getString(ResourceStringFlag(6, "6", "test", 1005))
+            featureFlagsDebug.getString(ResourceStringFlag(6, "6", "test", 1005))
         }
     }
 
@@ -323,10 +324,10 @@
     fun readIntFlag() {
         whenever(flagManager.readFlagValue<Int>(eq("3"), any())).thenReturn(22)
         whenever(flagManager.readFlagValue<Int>(eq("4"), any())).thenReturn(48)
-        assertThat(mFeatureFlagsDebug.getInt(IntFlag(1, "1", "test", 12))).isEqualTo(12)
-        assertThat(mFeatureFlagsDebug.getInt(IntFlag(2, "2", "test", 93))).isEqualTo(93)
-        assertThat(mFeatureFlagsDebug.getInt(IntFlag(3, "3", "test", 8))).isEqualTo(22)
-        assertThat(mFeatureFlagsDebug.getInt(IntFlag(4, "4", "test", 234))).isEqualTo(48)
+        assertThat(featureFlagsDebug.getInt(IntFlag(1, "1", "test", 12))).isEqualTo(12)
+        assertThat(featureFlagsDebug.getInt(IntFlag(2, "2", "test", 93))).isEqualTo(93)
+        assertThat(featureFlagsDebug.getInt(IntFlag(3, "3", "test", 8))).isEqualTo(22)
+        assertThat(featureFlagsDebug.getInt(IntFlag(4, "4", "test", 234))).isEqualTo(48)
     }
 
     @Test
@@ -342,17 +343,17 @@
         whenever(flagManager.readFlagValue<Int>(eq(4), any())).thenReturn(500)
         whenever(flagManager.readFlagValue<Int>(eq(5), any())).thenReturn(9519)
 
-        assertThat(mFeatureFlagsDebug.getInt(ResourceIntFlag(1, "1", "test", 1001))).isEqualTo(88)
-        assertThat(mFeatureFlagsDebug.getInt(ResourceIntFlag(2, "2", "test", 1002))).isEqualTo(61)
-        assertThat(mFeatureFlagsDebug.getInt(ResourceIntFlag(3, "3", "test", 1003))).isEqualTo(20)
+        assertThat(featureFlagsDebug.getInt(ResourceIntFlag(1, "1", "test", 1001))).isEqualTo(88)
+        assertThat(featureFlagsDebug.getInt(ResourceIntFlag(2, "2", "test", 1002))).isEqualTo(61)
+        assertThat(featureFlagsDebug.getInt(ResourceIntFlag(3, "3", "test", 1003))).isEqualTo(20)
 
         Assert.assertThrows(NotFoundException::class.java) {
-            mFeatureFlagsDebug.getInt(ResourceIntFlag(4, "4", "test", 1004))
+            featureFlagsDebug.getInt(ResourceIntFlag(4, "4", "test", 1004))
         }
         // Test that resource is loaded (and validated) even when the setting is set.
         //  This prevents developers from not noticing when they reference an invalid resource.
         Assert.assertThrows(NotFoundException::class.java) {
-            mFeatureFlagsDebug.getInt(ResourceIntFlag(5, "5", "test", 1005))
+            featureFlagsDebug.getInt(ResourceIntFlag(5, "5", "test", 1005))
         }
     }
 
@@ -432,11 +433,11 @@
         whenever(flagManager.readFlagValue<String>(eq("1"), any())).thenReturn("original")
 
         // gets the flag & cache it
-        assertThat(mFeatureFlagsDebug.getString(flag1)).isEqualTo("original")
+        assertThat(featureFlagsDebug.getString(flag1)).isEqualTo("original")
         verify(flagManager, times(1)).readFlagValue(eq("1"), eq(StringFlagSerializer))
 
         // hit the cache
-        assertThat(mFeatureFlagsDebug.getString(flag1)).isEqualTo("original")
+        assertThat(featureFlagsDebug.getString(flag1)).isEqualTo("original")
         verifyNoMoreInteractions(flagManager)
 
         // set the flag
@@ -444,7 +445,7 @@
         verifyPutData("1", "{\"type\":\"string\",\"value\":\"new\"}", numReads = 2)
         whenever(flagManager.readFlagValue<String>(eq("1"), any())).thenReturn("new")
 
-        assertThat(mFeatureFlagsDebug.getString(flag1)).isEqualTo("new")
+        assertThat(featureFlagsDebug.getString(flag1)).isEqualTo("new")
         verify(flagManager, times(3)).readFlagValue(eq("1"), eq(StringFlagSerializer))
     }
 
@@ -454,7 +455,7 @@
 
         serverFlagReader.setFlagValue(flag.namespace, flag.name, false)
 
-        assertThat(mFeatureFlagsDebug.isEnabled(flag)).isFalse()
+        assertThat(featureFlagsDebug.isEnabled(flag)).isFalse()
     }
 
     @Test
@@ -462,7 +463,33 @@
         val flag = UnreleasedFlag(100, name = "100", namespace = "test")
 
         serverFlagReader.setFlagValue(flag.namespace, flag.name, true)
-        assertThat(mFeatureFlagsDebug.isEnabled(flag)).isTrue()
+        assertThat(featureFlagsDebug.isEnabled(flag)).isTrue()
+    }
+
+    @Test
+    fun serverSide_OverrideUncached_NoRestart() {
+        // No one has read the flag, so it's not in the cache.
+        serverFlagReader.setFlagValue(
+            teamfoodableFlagA.namespace, teamfoodableFlagA.name, !teamfoodableFlagA.default)
+        verify(restarter, never()).restartSystemUI(anyString())
+    }
+
+    @Test
+    fun serverSide_Override_Restarts() {
+        // Read it to put it in the cache.
+        featureFlagsDebug.isEnabled(teamfoodableFlagA)
+        serverFlagReader.setFlagValue(
+            teamfoodableFlagA.namespace, teamfoodableFlagA.name, !teamfoodableFlagA.default)
+        verify(restarter).restartSystemUI(anyString())
+    }
+
+    @Test
+    fun serverSide_RedundantOverride_NoRestart() {
+        // Read it to put it in the cache.
+        featureFlagsDebug.isEnabled(teamfoodableFlagA)
+        serverFlagReader.setFlagValue(
+            teamfoodableFlagA.namespace, teamfoodableFlagA.name, teamfoodableFlagA.default)
+        verify(restarter, never()).restartSystemUI(anyString())
     }
 
     @Test
@@ -482,13 +509,13 @@
             .thenReturn("override7")
 
         // WHEN the flags have been accessed
-        assertThat(mFeatureFlagsDebug.isEnabled(flag1)).isTrue()
-        assertThat(mFeatureFlagsDebug.isEnabled(flag2)).isTrue()
-        assertThat(mFeatureFlagsDebug.isEnabled(flag3)).isFalse()
-        assertThat(mFeatureFlagsDebug.getString(flag4)).isEmpty()
-        assertThat(mFeatureFlagsDebug.getString(flag5)).isEqualTo("flag5default")
-        assertThat(mFeatureFlagsDebug.getString(flag6)).isEqualTo("resource1006")
-        assertThat(mFeatureFlagsDebug.getString(flag7)).isEqualTo("override7")
+        assertThat(featureFlagsDebug.isEnabled(flag1)).isTrue()
+        assertThat(featureFlagsDebug.isEnabled(flag2)).isTrue()
+        assertThat(featureFlagsDebug.isEnabled(flag3)).isFalse()
+        assertThat(featureFlagsDebug.getString(flag4)).isEmpty()
+        assertThat(featureFlagsDebug.getString(flag5)).isEqualTo("flag5default")
+        assertThat(featureFlagsDebug.getString(flag6)).isEqualTo("resource1006")
+        assertThat(featureFlagsDebug.getString(flag7)).isEqualTo("override7")
 
         // THEN the dump contains the flags and the default values
         val dump = dumpToString()
@@ -527,7 +554,7 @@
     private fun dumpToString(): String {
         val sw = StringWriter()
         val pw = PrintWriter(sw)
-        mFeatureFlagsDebug.dump(pw, emptyArray<String>())
+        featureFlagsDebug.dump(pw, emptyArray<String>())
         pw.flush()
         return sw.toString()
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
index 4c6028c..917147b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
@@ -24,6 +24,8 @@
 import org.junit.Before
 import org.junit.Test
 import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.never
 import org.mockito.MockitoAnnotations
 import org.mockito.Mockito.`when` as whenever
 
@@ -33,7 +35,7 @@
  */
 @SmallTest
 class FeatureFlagsReleaseTest : SysuiTestCase() {
-    private lateinit var mFeatureFlagsRelease: FeatureFlagsRelease
+    private lateinit var featureFlagsRelease: FeatureFlagsRelease
 
     @Mock private lateinit var mResources: Resources
     @Mock private lateinit var mSystemProperties: SystemPropertiesHelper
@@ -41,15 +43,21 @@
     private val flagMap = mutableMapOf<String, Flag<*>>()
     private val serverFlagReader = ServerFlagReaderFake()
 
+
+    private val flagA = ReleasedFlag(501, name = "a", namespace = "test")
+
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-        mFeatureFlagsRelease = FeatureFlagsRelease(
+        flagMap.put(flagA.name, flagA)
+        featureFlagsRelease = FeatureFlagsRelease(
             mResources,
             mSystemProperties,
             serverFlagReader,
             flagMap,
             restarter)
+
+        featureFlagsRelease.init()
     }
 
     @Test
@@ -60,7 +68,7 @@
         val flagNamespace = "test"
         val flag = ResourceBooleanFlag(flagId, flagName, flagNamespace, flagResourceId)
         whenever(mResources.getBoolean(flagResourceId)).thenReturn(true)
-        assertThat(mFeatureFlagsRelease.isEnabled(flag)).isTrue()
+        assertThat(featureFlagsRelease.isEnabled(flag)).isTrue()
     }
 
     @Test
@@ -70,16 +78,16 @@
         whenever(mResources.getString(1003)).thenReturn(null)
         whenever(mResources.getString(1004)).thenAnswer { throw NameNotFoundException() }
 
-        assertThat(mFeatureFlagsRelease.getString(
+        assertThat(featureFlagsRelease.getString(
             ResourceStringFlag(1, "1", "test", 1001))).isEqualTo("")
-        assertThat(mFeatureFlagsRelease.getString(
+        assertThat(featureFlagsRelease.getString(
             ResourceStringFlag(2, "2", "test", 1002))).isEqualTo("res2")
 
         assertThrows(NullPointerException::class.java) {
-            mFeatureFlagsRelease.getString(ResourceStringFlag(3, "3", "test", 1003))
+            featureFlagsRelease.getString(ResourceStringFlag(3, "3", "test", 1003))
         }
         assertThrows(NameNotFoundException::class.java) {
-            mFeatureFlagsRelease.getString(ResourceStringFlag(4, "4", "test", 1004))
+            featureFlagsRelease.getString(ResourceStringFlag(4, "4", "test", 1004))
         }
     }
 
@@ -92,7 +100,7 @@
 
         val flag = SysPropBooleanFlag(flagId, flagName, flagNamespace, flagDefault)
         whenever(mSystemProperties.getBoolean(flagName, flagDefault)).thenReturn(flagDefault)
-        assertThat(mFeatureFlagsRelease.isEnabled(flag)).isEqualTo(flagDefault)
+        assertThat(featureFlagsRelease.isEnabled(flag)).isEqualTo(flagDefault)
     }
 
     @Test
@@ -101,7 +109,7 @@
 
         serverFlagReader.setFlagValue(flag.namespace, flag.name, false)
 
-        assertThat(mFeatureFlagsRelease.isEnabled(flag)).isFalse()
+        assertThat(featureFlagsRelease.isEnabled(flag)).isFalse()
     }
 
     @Test
@@ -110,6 +118,32 @@
 
         serverFlagReader.setFlagValue(flag.namespace, flag.name, true)
 
-        assertThat(mFeatureFlagsRelease.isEnabled(flag)).isFalse()
+        assertThat(featureFlagsRelease.isEnabled(flag)).isFalse()
+    }
+
+    @Test
+    fun serverSide_OverrideUncached_NoRestart() {
+        // No one has read the flag, so it's not in the cache.
+        serverFlagReader.setFlagValue(
+            flagA.namespace, flagA.name, !flagA.default)
+        Mockito.verify(restarter, never()).restartSystemUI(Mockito.anyString())
+    }
+
+    @Test
+    fun serverSide_Override_Restarts() {
+        // Read it to put it in the cache.
+        featureFlagsRelease.isEnabled(flagA)
+        serverFlagReader.setFlagValue(
+            flagA.namespace, flagA.name, !flagA.default)
+        Mockito.verify(restarter).restartSystemUI(Mockito.anyString())
+    }
+
+    @Test
+    fun serverSide_RedundantOverride_NoRestart() {
+        // Read it to put it in the cache.
+        featureFlagsRelease.isEnabled(flagA)
+        serverFlagReader.setFlagValue(
+            flagA.namespace, flagA.name, flagA.default)
+        Mockito.verify(restarter, never()).restartSystemUI(Mockito.anyString())
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
index 2e98006..953b7fb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
@@ -21,11 +21,13 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.DeviceConfigProxyFake
 import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
 import com.android.systemui.util.time.FakeSystemClock
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
+import org.mockito.Mockito.anyString
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
@@ -57,18 +59,18 @@
         deviceConfig.setProperty(NAMESPACE, "flag_1", "1", false)
         executor.runAllReady()
 
-        verify(changeListener).onChange(flag)
+        verify(changeListener).onChange(flag, "1")
     }
 
     @Test
     fun testChange_ignoresListenersDuringTest() {
         val serverFlagReader = ServerFlagReaderImpl(NAMESPACE, deviceConfig, executor, true)
-        val flag = ReleasedFlag(1, "1", "test")
+        val flag = ReleasedFlag(1, "1", "   test")
         serverFlagReader.listenForChanges(listOf(flag), changeListener)
 
         deviceConfig.setProperty(NAMESPACE, "flag_override_1", "1", false)
         executor.runAllReady()
 
-        verify(changeListener, never()).onChange(flag)
+        verify(changeListener, never()).onChange(any(), anyString())
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
index e66be08..2ab1b99 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
@@ -93,7 +93,7 @@
     }
 
     @Test
-    fun shouldUpdateSideFps() = runTest {
+    fun shouldUpdateSideFps_show() = runTest {
         var count = 0
         val job = underTest.shouldUpdateSideFps.onEach { count++ }.launchIn(this)
         repository.setPrimaryShow(true)
@@ -104,6 +104,18 @@
     }
 
     @Test
+    fun shouldUpdateSideFps_hide() = runTest {
+        repository.setPrimaryShow(true)
+        var count = 0
+        val job = underTest.shouldUpdateSideFps.onEach { count++ }.launchIn(this)
+        repository.setPrimaryShow(false)
+        // Run the tasks that are pending at this point of virtual time.
+        runCurrent()
+        assertThat(count).isEqualTo(1)
+        job.cancel()
+    }
+
+    @Test
     fun sideFpsShowing() = runTest {
         var sideFpsIsShowing = false
         val job = underTest.sideFpsShowing.onEach { sideFpsIsShowing = it }.launchIn(this)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index fd353af..df13fdd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -94,7 +94,6 @@
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.nullable
 import com.android.systemui.util.mockito.withArgCaptor
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
@@ -1763,7 +1762,7 @@
     fun tapContentView_showOverLockscreen_openActivity() {
         // WHEN we are on lockscreen and this activity can show over lockscreen
         whenever(keyguardStateController.isShowing).thenReturn(true)
-        whenever(activityIntentHelper.wouldShowOverLockscreen(any(), any())).thenReturn(true)
+        whenever(activityIntentHelper.wouldPendingShowOverLockscreen(any(), any())).thenReturn(true)
 
         val clickIntent = mock(Intent::class.java)
         val pendingIntent = mock(PendingIntent::class.java)
@@ -1774,16 +1773,20 @@
         player.bindPlayer(data, KEY)
         verify(viewHolder.player).setOnClickListener(captor.capture())
 
-        // THEN it shows without dismissing keyguard first
+        // THEN it sends the PendingIntent without dismissing keyguard first,
+        // and does not use the Intent directly (see b/271845008)
         captor.value.onClick(viewHolder.player)
-        verify(activityStarter).startActivity(eq(clickIntent), eq(true), nullable(), eq(true))
+        verify(pendingIntent).send()
+        verify(pendingIntent, never()).getIntent()
+        verify(activityStarter, never()).postStartActivityDismissingKeyguard(eq(clickIntent), any())
     }
 
     @Test
     fun tapContentView_noShowOverLockscreen_dismissKeyguard() {
         // WHEN we are on lockscreen and the activity cannot show over lockscreen
         whenever(keyguardStateController.isShowing).thenReturn(true)
-        whenever(activityIntentHelper.wouldShowOverLockscreen(any(), any())).thenReturn(false)
+        whenever(activityIntentHelper.wouldPendingShowOverLockscreen(any(), any()))
+            .thenReturn(false)
 
         val clickIntent = mock(Intent::class.java)
         val pendingIntent = mock(PendingIntent::class.java)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 56e060d..17d8799 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -16,12 +16,13 @@
 
 package com.android.systemui.media.dialog;
 
-import static android.media.RouteListingPreference.Item.SELECTION_BEHAVIOR_GO_TO_APP;
-import static android.media.RouteListingPreference.Item.SELECTION_BEHAVIOR_NONE;
 import static android.media.RouteListingPreference.Item.SUBTEXT_AD_ROUTING_DISALLOWED;
 import static android.media.RouteListingPreference.Item.SUBTEXT_CUSTOM;
 import static android.media.RouteListingPreference.Item.SUBTEXT_SUBSCRIPTION_REQUIRED;
 
+import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_GO_TO_APP;
+import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_NONE;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.mock;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/data/repository/MultiShadeRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/data/repository/MultiShadeRepositoryTest.kt
new file mode 100644
index 0000000..ceacaf9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/multishade/data/repository/MultiShadeRepositoryTest.kt
@@ -0,0 +1,191 @@
+/*
+ * 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.data.repository
+
+import android.content.Context
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.multishade.data.model.MultiShadeInteractionModel
+import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
+import com.android.systemui.multishade.shared.model.ProxiedInputModel
+import com.android.systemui.multishade.shared.model.ShadeConfig
+import com.android.systemui.multishade.shared.model.ShadeId
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class MultiShadeRepositoryTest : SysuiTestCase() {
+
+    private lateinit var inputProxy: MultiShadeInputProxy
+
+    @Before
+    fun setUp() {
+        inputProxy = MultiShadeInputProxy()
+    }
+
+    @Test
+    fun proxiedInput() = runTest {
+        val underTest = create()
+        val latest: ProxiedInputModel? by collectLastValue(underTest.proxiedInput)
+
+        assertWithMessage("proxiedInput should start with null").that(latest).isNull()
+
+        inputProxy.onProxiedInput(ProxiedInputModel.OnTap)
+        assertThat(latest).isEqualTo(ProxiedInputModel.OnTap)
+
+        inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0f, 100f))
+        assertThat(latest).isEqualTo(ProxiedInputModel.OnDrag(0f, 100f))
+
+        inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0f, 120f))
+        assertThat(latest).isEqualTo(ProxiedInputModel.OnDrag(0f, 120f))
+
+        inputProxy.onProxiedInput(ProxiedInputModel.OnDragEnd)
+        assertThat(latest).isEqualTo(ProxiedInputModel.OnDragEnd)
+    }
+
+    @Test
+    fun shadeConfig_dualShadeEnabled() = runTest {
+        overrideResource(R.bool.dual_shade_enabled, true)
+        val underTest = create()
+        val shadeConfig: ShadeConfig? by collectLastValue(underTest.shadeConfig)
+
+        assertThat(shadeConfig).isInstanceOf(ShadeConfig.DualShadeConfig::class.java)
+    }
+
+    @Test
+    fun shadeConfig_dualShadeNotEnabled() = runTest {
+        overrideResource(R.bool.dual_shade_enabled, false)
+        val underTest = create()
+        val shadeConfig: ShadeConfig? by collectLastValue(underTest.shadeConfig)
+
+        assertThat(shadeConfig).isInstanceOf(ShadeConfig.SingleShadeConfig::class.java)
+    }
+
+    @Test
+    fun forceCollapseAll() = runTest {
+        val underTest = create()
+        val forceCollapseAll: Boolean? by collectLastValue(underTest.forceCollapseAll)
+
+        assertWithMessage("forceCollapseAll should start as false!")
+            .that(forceCollapseAll)
+            .isFalse()
+
+        underTest.setForceCollapseAll(true)
+        assertThat(forceCollapseAll).isTrue()
+
+        underTest.setForceCollapseAll(false)
+        assertThat(forceCollapseAll).isFalse()
+    }
+
+    @Test
+    fun shadeInteraction() = runTest {
+        val underTest = create()
+        val shadeInteraction: MultiShadeInteractionModel? by
+            collectLastValue(underTest.shadeInteraction)
+
+        assertWithMessage("shadeInteraction should start as null!").that(shadeInteraction).isNull()
+
+        underTest.setShadeInteraction(
+            MultiShadeInteractionModel(shadeId = ShadeId.LEFT, isProxied = false)
+        )
+        assertThat(shadeInteraction)
+            .isEqualTo(MultiShadeInteractionModel(shadeId = ShadeId.LEFT, isProxied = false))
+
+        underTest.setShadeInteraction(
+            MultiShadeInteractionModel(shadeId = ShadeId.RIGHT, isProxied = true)
+        )
+        assertThat(shadeInteraction)
+            .isEqualTo(MultiShadeInteractionModel(shadeId = ShadeId.RIGHT, isProxied = true))
+
+        underTest.setShadeInteraction(null)
+        assertThat(shadeInteraction).isNull()
+    }
+
+    @Test
+    fun expansion() = runTest {
+        val underTest = create()
+        val leftExpansion: Float? by
+            collectLastValue(underTest.getShade(ShadeId.LEFT).map { it.expansion })
+        val rightExpansion: Float? by
+            collectLastValue(underTest.getShade(ShadeId.RIGHT).map { it.expansion })
+        val singleExpansion: Float? by
+            collectLastValue(underTest.getShade(ShadeId.SINGLE).map { it.expansion })
+
+        assertWithMessage("expansion should start as 0!").that(leftExpansion).isZero()
+        assertWithMessage("expansion should start as 0!").that(rightExpansion).isZero()
+        assertWithMessage("expansion should start as 0!").that(singleExpansion).isZero()
+
+        underTest.setExpansion(
+            shadeId = ShadeId.LEFT,
+            0.4f,
+        )
+        assertThat(leftExpansion).isEqualTo(0.4f)
+        assertThat(rightExpansion).isEqualTo(0f)
+        assertThat(singleExpansion).isEqualTo(0f)
+
+        underTest.setExpansion(
+            shadeId = ShadeId.RIGHT,
+            0.73f,
+        )
+        assertThat(leftExpansion).isEqualTo(0.4f)
+        assertThat(rightExpansion).isEqualTo(0.73f)
+        assertThat(singleExpansion).isEqualTo(0f)
+
+        underTest.setExpansion(
+            shadeId = ShadeId.LEFT,
+            0.1f,
+        )
+        underTest.setExpansion(
+            shadeId = ShadeId.SINGLE,
+            0.88f,
+        )
+        assertThat(leftExpansion).isEqualTo(0.1f)
+        assertThat(rightExpansion).isEqualTo(0.73f)
+        assertThat(singleExpansion).isEqualTo(0.88f)
+    }
+
+    private fun create(): MultiShadeRepository {
+        return create(
+            context = context,
+            inputProxy = inputProxy,
+        )
+    }
+
+    companion object {
+        fun create(
+            context: Context,
+            inputProxy: MultiShadeInputProxy,
+        ): MultiShadeRepository {
+            return MultiShadeRepository(
+                applicationContext = context,
+                inputProxy = inputProxy,
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractorTest.kt
new file mode 100644
index 0000000..415e68f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractorTest.kt
@@ -0,0 +1,301 @@
+/*
+ * 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.domain.interactor
+
+import android.content.Context
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
+import com.android.systemui.multishade.data.repository.MultiShadeRepositoryTest
+import com.android.systemui.multishade.shared.model.ProxiedInputModel
+import com.android.systemui.multishade.shared.model.ShadeId
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+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.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class MultiShadeInteractorTest : SysuiTestCase() {
+
+    private lateinit var testScope: TestScope
+    private lateinit var inputProxy: MultiShadeInputProxy
+
+    @Before
+    fun setUp() {
+        testScope = TestScope()
+        inputProxy = MultiShadeInputProxy()
+    }
+
+    @Test
+    fun maxShadeExpansion() =
+        testScope.runTest {
+            val underTest = create()
+            val maxShadeExpansion: Float? by collectLastValue(underTest.maxShadeExpansion)
+            assertWithMessage("maxShadeExpansion must start with 0.0!")
+                .that(maxShadeExpansion)
+                .isEqualTo(0f)
+
+            underTest.setExpansion(shadeId = ShadeId.LEFT, expansion = 0.441f)
+            assertThat(maxShadeExpansion).isEqualTo(0.441f)
+
+            underTest.setExpansion(shadeId = ShadeId.RIGHT, expansion = 0.442f)
+            assertThat(maxShadeExpansion).isEqualTo(0.442f)
+
+            underTest.setExpansion(shadeId = ShadeId.RIGHT, expansion = 0f)
+            assertThat(maxShadeExpansion).isEqualTo(0.441f)
+
+            underTest.setExpansion(shadeId = ShadeId.LEFT, expansion = 0f)
+            assertThat(maxShadeExpansion).isEqualTo(0f)
+        }
+
+    @Test
+    fun isVisible_dualShadeConfig() =
+        testScope.runTest {
+            overrideResource(R.bool.dual_shade_enabled, true)
+            val underTest = create()
+            val isLeftShadeVisible: Boolean? by collectLastValue(underTest.isVisible(ShadeId.LEFT))
+            val isRightShadeVisible: Boolean? by
+                collectLastValue(underTest.isVisible(ShadeId.RIGHT))
+            val isSingleShadeVisible: Boolean? by
+                collectLastValue(underTest.isVisible(ShadeId.SINGLE))
+
+            assertThat(isLeftShadeVisible).isTrue()
+            assertThat(isRightShadeVisible).isTrue()
+            assertThat(isSingleShadeVisible).isFalse()
+        }
+
+    @Test
+    fun isVisible_singleShadeConfig() =
+        testScope.runTest {
+            overrideResource(R.bool.dual_shade_enabled, false)
+            val underTest = create()
+            val isLeftShadeVisible: Boolean? by collectLastValue(underTest.isVisible(ShadeId.LEFT))
+            val isRightShadeVisible: Boolean? by
+                collectLastValue(underTest.isVisible(ShadeId.RIGHT))
+            val isSingleShadeVisible: Boolean? by
+                collectLastValue(underTest.isVisible(ShadeId.SINGLE))
+
+            assertThat(isLeftShadeVisible).isFalse()
+            assertThat(isRightShadeVisible).isFalse()
+            assertThat(isSingleShadeVisible).isTrue()
+        }
+
+    @Test
+    fun isNonProxiedInputAllowed() =
+        testScope.runTest {
+            val underTest = create()
+            val isLeftShadeNonProxiedInputAllowed: Boolean? by
+                collectLastValue(underTest.isNonProxiedInputAllowed(ShadeId.LEFT))
+            assertWithMessage("isNonProxiedInputAllowed should start as true!")
+                .that(isLeftShadeNonProxiedInputAllowed)
+                .isTrue()
+
+            // Need to collect proxied input so the flows become hot as the gesture cancelation code
+            // logic sits in side the proxiedInput flow for each shade.
+            collectLastValue(underTest.proxiedInput(ShadeId.LEFT))
+            collectLastValue(underTest.proxiedInput(ShadeId.RIGHT))
+
+            // Starting a proxied interaction on the LEFT shade disallows non-proxied interaction on
+            // the
+            // same shade.
+            inputProxy.onProxiedInput(
+                ProxiedInputModel.OnDrag(xFraction = 0f, yDragAmountPx = 123f)
+            )
+            assertThat(isLeftShadeNonProxiedInputAllowed).isFalse()
+
+            // Registering the end of the proxied interaction re-allows it.
+            inputProxy.onProxiedInput(ProxiedInputModel.OnDragEnd)
+            assertThat(isLeftShadeNonProxiedInputAllowed).isTrue()
+
+            // Starting a proxied interaction on the RIGHT shade force-collapses the LEFT shade,
+            // disallowing non-proxied input on the LEFT shade.
+            inputProxy.onProxiedInput(
+                ProxiedInputModel.OnDrag(xFraction = 1f, yDragAmountPx = 123f)
+            )
+            assertThat(isLeftShadeNonProxiedInputAllowed).isFalse()
+
+            // Registering the end of the interaction on the RIGHT shade re-allows it.
+            inputProxy.onProxiedInput(ProxiedInputModel.OnDragEnd)
+            assertThat(isLeftShadeNonProxiedInputAllowed).isTrue()
+        }
+
+    @Test
+    fun isForceCollapsed_whenOtherShadeInteractionUnderway() =
+        testScope.runTest {
+            val underTest = create()
+            val isLeftShadeForceCollapsed: Boolean? by
+                collectLastValue(underTest.isForceCollapsed(ShadeId.LEFT))
+            val isRightShadeForceCollapsed: Boolean? by
+                collectLastValue(underTest.isForceCollapsed(ShadeId.RIGHT))
+            val isSingleShadeForceCollapsed: Boolean? by
+                collectLastValue(underTest.isForceCollapsed(ShadeId.SINGLE))
+
+            assertWithMessage("isForceCollapsed should start as false!")
+                .that(isLeftShadeForceCollapsed)
+                .isFalse()
+            assertWithMessage("isForceCollapsed should start as false!")
+                .that(isRightShadeForceCollapsed)
+                .isFalse()
+            assertWithMessage("isForceCollapsed should start as false!")
+                .that(isSingleShadeForceCollapsed)
+                .isFalse()
+
+            // Registering the start of an interaction on the RIGHT shade force-collapses the LEFT
+            // shade.
+            underTest.onUserInteractionStarted(ShadeId.RIGHT)
+            assertThat(isLeftShadeForceCollapsed).isTrue()
+            assertThat(isRightShadeForceCollapsed).isFalse()
+            assertThat(isSingleShadeForceCollapsed).isFalse()
+
+            // Registering the end of the interaction on the RIGHT shade re-allows it.
+            underTest.onUserInteractionEnded(ShadeId.RIGHT)
+            assertThat(isLeftShadeForceCollapsed).isFalse()
+            assertThat(isRightShadeForceCollapsed).isFalse()
+            assertThat(isSingleShadeForceCollapsed).isFalse()
+
+            // Registering the start of an interaction on the LEFT shade force-collapses the RIGHT
+            // shade.
+            underTest.onUserInteractionStarted(ShadeId.LEFT)
+            assertThat(isLeftShadeForceCollapsed).isFalse()
+            assertThat(isRightShadeForceCollapsed).isTrue()
+            assertThat(isSingleShadeForceCollapsed).isFalse()
+
+            // Registering the end of the interaction on the LEFT shade re-allows it.
+            underTest.onUserInteractionEnded(ShadeId.LEFT)
+            assertThat(isLeftShadeForceCollapsed).isFalse()
+            assertThat(isRightShadeForceCollapsed).isFalse()
+            assertThat(isSingleShadeForceCollapsed).isFalse()
+        }
+
+    @Test
+    fun collapseAll() =
+        testScope.runTest {
+            val underTest = create()
+            val isLeftShadeForceCollapsed: Boolean? by
+                collectLastValue(underTest.isForceCollapsed(ShadeId.LEFT))
+            val isRightShadeForceCollapsed: Boolean? by
+                collectLastValue(underTest.isForceCollapsed(ShadeId.RIGHT))
+            val isSingleShadeForceCollapsed: Boolean? by
+                collectLastValue(underTest.isForceCollapsed(ShadeId.SINGLE))
+
+            assertWithMessage("isForceCollapsed should start as false!")
+                .that(isLeftShadeForceCollapsed)
+                .isFalse()
+            assertWithMessage("isForceCollapsed should start as false!")
+                .that(isRightShadeForceCollapsed)
+                .isFalse()
+            assertWithMessage("isForceCollapsed should start as false!")
+                .that(isSingleShadeForceCollapsed)
+                .isFalse()
+
+            underTest.collapseAll()
+            assertThat(isLeftShadeForceCollapsed).isTrue()
+            assertThat(isRightShadeForceCollapsed).isTrue()
+            assertThat(isSingleShadeForceCollapsed).isTrue()
+
+            // Receiving proxied input on that's not a tap gesture, on the left-hand side resets the
+            // "collapse all". Note that now the RIGHT shade is force-collapsed because we're
+            // interacting with the LEFT shade.
+            inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0f, 0f))
+            assertThat(isLeftShadeForceCollapsed).isFalse()
+            assertThat(isRightShadeForceCollapsed).isTrue()
+            assertThat(isSingleShadeForceCollapsed).isFalse()
+        }
+
+    @Test
+    fun onTapOutside_collapsesAll() =
+        testScope.runTest {
+            val underTest = create()
+            val isLeftShadeForceCollapsed: Boolean? by
+                collectLastValue(underTest.isForceCollapsed(ShadeId.LEFT))
+            val isRightShadeForceCollapsed: Boolean? by
+                collectLastValue(underTest.isForceCollapsed(ShadeId.RIGHT))
+            val isSingleShadeForceCollapsed: Boolean? by
+                collectLastValue(underTest.isForceCollapsed(ShadeId.SINGLE))
+
+            assertWithMessage("isForceCollapsed should start as false!")
+                .that(isLeftShadeForceCollapsed)
+                .isFalse()
+            assertWithMessage("isForceCollapsed should start as false!")
+                .that(isRightShadeForceCollapsed)
+                .isFalse()
+            assertWithMessage("isForceCollapsed should start as false!")
+                .that(isSingleShadeForceCollapsed)
+                .isFalse()
+
+            inputProxy.onProxiedInput(ProxiedInputModel.OnTap)
+            assertThat(isLeftShadeForceCollapsed).isTrue()
+            assertThat(isRightShadeForceCollapsed).isTrue()
+            assertThat(isSingleShadeForceCollapsed).isTrue()
+        }
+
+    @Test
+    fun proxiedInput_ignoredWhileNonProxiedGestureUnderway() =
+        testScope.runTest {
+            val underTest = create()
+            val proxiedInput: ProxiedInputModel? by
+                collectLastValue(underTest.proxiedInput(ShadeId.RIGHT))
+            underTest.onUserInteractionStarted(shadeId = ShadeId.RIGHT)
+
+            inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0.9f, 100f))
+            assertThat(proxiedInput).isNull()
+
+            inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0.8f, 110f))
+            assertThat(proxiedInput).isNull()
+
+            underTest.onUserInteractionEnded(shadeId = ShadeId.RIGHT)
+
+            inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0.9f, 100f))
+            assertThat(proxiedInput).isNotNull()
+        }
+
+    private fun create(): MultiShadeInteractor {
+        return create(
+            testScope = testScope,
+            context = context,
+            inputProxy = inputProxy,
+        )
+    }
+
+    companion object {
+        fun create(
+            testScope: TestScope,
+            context: Context,
+            inputProxy: MultiShadeInputProxy,
+        ): MultiShadeInteractor {
+            return MultiShadeInteractor(
+                applicationScope = testScope.backgroundScope,
+                repository =
+                    MultiShadeRepositoryTest.create(
+                        context = context,
+                        inputProxy = inputProxy,
+                    ),
+                inputProxy = inputProxy,
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/ui/viewmodel/MultiShadeViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/ui/viewmodel/MultiShadeViewModelTest.kt
new file mode 100644
index 0000000..0484515
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/multishade/ui/viewmodel/MultiShadeViewModelTest.kt
@@ -0,0 +1,127 @@
+/*
+ * 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.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
+import com.android.systemui.multishade.domain.interactor.MultiShadeInteractorTest
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+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.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class MultiShadeViewModelTest : SysuiTestCase() {
+
+    private lateinit var testScope: TestScope
+    private lateinit var inputProxy: MultiShadeInputProxy
+
+    @Before
+    fun setUp() {
+        testScope = TestScope()
+        inputProxy = MultiShadeInputProxy()
+    }
+
+    @Test
+    fun scrim_whenDualShadeCollapsed() =
+        testScope.runTest {
+            val alpha = 0.5f
+            overrideResource(R.dimen.dual_shade_scrim_alpha, alpha)
+            overrideResource(R.bool.dual_shade_enabled, true)
+
+            val underTest = create()
+            val scrimAlpha: Float? by collectLastValue(underTest.scrimAlpha)
+            val isScrimEnabled: Boolean? by collectLastValue(underTest.isScrimEnabled)
+
+            assertThat(scrimAlpha).isZero()
+            assertThat(isScrimEnabled).isFalse()
+        }
+
+    @Test
+    fun scrim_whenDualShadeExpanded() =
+        testScope.runTest {
+            val alpha = 0.5f
+            overrideResource(R.dimen.dual_shade_scrim_alpha, alpha)
+            overrideResource(R.bool.dual_shade_enabled, true)
+            val underTest = create()
+            val scrimAlpha: Float? by collectLastValue(underTest.scrimAlpha)
+            val isScrimEnabled: Boolean? by collectLastValue(underTest.isScrimEnabled)
+            assertThat(scrimAlpha).isZero()
+            assertThat(isScrimEnabled).isFalse()
+
+            underTest.leftShade.onExpansionChanged(0.5f)
+            assertThat(scrimAlpha).isEqualTo(alpha * 0.5f)
+            assertThat(isScrimEnabled).isTrue()
+
+            underTest.rightShade.onExpansionChanged(1f)
+            assertThat(scrimAlpha).isEqualTo(alpha * 1f)
+            assertThat(isScrimEnabled).isTrue()
+        }
+
+    @Test
+    fun scrim_whenSingleShadeCollapsed() =
+        testScope.runTest {
+            val alpha = 0.5f
+            overrideResource(R.dimen.dual_shade_scrim_alpha, alpha)
+            overrideResource(R.bool.dual_shade_enabled, false)
+
+            val underTest = create()
+            val scrimAlpha: Float? by collectLastValue(underTest.scrimAlpha)
+            val isScrimEnabled: Boolean? by collectLastValue(underTest.isScrimEnabled)
+
+            assertThat(scrimAlpha).isZero()
+            assertThat(isScrimEnabled).isFalse()
+        }
+
+    @Test
+    fun scrim_whenSingleShadeExpanded() =
+        testScope.runTest {
+            val alpha = 0.5f
+            overrideResource(R.dimen.dual_shade_scrim_alpha, alpha)
+            overrideResource(R.bool.dual_shade_enabled, false)
+            val underTest = create()
+            val scrimAlpha: Float? by collectLastValue(underTest.scrimAlpha)
+            val isScrimEnabled: Boolean? by collectLastValue(underTest.isScrimEnabled)
+
+            underTest.singleShade.onExpansionChanged(0.95f)
+
+            assertThat(scrimAlpha).isZero()
+            assertThat(isScrimEnabled).isFalse()
+        }
+
+    private fun create(): MultiShadeViewModel {
+        return MultiShadeViewModel(
+            viewModelScope = testScope.backgroundScope,
+            interactor =
+                MultiShadeInteractorTest.create(
+                    testScope = testScope,
+                    context = context,
+                    inputProxy = inputProxy,
+                ),
+        )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/ui/viewmodel/ShadeViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/ui/viewmodel/ShadeViewModelTest.kt
new file mode 100644
index 0000000..e32aac5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/multishade/ui/viewmodel/ShadeViewModelTest.kt
@@ -0,0 +1,226 @@
+/*
+ * 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.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
+import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor
+import com.android.systemui.multishade.domain.interactor.MultiShadeInteractorTest
+import com.android.systemui.multishade.shared.model.ProxiedInputModel
+import com.android.systemui.multishade.shared.model.ShadeId
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+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.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class ShadeViewModelTest : SysuiTestCase() {
+
+    private lateinit var testScope: TestScope
+    private lateinit var inputProxy: MultiShadeInputProxy
+    private var interactor: MultiShadeInteractor? = null
+
+    @Before
+    fun setUp() {
+        testScope = TestScope()
+        inputProxy = MultiShadeInputProxy()
+    }
+
+    @Test
+    fun isVisible_dualShadeConfig() =
+        testScope.runTest {
+            overrideResource(R.bool.dual_shade_enabled, true)
+            val isLeftShadeVisible: Boolean? by collectLastValue(create(ShadeId.LEFT).isVisible)
+            val isRightShadeVisible: Boolean? by collectLastValue(create(ShadeId.RIGHT).isVisible)
+            val isSingleShadeVisible: Boolean? by collectLastValue(create(ShadeId.SINGLE).isVisible)
+
+            assertThat(isLeftShadeVisible).isTrue()
+            assertThat(isRightShadeVisible).isTrue()
+            assertThat(isSingleShadeVisible).isFalse()
+        }
+
+    @Test
+    fun isVisible_singleShadeConfig() =
+        testScope.runTest {
+            overrideResource(R.bool.dual_shade_enabled, false)
+            val isLeftShadeVisible: Boolean? by collectLastValue(create(ShadeId.LEFT).isVisible)
+            val isRightShadeVisible: Boolean? by collectLastValue(create(ShadeId.RIGHT).isVisible)
+            val isSingleShadeVisible: Boolean? by collectLastValue(create(ShadeId.SINGLE).isVisible)
+
+            assertThat(isLeftShadeVisible).isFalse()
+            assertThat(isRightShadeVisible).isFalse()
+            assertThat(isSingleShadeVisible).isTrue()
+        }
+
+    @Test
+    fun isSwipingEnabled() =
+        testScope.runTest {
+            val underTest = create(ShadeId.LEFT)
+            val isSwipingEnabled: Boolean? by collectLastValue(underTest.isSwipingEnabled)
+            assertWithMessage("isSwipingEnabled should start as true!")
+                .that(isSwipingEnabled)
+                .isTrue()
+
+            // Need to collect proxied input so the flows become hot as the gesture cancelation code
+            // logic sits in side the proxiedInput flow for each shade.
+            collectLastValue(underTest.proxiedInput)
+            collectLastValue(create(ShadeId.RIGHT).proxiedInput)
+
+            // Starting a proxied interaction on the LEFT shade disallows non-proxied interaction on
+            // the
+            // same shade.
+            inputProxy.onProxiedInput(
+                ProxiedInputModel.OnDrag(xFraction = 0f, yDragAmountPx = 123f)
+            )
+            assertThat(isSwipingEnabled).isFalse()
+
+            // Registering the end of the proxied interaction re-allows it.
+            inputProxy.onProxiedInput(ProxiedInputModel.OnDragEnd)
+            assertThat(isSwipingEnabled).isTrue()
+
+            // Starting a proxied interaction on the RIGHT shade force-collapses the LEFT shade,
+            // disallowing non-proxied input on the LEFT shade.
+            inputProxy.onProxiedInput(
+                ProxiedInputModel.OnDrag(xFraction = 1f, yDragAmountPx = 123f)
+            )
+            assertThat(isSwipingEnabled).isFalse()
+
+            // Registering the end of the interaction on the RIGHT shade re-allows it.
+            inputProxy.onProxiedInput(ProxiedInputModel.OnDragEnd)
+            assertThat(isSwipingEnabled).isTrue()
+        }
+
+    @Test
+    fun isForceCollapsed_whenOtherShadeInteractionUnderway() =
+        testScope.runTest {
+            val leftShade = create(ShadeId.LEFT)
+            val rightShade = create(ShadeId.RIGHT)
+            val isLeftShadeForceCollapsed: Boolean? by collectLastValue(leftShade.isForceCollapsed)
+            val isRightShadeForceCollapsed: Boolean? by
+                collectLastValue(rightShade.isForceCollapsed)
+            val isSingleShadeForceCollapsed: Boolean? by
+                collectLastValue(create(ShadeId.SINGLE).isForceCollapsed)
+
+            assertWithMessage("isForceCollapsed should start as false!")
+                .that(isLeftShadeForceCollapsed)
+                .isFalse()
+            assertWithMessage("isForceCollapsed should start as false!")
+                .that(isRightShadeForceCollapsed)
+                .isFalse()
+            assertWithMessage("isForceCollapsed should start as false!")
+                .that(isSingleShadeForceCollapsed)
+                .isFalse()
+
+            // Registering the start of an interaction on the RIGHT shade force-collapses the LEFT
+            // shade.
+            rightShade.onDragStarted()
+            assertThat(isLeftShadeForceCollapsed).isTrue()
+            assertThat(isRightShadeForceCollapsed).isFalse()
+            assertThat(isSingleShadeForceCollapsed).isFalse()
+
+            // Registering the end of the interaction on the RIGHT shade re-allows it.
+            rightShade.onDragEnded()
+            assertThat(isLeftShadeForceCollapsed).isFalse()
+            assertThat(isRightShadeForceCollapsed).isFalse()
+            assertThat(isSingleShadeForceCollapsed).isFalse()
+
+            // Registering the start of an interaction on the LEFT shade force-collapses the RIGHT
+            // shade.
+            leftShade.onDragStarted()
+            assertThat(isLeftShadeForceCollapsed).isFalse()
+            assertThat(isRightShadeForceCollapsed).isTrue()
+            assertThat(isSingleShadeForceCollapsed).isFalse()
+
+            // Registering the end of the interaction on the LEFT shade re-allows it.
+            leftShade.onDragEnded()
+            assertThat(isLeftShadeForceCollapsed).isFalse()
+            assertThat(isRightShadeForceCollapsed).isFalse()
+            assertThat(isSingleShadeForceCollapsed).isFalse()
+        }
+
+    @Test
+    fun onTapOutside_collapsesAll() =
+        testScope.runTest {
+            val isLeftShadeForceCollapsed: Boolean? by
+                collectLastValue(create(ShadeId.LEFT).isForceCollapsed)
+            val isRightShadeForceCollapsed: Boolean? by
+                collectLastValue(create(ShadeId.RIGHT).isForceCollapsed)
+            val isSingleShadeForceCollapsed: Boolean? by
+                collectLastValue(create(ShadeId.SINGLE).isForceCollapsed)
+
+            assertWithMessage("isForceCollapsed should start as false!")
+                .that(isLeftShadeForceCollapsed)
+                .isFalse()
+            assertWithMessage("isForceCollapsed should start as false!")
+                .that(isRightShadeForceCollapsed)
+                .isFalse()
+            assertWithMessage("isForceCollapsed should start as false!")
+                .that(isSingleShadeForceCollapsed)
+                .isFalse()
+
+            inputProxy.onProxiedInput(ProxiedInputModel.OnTap)
+            assertThat(isLeftShadeForceCollapsed).isTrue()
+            assertThat(isRightShadeForceCollapsed).isTrue()
+            assertThat(isSingleShadeForceCollapsed).isTrue()
+        }
+
+    @Test
+    fun proxiedInput_ignoredWhileNonProxiedGestureUnderway() =
+        testScope.runTest {
+            val underTest = create(ShadeId.RIGHT)
+            val proxiedInput: ProxiedInputModel? by collectLastValue(underTest.proxiedInput)
+            underTest.onDragStarted()
+
+            inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0.9f, 100f))
+            assertThat(proxiedInput).isNull()
+
+            inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0.8f, 110f))
+            assertThat(proxiedInput).isNull()
+
+            underTest.onDragEnded()
+
+            inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0.9f, 100f))
+            assertThat(proxiedInput).isNotNull()
+        }
+
+    private fun create(
+        shadeId: ShadeId,
+    ): ShadeViewModel {
+        return ShadeViewModel(
+            viewModelScope = testScope.backgroundScope,
+            shadeId = shadeId,
+            interactor = interactor
+                    ?: MultiShadeInteractorTest.create(
+                            testScope = testScope,
+                            context = context,
+                            inputProxy = inputProxy,
+                        )
+                        .also { interactor = it },
+        )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index 3f940d6..40c733a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -232,7 +232,7 @@
 
         verifyZeroInteractions(context)
         val intentCaptor = argumentCaptor<Intent>()
-        verify(bubbles).showOrHideAppBubble(capture(intentCaptor))
+        verify(bubbles).showOrHideAppBubble(capture(intentCaptor), eq(userTracker.userHandle))
         intentCaptor.value.let { intent ->
             assertThat(intent.action).isEqualTo(Intent.ACTION_CREATE_NOTE)
             assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
@@ -366,7 +366,7 @@
         createNoteTaskController().showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE)
 
         val intentCaptor = argumentCaptor<Intent>()
-        verify(bubbles).showOrHideAppBubble(capture(intentCaptor))
+        verify(bubbles).showOrHideAppBubble(capture(intentCaptor), eq(userTracker.userHandle))
         intentCaptor.value.let { intent ->
             assertThat(intent.action).isEqualTo(Intent.ACTION_CREATE_NOTE)
             assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
@@ -389,7 +389,7 @@
         createNoteTaskController().showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE)
 
         val intentCaptor = argumentCaptor<Intent>()
-        verify(bubbles).showOrHideAppBubble(capture(intentCaptor))
+        verify(bubbles).showOrHideAppBubble(capture(intentCaptor), eq(userTracker.userHandle))
         intentCaptor.value.let { intent ->
             assertThat(intent.action).isEqualTo(Intent.ACTION_CREATE_NOTE)
             assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
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 51492eb..bdb0e7e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -29,12 +29,17 @@
 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.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
@@ -48,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
@@ -63,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
@@ -100,6 +111,8 @@
 
     private lateinit var underTest: NotificationShadeWindowViewController
 
+    private lateinit var testScope: TestScope
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
@@ -112,6 +125,13 @@
             .thenReturn(keyguardSecurityContainerController)
         whenever(keyguardTransitionInteractor.lockscreenToDreamingTransition)
             .thenReturn(emptyFlow<TransitionStep>())
+
+        val featureFlags = FakeFeatureFlags()
+        featureFlags.set(Flags.TRACKPAD_GESTURE_BACK, false)
+        featureFlags.set(Flags.DUAL_SHADE, false)
+
+        val inputProxy = MultiShadeInputProxy()
+        testScope = TestScope()
         underTest =
             NotificationShadeWindowViewController(
                 lockscreenShadeTransitionController,
@@ -138,6 +158,19 @@
                 udfpsOverlayInteractor,
                 keyguardTransitionInteractor,
                 primaryBouncerToGoneTransitionViewModel,
+                featureFlags,
+                {
+                    MultiShadeInteractor(
+                        applicationScope = testScope.backgroundScope,
+                        repository =
+                            MultiShadeRepository(
+                                applicationContext = context,
+                                inputProxy = inputProxy,
+                            ),
+                        inputProxy = inputProxy,
+                    )
+                },
+                FakeSystemClock(),
             )
         underTest.setupExpandedStatusBar()
 
@@ -150,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 2f528a8..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
+++ /dev/null
@@ -1,221 +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.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());
-
-        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
-        );
-        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/smartspace/DreamSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
index a280510..58b44ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
@@ -24,6 +24,7 @@
 import android.testing.TestableLooper
 import android.view.View
 import android.view.ViewGroup
+import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dreams.smartspace.DreamSmartspaceController
@@ -46,6 +47,7 @@
 import org.mockito.Mockito
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
+import org.mockito.Mockito.anyInt
 import org.mockito.MockitoAnnotations
 import org.mockito.Spy
 
@@ -69,12 +71,21 @@
     private lateinit var viewComponent: SmartspaceViewComponent
 
     @Mock
+    private lateinit var weatherViewComponent: SmartspaceViewComponent
+
+    @Spy
+    private var weatherSmartspaceView: SmartspaceView = TestView(context)
+
+    @Mock
     private lateinit var targetFilter: SmartspaceTargetFilter
 
     @Mock
     private lateinit var plugin: BcSmartspaceDataPlugin
 
     @Mock
+    private lateinit var weatherPlugin: BcSmartspaceDataPlugin
+
+    @Mock
     private lateinit var precondition: SmartspacePrecondition
 
     @Spy
@@ -88,6 +99,9 @@
 
     private lateinit var controller: DreamSmartspaceController
 
+    // TODO(b/272811280): Remove usage of real view
+    private val fakeParent = FrameLayout(context)
+
     /**
      * A class which implements SmartspaceView and extends View. This is mocked to provide the right
      * object inheritance and interface implementation used in DreamSmartspaceController
@@ -121,13 +135,17 @@
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-        `when`(viewComponentFactory.create(any(), eq(plugin), any()))
+        `when`(viewComponentFactory.create(any(), eq(plugin), any(), eq(null)))
                 .thenReturn(viewComponent)
         `when`(viewComponent.getView()).thenReturn(smartspaceView)
+        `when`(viewComponentFactory.create(any(), eq(weatherPlugin), any(), any()))
+            .thenReturn(weatherViewComponent)
+        `when`(weatherViewComponent.getView()).thenReturn(weatherSmartspaceView)
         `when`(smartspaceManager.createSmartspaceSession(any())).thenReturn(session)
 
         controller = DreamSmartspaceController(context, smartspaceManager, execution, uiExecutor,
-                viewComponentFactory, precondition, Optional.of(targetFilter), Optional.of(plugin))
+                viewComponentFactory, precondition, Optional.of(targetFilter), Optional.of(plugin),
+        Optional.of(weatherPlugin))
     }
 
     /**
@@ -168,11 +186,11 @@
         `when`(precondition.conditionsMet()).thenReturn(true)
         controller.buildAndConnectView(Mockito.mock(ViewGroup::class.java))
 
-        var stateChangeListener = withArgCaptor<View.OnAttachStateChangeListener> {
-            verify(viewComponentFactory).create(any(), eq(plugin), capture())
+        val stateChangeListener = withArgCaptor<View.OnAttachStateChangeListener> {
+            verify(viewComponentFactory).create(any(), eq(plugin), capture(), eq(null))
         }
 
-        var mockView = Mockito.mock(TestView::class.java)
+        val mockView = Mockito.mock(TestView::class.java)
         `when`(precondition.conditionsMet()).thenReturn(true)
         stateChangeListener.onViewAttachedToWindow(mockView)
 
@@ -183,4 +201,74 @@
 
         verify(session).close()
     }
+
+    /**
+     * Ensures session is created when weather smartspace view is created and attached.
+     */
+    @Test
+    fun testConnectOnWeatherViewCreate() {
+        `when`(precondition.conditionsMet()).thenReturn(true)
+
+        val customView = Mockito.mock(TestView::class.java)
+        val weatherView = controller.buildAndConnectWeatherView(fakeParent, customView)
+        val weatherSmartspaceView = weatherView as SmartspaceView
+        fakeParent.addView(weatherView)
+
+        // Then weather view is created with custom view and the default weatherPlugin.getView
+        // should not be called
+        verify(viewComponentFactory).create(eq(fakeParent), eq(weatherPlugin), any(),
+            eq(customView))
+        verify(weatherPlugin, Mockito.never()).getView(fakeParent)
+
+        // And then session is created
+        controller.stateChangeListener.onViewAttachedToWindow(weatherView)
+        verify(smartspaceManager).createSmartspaceSession(any())
+        verify(weatherSmartspaceView).setPrimaryTextColor(anyInt())
+        verify(weatherSmartspaceView).setDozeAmount(0f)
+    }
+
+    /**
+     * Ensures weather plugin registers target listener when it is added from the controller.
+     */
+    @Test
+    fun testAddListenerInController_registersListenerForWeatherPlugin() {
+        val customView = Mockito.mock(TestView::class.java)
+        `when`(precondition.conditionsMet()).thenReturn(true)
+
+        // Given a session is created
+        val weatherView = controller.buildAndConnectWeatherView(fakeParent, customView)
+        controller.stateChangeListener.onViewAttachedToWindow(weatherView)
+        verify(smartspaceManager).createSmartspaceSession(any())
+
+        // When a listener is added
+        controller.addListenerForWeatherPlugin(listener)
+
+        // Then the listener is registered to the weather plugin only
+        verify(weatherPlugin).registerListener(listener)
+        verify(plugin, Mockito.never()).registerListener(any())
+    }
+
+    /**
+     * Ensures session is closed and weather plugin unregisters the notifier when weather smartspace
+     * view is detached.
+     */
+    @Test
+    fun testDisconnect_emitsEmptyListAndRemovesNotifier() {
+        `when`(precondition.conditionsMet()).thenReturn(true)
+
+        // Given a session is created
+        val customView = Mockito.mock(TestView::class.java)
+        val weatherView = controller.buildAndConnectWeatherView(fakeParent, customView)
+        controller.stateChangeListener.onViewAttachedToWindow(weatherView)
+        verify(smartspaceManager).createSmartspaceSession(any())
+
+        // When view is detached
+        controller.stateChangeListener.onViewDetachedFromWindow(weatherView)
+        // Then the session is closed
+        verify(session).close()
+
+        // And the listener receives an empty list of targets and unregisters the notifier
+        verify(weatherPlugin).onTargetsAvailable(emptyList())
+        verify(weatherPlugin).registerSmartspaceEventNotifier(null)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
index 5170678..ced0734 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
@@ -34,6 +34,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
+import com.android.systemui.statusbar.notification.RemoteInputControllerLogger;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
@@ -41,6 +42,8 @@
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.policy.RemoteInputUriController;
 
+import dagger.Lazy;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -49,8 +52,6 @@
 
 import java.util.Optional;
 
-import dagger.Lazy;
-
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -84,6 +85,7 @@
                 () -> Optional.of(mock(CentralSurfaces.class)),
                 mStateController,
                 mRemoteInputUriController,
+                mock(RemoteInputControllerLogger.class),
                 mClickNotifier,
                 mock(ActionClickLogger.class),
                 mock(DumpManager.class));
@@ -141,6 +143,7 @@
                 Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
                 StatusBarStateController statusBarStateController,
                 RemoteInputUriController remoteInputUriController,
+                RemoteInputControllerLogger remoteInputControllerLogger,
                 NotificationClickNotifier clickNotifier,
                 ActionClickLogger actionClickLogger,
                 DumpManager dumpManager) {
@@ -153,6 +156,7 @@
                     centralSurfacesOptionalLazy,
                     statusBarStateController,
                     remoteInputUriController,
+                    remoteInputControllerLogger,
                     clickNotifier,
                     actionClickLogger,
                     dumpManager);
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 e185922..8e3988b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -293,6 +293,8 @@
 
     private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
 
+    private UserHandle mUser0;
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
@@ -301,6 +303,8 @@
         // For the purposes of this test, just run everything synchronously
         ShellExecutor syncExecutor = new SyncExecutor();
 
+        mUser0 = createUserHande(/* userId= */ 0);
+
         when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
         when(mNotificationShadeWindowView.getViewTreeObserver())
                 .thenReturn(mock(ViewTreeObserver.class));
@@ -339,7 +343,6 @@
         TestableNotificationInterruptStateProviderImpl interruptionStateProvider =
                 new TestableNotificationInterruptStateProviderImpl(mContext.getContentResolver(),
                         mock(PowerManager.class),
-                        mock(IDreamManager.class),
                         mock(AmbientDisplayConfiguration.class),
                         mock(StatusBarStateController.class),
                         mock(KeyguardStateController.class),
@@ -1650,7 +1653,7 @@
         assertThat(mBubbleController.isStackExpanded()).isFalse();
         assertThat(mBubbleData.getBubbleInStackWithKey(KEY_APP_BUBBLE)).isNull();
 
-        mBubbleController.showOrHideAppBubble(mAppBubbleIntent);
+        mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0);
 
         verify(mBubbleController).inflateAndAdd(any(Bubble.class), /* suppressFlyout= */ eq(true),
                 /* showInShade= */ eq(false));
@@ -1660,13 +1663,13 @@
 
     @Test
     public void testShowOrHideAppBubble_expandIfCollapsed() {
-        mBubbleController.showOrHideAppBubble(mAppBubbleIntent);
+        mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0);
         mBubbleController.updateBubble(mBubbleEntry);
         mBubbleController.collapseStack();
         assertThat(mBubbleController.isStackExpanded()).isFalse();
 
         // Calling this while collapsed will expand the app bubble
-        mBubbleController.showOrHideAppBubble(mAppBubbleIntent);
+        mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0);
 
         assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(KEY_APP_BUBBLE);
         assertThat(mBubbleController.isStackExpanded()).isTrue();
@@ -1675,27 +1678,46 @@
 
     @Test
     public void testShowOrHideAppBubble_collapseIfSelected() {
-        mBubbleController.showOrHideAppBubble(mAppBubbleIntent);
+        mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0);
         assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(KEY_APP_BUBBLE);
         assertThat(mBubbleController.isStackExpanded()).isTrue();
 
         // Calling this while the app bubble is expanded should collapse the stack
-        mBubbleController.showOrHideAppBubble(mAppBubbleIntent);
+        mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0);
 
         assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(KEY_APP_BUBBLE);
         assertThat(mBubbleController.isStackExpanded()).isFalse();
         assertThat(mBubbleData.getBubbles().size()).isEqualTo(1);
+        assertThat(mBubbleData.getBubbles().get(0).getUser()).isEqualTo(mUser0);
+    }
+
+    @Test
+    public void testShowOrHideAppBubbleWithNonPrimaryUser_bubbleCollapsedWithExpectedUser() {
+        UserHandle user10 = createUserHande(/* userId = */ 10);
+        mBubbleController.showOrHideAppBubble(mAppBubbleIntent, user10);
+        assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(KEY_APP_BUBBLE);
+        assertThat(mBubbleController.isStackExpanded()).isTrue();
+        assertThat(mBubbleData.getBubbles().size()).isEqualTo(1);
+        assertThat(mBubbleData.getBubbles().get(0).getUser()).isEqualTo(user10);
+
+        // Calling this while the app bubble is expanded should collapse the stack
+        mBubbleController.showOrHideAppBubble(mAppBubbleIntent, user10);
+
+        assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(KEY_APP_BUBBLE);
+        assertThat(mBubbleController.isStackExpanded()).isFalse();
+        assertThat(mBubbleData.getBubbles().size()).isEqualTo(1);
+        assertThat(mBubbleData.getBubbles().get(0).getUser()).isEqualTo(user10);
     }
 
     @Test
     public void testShowOrHideAppBubble_selectIfNotSelected() {
-        mBubbleController.showOrHideAppBubble(mAppBubbleIntent);
+        mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0);
         mBubbleController.updateBubble(mBubbleEntry);
         mBubbleController.expandStackAndSelectBubble(mBubbleEntry);
         assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(mBubbleEntry.getKey());
         assertThat(mBubbleController.isStackExpanded()).isTrue();
 
-        mBubbleController.showOrHideAppBubble(mAppBubbleIntent);
+        mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0);
         assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(KEY_APP_BUBBLE);
         assertThat(mBubbleController.isStackExpanded()).isTrue();
         assertThat(mBubbleData.getBubbles().size()).isEqualTo(2);
@@ -1830,6 +1852,12 @@
         mBubbleController.onUserChanged(userId);
     }
 
+    private UserHandle createUserHande(int userId) {
+        UserHandle user = mock(UserHandle.class);
+        when(user.getIdentifier()).thenReturn(userId);
+        return user;
+    }
+
     /**
      * Asserts that the bubble stack is expanded and also validates the cached state is updated.
      */
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/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index 607439b..bd67889 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -23,6 +23,7 @@
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.input.InputDeviceIdentifier;
 import android.hardware.input.InputManager;
+import android.hardware.input.InputManagerGlobal;
 import android.hardware.input.VirtualKeyEvent;
 import android.hardware.input.VirtualMouseButtonEvent;
 import android.hardware.input.VirtualMouseRelativeEvent;
@@ -686,7 +687,7 @@
             mListener = new InputManager.InputDeviceListener() {
                 @Override
                 public void onInputDeviceAdded(int deviceId) {
-                    final InputDevice device = InputManager.getInstance().getInputDevice(
+                    final InputDevice device = InputManagerGlobal.getInstance().getInputDevice(
                             deviceId);
                     Objects.requireNonNull(device, "Newly added input device was null.");
                     if (!device.getName().equals(deviceName)) {
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index e9a7f20..d94f4f2 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -194,19 +194,19 @@
 
     private Bundle mBatteryChangedOptions = BroadcastOptions.makeBasic()
             .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
-            .setDeferUntilActive(true)
+            .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
             .toBundle();
     /** Used for both connected/disconnected, so match using key */
     private Bundle mPowerOptions = BroadcastOptions.makeBasic()
             .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
             .setDeliveryGroupMatchingKey("android", Intent.ACTION_POWER_CONNECTED)
-            .setDeferUntilActive(true)
+            .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
             .toBundle();
     /** Used for both low/okay, so match using key */
     private Bundle mBatteryOptions = BroadcastOptions.makeBasic()
             .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
             .setDeliveryGroupMatchingKey("android", Intent.ACTION_BATTERY_OKAY)
-            .setDeferUntilActive(true)
+            .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
             .toBundle();
 
     private MetricsLogger mMetricsLogger;
diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java
index 19e5cb1..a3dc21e 100644
--- a/services/core/java/com/android/server/DropBoxManagerService.java
+++ b/services/core/java/com/android/server/DropBoxManagerService.java
@@ -320,7 +320,7 @@
                     .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MERGED)
                     .setDeliveryGroupMatchingFilter(matchingFilter)
                     .setDeliveryGroupExtrasMerger(extrasMerger)
-                    .setDeferUntilActive(true)
+                    .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
                     .toBundle();
         }
 
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index c441859..409f054 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -35,7 +35,7 @@
 per-file NetIdManager.java = file:/services/core/java/com/android/server/net/OWNERS
 per-file PackageWatchdog.java, RescueParty.java = file:/services/core/java/com/android/server/rollback/OWNERS
 per-file PinnerService.java = file:/apct-tests/perftests/OWNERS
-per-file RescueParty.java = fdunlap@google.com, shuc@google.com
+per-file RescueParty.java = fdunlap@google.com, shuc@google.com, ancr@google.com, harshitmahajan@google.com
 per-file SystemClockTime.java = file:/services/core/java/com/android/server/timedetector/OWNERS
 per-file SystemTimeZone.java = file:/services/core/java/com/android/server/timezonedetector/OWNERS
 per-file TelephonyRegistry.java = file:/telephony/OWNERS
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index d1e0f16..3de65f9 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -79,6 +79,7 @@
     static final String PROP_ATTEMPTING_FACTORY_RESET = "sys.attempting_factory_reset";
     static final String PROP_ATTEMPTING_REBOOT = "sys.attempting_reboot";
     static final String PROP_MAX_RESCUE_LEVEL_ATTEMPTED = "sys.max_rescue_level_attempted";
+    static final String PROP_LAST_FACTORY_RESET_TIME_MS = "persist.sys.last_factory_reset";
     @VisibleForTesting
     static final int LEVEL_NONE = 0;
     @VisibleForTesting
@@ -105,10 +106,11 @@
     @VisibleForTesting
     static final String NAMESPACE_TO_PACKAGE_MAPPING_FLAG =
             "namespace_to_package_mapping";
+    @VisibleForTesting
+    static final long FACTORY_RESET_THROTTLE_DURATION_MS = TimeUnit.MINUTES.toMillis(10);
 
     private static final String NAME = "rescue-party-observer";
 
-
     private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue";
     private static final String PROP_VIRTUAL_DEVICE = "ro.hardware.virtual_device";
     private static final String PROP_DEVICE_CONFIG_DISABLE_FLAG =
@@ -327,8 +329,8 @@
         }
     }
 
-    private static int getMaxRescueLevel(boolean mayPerformFactoryReset) {
-        if (!mayPerformFactoryReset
+    private static int getMaxRescueLevel(boolean mayPerformReboot) {
+        if (!mayPerformReboot
                 || SystemProperties.getBoolean(PROP_DISABLE_FACTORY_RESET_FLAG, false)) {
             return LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS;
         }
@@ -339,11 +341,11 @@
      * Get the rescue level to perform if this is the n-th attempt at mitigating failure.
      *
      * @param mitigationCount: the mitigation attempt number (1 = first attempt etc.)
-     * @param mayPerformFactoryReset: whether or not a factory reset may be performed for the given
-     *                              failure.
+     * @param mayPerformReboot: whether or not a reboot and factory reset may be performed
+     *                          for the given failure.
      * @return the rescue level for the n-th mitigation attempt.
      */
-    private static int getRescueLevel(int mitigationCount, boolean mayPerformFactoryReset) {
+    private static int getRescueLevel(int mitigationCount, boolean mayPerformReboot) {
         if (mitigationCount == 1) {
             return LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS;
         } else if (mitigationCount == 2) {
@@ -351,9 +353,9 @@
         } else if (mitigationCount == 3) {
             return LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS;
         } else if (mitigationCount == 4) {
-            return Math.min(getMaxRescueLevel(mayPerformFactoryReset), LEVEL_WARM_REBOOT);
+            return Math.min(getMaxRescueLevel(mayPerformReboot), LEVEL_WARM_REBOOT);
         } else if (mitigationCount >= 5) {
-            return Math.min(getMaxRescueLevel(mayPerformFactoryReset), LEVEL_FACTORY_RESET);
+            return Math.min(getMaxRescueLevel(mayPerformReboot), LEVEL_FACTORY_RESET);
         } else {
             Slog.w(TAG, "Expected positive mitigation count, was " + mitigationCount);
             return LEVEL_NONE;
@@ -450,6 +452,8 @@
                     break;
                 }
                 SystemProperties.set(PROP_ATTEMPTING_FACTORY_RESET, "true");
+                long now = System.currentTimeMillis();
+                SystemProperties.set(PROP_LAST_FACTORY_RESET_TIME_MS, Long.toString(now));
                 runnable = new Runnable() {
                     @Override
                     public void run() {
@@ -627,7 +631,7 @@
             if (!isDisabled() && (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
                     || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING)) {
                 return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount,
-                        mayPerformFactoryReset(failedPackage)));
+                        mayPerformReboot(failedPackage)));
             } else {
                 return PackageHealthObserverImpact.USER_IMPACT_NONE;
             }
@@ -642,7 +646,7 @@
             if (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
                     || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING) {
                 final int level = getRescueLevel(mitigationCount,
-                        mayPerformFactoryReset(failedPackage));
+                        mayPerformReboot(failedPackage));
                 executeRescueLevel(mContext,
                         failedPackage == null ? null : failedPackage.getPackageName(), level);
                 return true;
@@ -683,8 +687,9 @@
             if (isDisabled()) {
                 return false;
             }
+            boolean mayPerformReboot = !shouldThrottleReboot();
             executeRescueLevel(mContext, /*failedPackage=*/ null,
-                    getRescueLevel(mitigationCount, true));
+                    getRescueLevel(mitigationCount, mayPerformReboot));
             return true;
         }
 
@@ -698,14 +703,27 @@
          * prompting a factory reset is an acceptable mitigation strategy for the package's
          * failure, {@code false} otherwise.
          */
-        private boolean mayPerformFactoryReset(@Nullable VersionedPackage failingPackage) {
+        private boolean mayPerformReboot(@Nullable VersionedPackage failingPackage) {
             if (failingPackage == null) {
                 return false;
             }
+            if (shouldThrottleReboot())  {
+                return false;
+            }
 
             return isPersistentSystemApp(failingPackage.getPackageName());
         }
 
+        /**
+         * Returns {@code true} if Rescue Party is allowed to attempt a reboot or factory reset.
+         * Will return {@code false} if a factory reset was already offered recently.
+         */
+        private boolean shouldThrottleReboot() {
+            Long lastResetTime = SystemProperties.getLong(PROP_LAST_FACTORY_RESET_TIME_MS, 0);
+            long now = System.currentTimeMillis();
+            return now < lastResetTime + FACTORY_RESET_THROTTLE_DURATION_MS;
+        }
+
         private boolean isPersistentSystemApp(@NonNull String packageName) {
             PackageManager pm = mContext.getPackageManager();
             try {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index c5008fa..4a0a228 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -252,12 +252,6 @@
 
     private static final boolean LOG_SERVICE_START_STOP = DEBUG_SERVICE;
 
-    // How long we wait for a service to finish executing.
-    static final int SERVICE_TIMEOUT = 20 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
-
-    // How long we wait for a service to finish executing.
-    static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10;
-
     // Foreground service types that always get immediate notification display,
     // expressed in the same bitmask format that ServiceRecord.foregroundServiceType
     // uses.
@@ -337,6 +331,13 @@
     final ArrayMap<ForegroundServiceDelegation, ServiceRecord> mFgsDelegations = new ArrayMap<>();
 
     /**
+     * A global counter for generating sequence numbers to uniquely identify bindService requests.
+     * It is purely for logging purposes.
+     */
+    @GuardedBy("mAm")
+    private long mBindServiceSeqCounter = 0;
+
+    /**
      * Whether there is a rate limit that suppresses immediate re-deferral of new FGS
      * notifications from each app.  On by default, disabled only by shell command for
      * test-suite purposes.  To disable the behavior more generally, use the usual
@@ -4429,8 +4430,12 @@
             try {
                 bumpServiceExecutingLocked(r, execInFg, "bind",
                         OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE);
+                if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+                    Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, "requestServiceBinding="
+                            + i.intent.getIntent() + ". bindSeq=" + mBindServiceSeqCounter);
+                }
                 r.app.getThread().scheduleBindService(r, i.intent.getIntent(), rebind,
-                        r.app.mState.getReportedProcState());
+                        r.app.mState.getReportedProcState(), mBindServiceSeqCounter++);
                 if (!rebind) {
                     i.requested = true;
                 }
@@ -6598,13 +6603,15 @@
                     return;
                 }
                 final ProcessServiceRecord psr = proc.mServices;
-                if (psr.numberOfExecutingServices() == 0 || proc.getThread() == null) {
+                if (psr.numberOfExecutingServices() == 0 || proc.getThread() == null
+                        || proc.isKilled()) {
                     return;
                 }
                 final long now = SystemClock.uptimeMillis();
                 final long maxTime =  now
                         - (psr.shouldExecServicesFg()
-                        ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
+                        ? mAm.mConstants.SERVICE_TIMEOUT
+                        : mAm.mConstants.SERVICE_BACKGROUND_TIMEOUT);
                 ServiceRecord timeout = null;
                 long nextTime = 0;
                 for (int i = psr.numberOfExecutingServices() - 1; i >= 0; i--) {
@@ -6635,8 +6642,8 @@
                             ActivityManagerService.SERVICE_TIMEOUT_MSG);
                     msg.obj = proc;
                     mAm.mHandler.sendMessageAtTime(msg, psr.shouldExecServicesFg()
-                            ? (nextTime + SERVICE_TIMEOUT) :
-                            (nextTime + SERVICE_BACKGROUND_TIMEOUT));
+                            ? (nextTime + mAm.mConstants.SERVICE_TIMEOUT) :
+                            (nextTime + mAm.mConstants.SERVICE_BACKGROUND_TIMEOUT));
                 }
             }
 
@@ -6732,7 +6739,7 @@
                 ActivityManagerService.SERVICE_TIMEOUT_MSG);
         msg.obj = proc;
         mAm.mHandler.sendMessageDelayed(msg, proc.mServices.shouldExecServicesFg()
-                ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
+                ? mAm.mConstants.SERVICE_TIMEOUT : mAm.mConstants.SERVICE_BACKGROUND_TIMEOUT);
     }
 
     void scheduleServiceForegroundTransitionTimeoutLocked(ServiceRecord r) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 5696004..4fa28a1 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -248,7 +248,16 @@
 
     private static final long DEFAULT_SERVICE_BIND_ALMOST_PERCEPTIBLE_TIMEOUT_MS = 15 * 1000;
 
-    // Flag stored in the DeviceConfig API.
+    /**
+     * Default value to {@link #SERVICE_TIMEOUT}.
+     */
+    private static final long DEFAULT_SERVICE_TIMEOUT = 20 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
+
+    /**
+     * Default value to {@link #SERVICE_BACKGROUND_TIMEOUT}.
+     */
+    private static final long DEFAULT_SERVICE_BACKGROUND_TIMEOUT = DEFAULT_SERVICE_TIMEOUT * 10;
+
     /**
      * Maximum number of cached processes.
      */
@@ -506,6 +515,12 @@
     // to restart less than this amount of time from the last one.
     public long SERVICE_MIN_RESTART_TIME_BETWEEN = DEFAULT_SERVICE_MIN_RESTART_TIME_BETWEEN;
 
+    // How long we wait for a service to finish executing.
+    long SERVICE_TIMEOUT = DEFAULT_SERVICE_TIMEOUT;
+
+    // How long we wait for a service to finish executing.
+    long SERVICE_BACKGROUND_TIMEOUT = DEFAULT_SERVICE_BACKGROUND_TIMEOUT;
+
     // Maximum amount of time for there to be no activity on a service before
     // we consider it non-essential and allow its process to go on the
     // LRU background list.
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 2a1e860..7550196 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -14507,18 +14507,6 @@
             }
         }
 
-        // resultTo broadcasts are always infinitely deferrable.
-        if ((resultTo != null) && !ordered && mEnableModernQueue) {
-            if (brOptions == null) {
-                brOptions = BroadcastOptions.makeBasic();
-            }
-            brOptions.setDeferUntilActive(true);
-        }
-
-        if (mEnableModernQueue && ordered && brOptions != null && brOptions.isDeferUntilActive()) {
-            throw new IllegalArgumentException("Ordered broadcasts can't be deferred until active");
-        }
-
         // Verify that protected broadcasts are only being sent by system code,
         // and that system code is only sending protected broadcasts.
         final boolean isProtectedBroadcast;
diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java
index 53fcddf..33d4004 100644
--- a/services/core/java/com/android/server/am/BroadcastConstants.java
+++ b/services/core/java/com/android/server/am/BroadcastConstants.java
@@ -236,6 +236,14 @@
     private static final int DEFAULT_MAX_HISTORY_SUMMARY_SIZE =
             ActivityManager.isLowRamDeviceStatic() ? 256 : 1024;
 
+    /**
+     * For {@link BroadcastRecord}: Default to treating all broadcasts sent by
+     * the system as be {@link BroadcastOptions#DEFERRAL_POLICY_UNTIL_ACTIVE}.
+     */
+    public boolean CORE_DEFER_UNTIL_ACTIVE = DEFAULT_CORE_DEFER_UNTIL_ACTIVE;
+    private static final String KEY_CORE_DEFER_UNTIL_ACTIVE = "bcast_core_defer_until_active";
+    private static final boolean DEFAULT_CORE_DEFER_UNTIL_ACTIVE = false;
+
     // Settings override tracking for this instance
     private String mSettingsKey;
     private SettingsObserver mSettingsObserver;
@@ -373,7 +381,12 @@
                     DEFAULT_MAX_HISTORY_COMPLETE_SIZE);
             MAX_HISTORY_SUMMARY_SIZE = getDeviceConfigInt(KEY_MAX_HISTORY_SUMMARY_SIZE,
                     DEFAULT_MAX_HISTORY_SUMMARY_SIZE);
+            CORE_DEFER_UNTIL_ACTIVE = getDeviceConfigBoolean(KEY_CORE_DEFER_UNTIL_ACTIVE,
+                    DEFAULT_CORE_DEFER_UNTIL_ACTIVE);
         }
+
+        // TODO: migrate BroadcastRecord to accept a BroadcastConstants
+        BroadcastRecord.CORE_DEFER_UNTIL_ACTIVE = CORE_DEFER_UNTIL_ACTIVE;
     }
 
     /**
@@ -418,6 +431,8 @@
                     MAX_CONSECUTIVE_URGENT_DISPATCHES).println();
             pw.print(KEY_MAX_CONSECUTIVE_NORMAL_DISPATCHES,
                     MAX_CONSECUTIVE_NORMAL_DISPATCHES).println();
+            pw.print(KEY_CORE_DEFER_UNTIL_ACTIVE,
+                    CORE_DEFER_UNTIL_ACTIVE).println();
             pw.decreaseIndent();
             pw.println();
         }
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 59f33dd..6bd3c79 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -90,6 +90,7 @@
     final boolean prioritized; // contains more than one priority tranche
     final boolean deferUntilActive; // infinitely deferrable broadcast
     final boolean shareIdentity;  // whether the broadcaster's identity should be shared
+    final boolean urgent;    // has been classified as "urgent"
     final int userId;       // user id this broadcast was for
     final @Nullable String resolvedType; // the resolved data type
     final @Nullable String[] requiredPermissions; // permissions the caller has required
@@ -146,6 +147,13 @@
     private @Nullable String mCachedToString;
     private @Nullable String mCachedToShortString;
 
+    /**
+     * When enabled, assume that {@link UserHandle#isCore(int)} apps should
+     * treat {@link BroadcastOptions#DEFERRAL_POLICY_DEFAULT} as
+     * {@link BroadcastOptions#DEFERRAL_POLICY_UNTIL_ACTIVE}.
+     */
+    static boolean CORE_DEFER_UNTIL_ACTIVE = false;
+
     /** Empty immutable list of receivers */
     static final List<Object> EMPTY_RECEIVERS = List.of();
 
@@ -400,7 +408,9 @@
         receivers = (_receivers != null) ? _receivers : EMPTY_RECEIVERS;
         delivery = new int[_receivers != null ? _receivers.size() : 0];
         deliveryReasons = new String[delivery.length];
-        deferUntilActive = options != null ? options.isDeferUntilActive() : false;
+        urgent = calculateUrgent(_intent, _options);
+        deferUntilActive = calculateDeferUntilActive(_callingUid,
+                _options, _resultTo, _serialized, urgent);
         deferredUntilActive = new boolean[deferUntilActive ? delivery.length : 0];
         blockedUntilTerminalCount = calculateBlockedUntilTerminalCount(receivers, _serialized);
         scheduledTime = new long[delivery.length];
@@ -488,6 +498,7 @@
         pushMessageOverQuota = from.pushMessageOverQuota;
         interactive = from.interactive;
         shareIdentity = from.shareIdentity;
+        urgent = from.urgent;
         filterExtrasForReceiver = from.filterExtrasForReceiver;
     }
 
@@ -681,15 +692,8 @@
         return deferUntilActive;
     }
 
-    /**
-     * Core policy determination about this broadcast's delivery prioritization
-     */
     boolean isUrgent() {
-        // TODO: flags for controlling policy
-        // TODO: migrate alarm-prioritization flag to BroadcastConstants
-        return (isForeground()
-                || interactive
-                || alarm);
+        return urgent;
     }
 
     @NonNull String getHostingRecordTriggerType() {
@@ -849,6 +853,69 @@
         }
     }
 
+    /**
+     * Core policy determination about this broadcast's delivery prioritization
+     */
+    @VisibleForTesting
+    static boolean calculateUrgent(@NonNull Intent intent, @Nullable BroadcastOptions options) {
+        // TODO: flags for controlling policy
+        // TODO: migrate alarm-prioritization flag to BroadcastConstants
+        if ((intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) != 0) {
+            return true;
+        }
+        if (options != null) {
+            if (options.isInteractive()) {
+                return true;
+            }
+            if (options.isAlarmBroadcast()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Resolve the requested {@link BroadcastOptions#setDeferralPolicy(int)}
+     * against this broadcast state to determine if it should be marked as
+     * "defer until active".
+     */
+    @VisibleForTesting
+    static boolean calculateDeferUntilActive(int callingUid, @Nullable BroadcastOptions options,
+            @Nullable IIntentReceiver resultTo, boolean ordered, boolean urgent) {
+        // Ordered broadcasts can never be deferred until active
+        if (ordered) {
+            return false;
+        }
+
+        // Unordered resultTo broadcasts are always deferred until active
+        if (!ordered && resultTo != null) {
+            return true;
+        }
+
+        // Determine if a strong preference in either direction was expressed;
+        // a preference here overrides all remaining policies
+        if (options != null) {
+            switch (options.getDeferralPolicy()) {
+                case BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE:
+                    return true;
+                case BroadcastOptions.DEFERRAL_POLICY_NONE:
+                    return false;
+            }
+        }
+
+        // Urgent broadcasts aren't deferred until active
+        if (urgent) {
+            return false;
+        }
+
+        // Otherwise, choose a reasonable default
+        if (CORE_DEFER_UNTIL_ACTIVE && UserHandle.isCore(callingUid)) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
     public BroadcastRecord maybeStripForHistory() {
         if (!intent.canStripForHistory()) {
             return this;
diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java
index ddc9e91..844f175 100644
--- a/services/core/java/com/android/server/am/CoreSettingsObserver.java
+++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java
@@ -25,6 +25,7 @@
 import android.os.Bundle;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
+import android.text.TextFlags;
 import android.widget.WidgetFlags;
 
 import com.android.internal.R;
@@ -162,6 +163,11 @@
                 DeviceConfig.NAMESPACE_WIDGET, WidgetFlags.MAGNIFIER_ASPECT_RATIO,
                 WidgetFlags.KEY_MAGNIFIER_ASPECT_RATIO, float.class,
                 WidgetFlags.MAGNIFIER_ASPECT_RATIO_DEFAULT));
+
+        sDeviceConfigEntries.add(new DeviceConfigEntry<Boolean>(
+                TextFlags.NAMESPACE, TextFlags.ENABLE_NEW_CONTEXT_MENU,
+                TextFlags.KEY_ENABLE_NEW_CONTEXT_MENU, boolean.class,
+                TextFlags.ENABLE_NEW_CONTEXT_MENU_DEFAULT));
         // add other device configs here...
     }
     private static volatile boolean sDeviceConfigContextEntriesLoaded = false;
diff --git a/services/core/java/com/android/server/am/ProcessStatsService.java b/services/core/java/com/android/server/am/ProcessStatsService.java
index bac9253..8e93c1b 100644
--- a/services/core/java/com/android/server/am/ProcessStatsService.java
+++ b/services/core/java/com/android/server/am/ProcessStatsService.java
@@ -69,7 +69,7 @@
     // define the encoding of that data in an integer.
 
     static final int MAX_HISTORIC_STATES = 8;   // Maximum number of historic states we will keep.
-    static final String STATE_FILE_PREFIX = "state-"; // Prefix to use for state filenames.
+    static final String STATE_FILE_PREFIX = "state-v2-"; // Prefix to use for state filenames.
     static final String STATE_FILE_SUFFIX = ".bin"; // Suffix to use for state filenames.
     static final String STATE_FILE_CHECKIN_SUFFIX = ".ci"; // State files that have checked in.
     static long WRITE_PERIOD = 30*60*1000;      // Write file every 30 minutes or so.
@@ -462,6 +462,10 @@
             File file = files[i];
             String fileStr = file.getPath();
             if (DEBUG) Slog.d(TAG, "Collecting: " + fileStr);
+            if (!file.getName().startsWith(STATE_FILE_PREFIX)) {
+                if (DEBUG) Slog.d(TAG, "Skipping: mismatching prefix");
+                continue;
+            }
             if (!inclCheckedIn && fileStr.endsWith(STATE_FILE_CHECKIN_SUFFIX)) {
                 if (DEBUG) Slog.d(TAG, "Skipping: already checked in");
                 continue;
@@ -478,6 +482,14 @@
 
     @GuardedBy("mFileLock")
     private void trimHistoricStatesWriteLF() {
+        File[] files = mBaseDir.listFiles();
+        if (files != null) {
+            for (int i = 0; i < files.length; i++) {
+                if (!files[i].getName().startsWith(STATE_FILE_PREFIX)) {
+                    files[i].delete();
+                }
+            }
+        }
         ArrayList<String> filesArray = getCommittedFilesLF(MAX_HISTORIC_STATES, false, true);
         if (filesArray == null) {
             return;
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 490a33e..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");
@@ -8170,7 +8177,7 @@
             volumeChangedOptions.setDeliveryGroupPolicy(DELIVERY_GROUP_POLICY_MOST_RECENT);
             volumeChangedOptions.setDeliveryGroupMatchingKey(
                     AudioManager.VOLUME_CHANGED_ACTION, String.valueOf(mStreamType));
-            volumeChangedOptions.setDeferUntilActive(true);
+            volumeChangedOptions.setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE);
             mVolumeChangedOptions = volumeChangedOptions.toBundle();
 
             mStreamDevicesChanged = new Intent(AudioManager.STREAM_DEVICES_CHANGED_ACTION);
@@ -8179,7 +8186,8 @@
             streamDevicesChangedOptions.setDeliveryGroupPolicy(DELIVERY_GROUP_POLICY_MOST_RECENT);
             streamDevicesChangedOptions.setDeliveryGroupMatchingKey(
                     AudioManager.STREAM_DEVICES_CHANGED_ACTION, String.valueOf(mStreamType));
-            streamDevicesChangedOptions.setDeferUntilActive(true);
+            streamDevicesChangedOptions.setDeferralPolicy(
+                    BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE);
             mStreamDevicesChangedOptions = streamDevicesChangedOptions.toBundle();
         }
 
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/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index 3dac04c..c248367 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -41,6 +41,7 @@
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.MathUtils;
+import android.util.SparseIntArray;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
@@ -52,7 +53,6 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.atomic.AtomicReference;
@@ -112,6 +112,8 @@
 
     private static final long GLOBAL_TIME_OFFSET_UNINITIALIZED = -1;
 
+    private static final int SAFE_MEDIA_VOLUME_UNINITIALIZED = -1;
+
     private final EventLogger mLogger = new EventLogger(AudioService.LOG_NB_EVENTS_SOUND_DOSE,
             "CSD updates");
 
@@ -132,15 +134,6 @@
     // For now using the same value for CSD supported devices
     private float mSafeMediaVolumeDbfs;
 
-    private static class SafeDeviceVolumeInfo {
-        int mDeviceType;
-        int mSafeVolumeIndex = -1;
-
-        SafeDeviceVolumeInfo(int deviceType) {
-            mDeviceType = deviceType;
-        }
-    }
-
     /**
      * mSafeMediaVolumeDevices lists the devices for which safe media volume is enforced.
      * Contains a safe volume index for a given device type.
@@ -152,25 +145,7 @@
      * This level corresponds to a loudness of 85 dB SPL for the warning to be displayed when
      * the headset is compliant to EN 60950 with a max loudness of 100dB SPL.
      */
-    private final HashMap<Integer, SafeDeviceVolumeInfo> mSafeMediaVolumeDevices =
-            new HashMap<>() {{
-                put(AudioSystem.DEVICE_OUT_WIRED_HEADSET,
-                        new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_WIRED_HEADSET));
-                put(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE,
-                        new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE));
-                put(AudioSystem.DEVICE_OUT_USB_HEADSET,
-                        new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_USB_HEADSET));
-                put(AudioSystem.DEVICE_OUT_BLE_HEADSET,
-                        new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_BLE_HEADSET));
-                put(AudioSystem.DEVICE_OUT_BLE_BROADCAST,
-                        new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_BLE_BROADCAST));
-                put(AudioSystem.DEVICE_OUT_HEARING_AID,
-                        new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_HEARING_AID));
-                put(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES,
-                        new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES));
-                put(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
-                        new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP));
-            }};
+    private final SparseIntArray mSafeMediaVolumeDevices = new SparseIntArray();
 
     // mMusicActiveMs is the cumulative time of music activity since safe volume was disabled.
     // When this time reaches UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX, the safe media volume is re-enabled
@@ -291,6 +266,7 @@
 
         mEnableCsd = mContext.getResources().getBoolean(R.bool.config_audio_csd_enabled_default);
         initCsd();
+        initSafeVolumes();
 
         mSafeMediaVolumeState = mSettings.getGlobalInt(audioService.getContentResolver(),
                 Settings.Global.AUDIO_SAFE_VOLUME_STATE, SAFE_MEDIA_VOLUME_NOT_CONFIGURED);
@@ -305,6 +281,25 @@
                 Context.ALARM_SERVICE);
     }
 
+    void initSafeVolumes() {
+        mSafeMediaVolumeDevices.append(AudioSystem.DEVICE_OUT_WIRED_HEADSET,
+                SAFE_MEDIA_VOLUME_UNINITIALIZED);
+        mSafeMediaVolumeDevices.append(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE,
+                SAFE_MEDIA_VOLUME_UNINITIALIZED);
+        mSafeMediaVolumeDevices.append(AudioSystem.DEVICE_OUT_USB_HEADSET,
+                SAFE_MEDIA_VOLUME_UNINITIALIZED);
+        mSafeMediaVolumeDevices.append(AudioSystem.DEVICE_OUT_BLE_HEADSET,
+                SAFE_MEDIA_VOLUME_UNINITIALIZED);
+        mSafeMediaVolumeDevices.append(AudioSystem.DEVICE_OUT_BLE_BROADCAST,
+                SAFE_MEDIA_VOLUME_UNINITIALIZED);
+        mSafeMediaVolumeDevices.append(AudioSystem.DEVICE_OUT_HEARING_AID,
+                SAFE_MEDIA_VOLUME_UNINITIALIZED);
+        mSafeMediaVolumeDevices.append(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES,
+                SAFE_MEDIA_VOLUME_UNINITIALIZED);
+        mSafeMediaVolumeDevices.append(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+                SAFE_MEDIA_VOLUME_UNINITIALIZED);
+    }
+
     float getRs2Value() {
         if (!mEnableCsd) {
             return 0.f;
@@ -435,12 +430,12 @@
     }
 
     /*package*/ int safeMediaVolumeIndex(int device) {
-        final SafeDeviceVolumeInfo vi = mSafeMediaVolumeDevices.get(device);
-        if (vi == null) {
+        final int vol = mSafeMediaVolumeDevices.get(device);
+        if (vol == SAFE_MEDIA_VOLUME_UNINITIALIZED) {
             return MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];
         }
 
-        return vi.mSafeVolumeIndex;
+        return vol;
     }
 
     /*package*/ void restoreMusicActiveMs() {
@@ -465,14 +460,15 @@
         AudioService.VolumeStreamState streamState = mAudioService.getVssVolumeForStream(
                 AudioSystem.STREAM_MUSIC);
 
-        for (SafeDeviceVolumeInfo vi : mSafeMediaVolumeDevices.values()) {
-            int index = streamState.getIndex(vi.mDeviceType);
-            int safeIndex = safeMediaVolumeIndex(vi.mDeviceType);
+        for (int i = 0; i < mSafeMediaVolumeDevices.size(); ++i)  {
+            int deviceType = mSafeMediaVolumeDevices.keyAt(i);
+            int index = streamState.getIndex(deviceType);
+            int safeIndex = safeMediaVolumeIndex(deviceType);
             if (index > safeIndex) {
-                streamState.setIndex(safeIndex, vi.mDeviceType, caller,
+                streamState.setIndex(safeIndex, deviceType, caller,
                         true /*hasModifyAudioSettings*/);
                 mAudioHandler.sendMessageAtTime(
-                        mAudioHandler.obtainMessage(MSG_SET_DEVICE_VOLUME, vi.mDeviceType,
+                        mAudioHandler.obtainMessage(MSG_SET_DEVICE_VOLUME, deviceType,
                                 /*arg2=*/0, streamState), /*delay=*/0);
             }
         }
@@ -494,7 +490,7 @@
     private boolean checkSafeMediaVolume_l(int streamType, int index, int device) {
         return (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE)
                     && (AudioService.mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC)
-                    && (mSafeMediaVolumeDevices.containsKey(device))
+                    && safeDevicesContains(device)
                     && (index > safeMediaVolumeIndex(device));
     }
 
@@ -546,7 +542,7 @@
         synchronized (mSafeMediaVolumeStateLock) {
             if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_INACTIVE) {
                 int device = mAudioService.getDeviceForStream(AudioSystem.STREAM_MUSIC);
-                if (mSafeMediaVolumeDevices.containsKey(device) && isStreamActive) {
+                if (safeDevicesContains(device) && isStreamActive) {
                     scheduleMusicActiveCheck();
                     int index = mAudioService.getVssVolumeForDevice(AudioSystem.STREAM_MUSIC,
                             device);
@@ -589,14 +585,15 @@
     }
 
     /*package*/ void initSafeMediaVolumeIndex() {
-        for (SafeDeviceVolumeInfo vi : mSafeMediaVolumeDevices.values()) {
-            vi.mSafeVolumeIndex = getSafeDeviceMediaVolumeIndex(vi.mDeviceType);
+        for (int i = 0; i < mSafeMediaVolumeDevices.size(); ++i)  {
+            int deviceType = mSafeMediaVolumeDevices.keyAt(i);
+            mSafeMediaVolumeDevices.put(deviceType, getSafeDeviceMediaVolumeIndex(deviceType));
         }
     }
 
     /*package*/ int getSafeMediaVolumeIndex(int device) {
         if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE
-                && mSafeMediaVolumeDevices.containsKey(device)) {
+                && safeDevicesContains(device)) {
             return safeMediaVolumeIndex(device);
         } else {
             return -1;
@@ -614,7 +611,7 @@
     }
 
     /*package*/ boolean safeDevicesContains(int device) {
-        return mSafeMediaVolumeDevices.containsKey(device);
+        return mSafeMediaVolumeDevices.indexOfKey(device) >= 0;
     }
 
     /*package*/ void invalidatPendingVolumeCommand() {
@@ -665,9 +662,9 @@
         pw.print("  mSafeMediaVolumeState=");
         pw.println(safeMediaVolumeStateToString(mSafeMediaVolumeState));
         pw.print("  mSafeMediaVolumeIndex="); pw.println(mSafeMediaVolumeIndex);
-        for (SafeDeviceVolumeInfo vi : mSafeMediaVolumeDevices.values()) {
-            pw.print("  mSafeMediaVolumeIndex["); pw.print(vi.mDeviceType);
-            pw.print("]="); pw.println(vi.mSafeVolumeIndex);
+        for (int i = 0; i < mSafeMediaVolumeDevices.size(); ++i)  {
+            pw.print("  mSafeMediaVolumeIndex["); pw.print(mSafeMediaVolumeDevices.keyAt(i));
+            pw.print("]="); pw.println(mSafeMediaVolumeDevices.valueAt(i));
         }
         pw.print("  mSafeMediaVolumeDbfs="); pw.println(mSafeMediaVolumeDbfs);
         pw.print("  mMusicActiveMs="); pw.println(mMusicActiveMs);
@@ -721,7 +718,7 @@
             }
 
             if (AudioService.mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC
-                    && mSafeMediaVolumeDevices.containsKey(device)) {
+                    && safeDevicesContains(device)) {
                 soundDose.updateAttenuation(
                         AudioSystem.getStreamVolumeDB(AudioSystem.STREAM_MUSIC,
                                 (newIndex + 5) / 10,
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/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/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index db6944d0..3864200 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -2088,7 +2088,7 @@
         }
 
         @VisibleForTesting
-        void onRefreshRateSettingChangedLocked(float min, float max) {
+        public void onRefreshRateSettingChangedLocked(float min, float max) {
             boolean changeable = (max - min > 1f && max > 60f);
             if (mRefreshRateChangeable != changeable) {
                 mRefreshRateChangeable = changeable;
diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java
index 3e2efdd..20ff51c 100644
--- a/services/core/java/com/android/server/dreams/DreamController.java
+++ b/services/core/java/com/android/server/dreams/DreamController.java
@@ -120,7 +120,7 @@
         options.setDeliveryGroupMatchingKey(
                 DREAMING_DELIVERY_GROUP_NAMESPACE, DREAMING_DELIVERY_GROUP_KEY);
         // This allows the broadcast delivery to be delayed to apps in the Cached state.
-        options.setDeferUntilActive(true);
+        options.setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE);
         return options.toBundle();
     }
 
diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java
index 4d03e44..7e990c6 100644
--- a/services/core/java/com/android/server/input/InputManagerInternal.java
+++ b/services/core/java/com/android/server/input/InputManagerInternal.java
@@ -96,7 +96,11 @@
      */
     public abstract int getVirtualMousePointerDisplayId();
 
-    /** Gets the current position of the mouse cursor. */
+    /**
+     * Gets the current position of the mouse cursor.
+     *
+     * Returns NaN-s as the coordinates if the cursor is not available.
+     */
     public abstract PointF getCursorPosition();
 
     /**
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index b2b22a0..efc4f11 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -2863,9 +2863,6 @@
 
         int getPointerDisplayId();
 
-        /** Gets the x and y coordinates of the cursor's current position. */
-        PointF getCursorPosition();
-
         /**
          * Notifies window manager that a {@link android.view.MotionEvent#ACTION_DOWN} pointer event
          * occurred on a window that did not have focus.
@@ -3189,7 +3186,11 @@
 
         @Override
         public PointF getCursorPosition() {
-            return mWindowManagerCallbacks.getCursorPosition();
+            final float[] p = mNative.getMouseCursorPosition();
+            if (p == null || p.length != 2) {
+                throw new IllegalStateException("Failed to get mouse cursor position");
+            }
+            return new PointF(p[0], p[1]);
         }
 
         @Override
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/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 22226e8..5395302d 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -224,6 +224,16 @@
     /** Set whether stylus button reporting through motion events should be enabled. */
     void setStylusButtonMotionEventsEnabled(boolean enabled);
 
+    /**
+     * Get the current position of the mouse cursor.
+     *
+     * If the mouse cursor is not currently shown, the coordinate values will be NaN-s.
+     *
+     * NOTE: This will grab the PointerController's lock, so we must be careful about calling this
+     * from the InputReader or Display threads, which may result in a deadlock.
+     */
+    float[] getMouseCursorPosition();
+
     /** The native implementation of InputManagerService methods. */
     class NativeImpl implements NativeInputManagerService {
         /** Pointer to native input manager service object, used by native code. */
@@ -465,5 +475,8 @@
 
         @Override
         public native void setStylusButtonMotionEventsEnabled(boolean enabled);
+
+        @Override
+        public native float[] getMouseCursorPosition();
     }
 }
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 91f91f8..b336b95 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -1488,16 +1488,19 @@
                         }
 
                         int change = isPackageDisappearing(imi.getPackageName());
-                        if (isPackageModified(imi.getPackageName())) {
-                            mAdditionalSubtypeMap.remove(imi.getId());
-                            AdditionalSubtypeUtils.save(mAdditionalSubtypeMap, mMethodMap,
-                                    mSettings.getCurrentUserId());
-                        }
                         if (change == PACKAGE_TEMPORARY_CHANGE
                                 || change == PACKAGE_PERMANENT_CHANGE) {
                             Slog.i(TAG, "Input method uninstalled, disabling: "
                                     + imi.getComponent());
                             setInputMethodEnabledLocked(imi.getId(), false);
+                        } else if (change == PACKAGE_UPDATING) {
+                            Slog.i(TAG,
+                                    "Input method reinstalling, clearing additional subtypes: "
+                                            + imi.getComponent());
+                            mAdditionalSubtypeMap.remove(imi.getId());
+                            AdditionalSubtypeUtils.save(mAdditionalSubtypeMap,
+                                    mMethodMap,
+                                    mSettings.getCurrentUserId());
                         }
                     }
                 }
@@ -2336,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/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index 3329f54..030c96e 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -261,11 +261,15 @@
         }
     }
 
-    private Condition[] removeDuplicateConditions(String pkg, Condition[] conditions) {
+    private Condition[] getValidConditions(String pkg, Condition[] conditions) {
         if (conditions == null || conditions.length == 0) return null;
         final int N = conditions.length;
         final ArrayMap<Uri, Condition> valid = new ArrayMap<Uri, Condition>(N);
         for (int i = 0; i < N; i++) {
+            if (conditions[i] == null) {
+                Slog.w(TAG, "Ignoring null condition from " + pkg);
+                continue;
+            }
             final Uri id = conditions[i].id;
             if (valid.containsKey(id)) {
                 Slog.w(TAG, "Ignoring condition from " + pkg + " for duplicate id: " + id);
@@ -303,7 +307,7 @@
         synchronized(mMutex) {
             if (DEBUG) Slog.d(TAG, "notifyConditions pkg=" + pkg + " info=" + info + " conditions="
                     + (conditions == null ? null : Arrays.asList(conditions)));
-            conditions = removeDuplicateConditions(pkg, conditions);
+            conditions = getValidConditions(pkg, conditions);
             if (conditions == null || conditions.length == 0) return;
             final int N = conditions.length;
             for (int i = 0; i < N; i++) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index c2e8df1..46337a9 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -6864,7 +6864,8 @@
      * A notification should be dismissible, unless it's exempted for some reason.
      */
     private boolean canBeNonDismissible(ApplicationInfo ai, Notification notification) {
-        return notification.isMediaNotification() || isEnterpriseExempted(ai);
+        return notification.isMediaNotification() || isEnterpriseExempted(ai)
+                || isCallNotification(ai.packageName, ai.uid, notification);
     }
 
     private boolean isEnterpriseExempted(ApplicationInfo ai) {
diff --git a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
index 5e0a180..8417049 100644
--- a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
+++ b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
@@ -62,7 +62,7 @@
 public class ValidateNotificationPeople implements NotificationSignalExtractor {
     // Using a shorter log tag since setprop has a limit of 32chars on variable name.
     private static final String TAG = "ValidateNoPeople";
-    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);;
+    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     private static final boolean ENABLE_PEOPLE_VALIDATOR = true;
@@ -105,12 +105,13 @@
     private int mEvictionCount;
     private NotificationUsageStats mUsageStats;
 
+    @Override
     public void initialize(Context context, NotificationUsageStats usageStats) {
         if (DEBUG) Slog.d(TAG, "Initializing  " + getClass().getSimpleName() + ".");
         mUserToContextMap = new ArrayMap<>();
         mBaseContext = context;
         mUsageStats = usageStats;
-        mPeopleCache = new LruCache<String, LookupResult>(PEOPLE_CACHE_SIZE);
+        mPeopleCache = new LruCache<>(PEOPLE_CACHE_SIZE);
         mEnabled = ENABLE_PEOPLE_VALIDATOR && 1 == Settings.Global.getInt(
                 mBaseContext.getContentResolver(), SETTING_ENABLE_PEOPLE_VALIDATOR, 1);
         if (mEnabled) {
@@ -134,7 +135,7 @@
     // For tests: just do the setting of various local variables without actually doing work
     @VisibleForTesting
     protected void initForTests(Context context, NotificationUsageStats usageStats,
-            LruCache peopleCache) {
+            LruCache<String, LookupResult> peopleCache) {
         mUserToContextMap = new ArrayMap<>();
         mBaseContext = context;
         mUsageStats = usageStats;
@@ -142,6 +143,7 @@
         mEnabled = true;
     }
 
+    @Override
     public RankingReconsideration process(NotificationRecord record) {
         if (!mEnabled) {
             if (VERBOSE) Slog.i(TAG, "disabled");
@@ -272,7 +274,7 @@
         }
 
         if (VERBOSE) Slog.i(TAG, "Validating: " + key + " for " + context.getUserId());
-        final LinkedList<String> pendingLookups = new LinkedList<String>();
+        final LinkedList<String> pendingLookups = new LinkedList<>();
         int personIdx = 0;
         for (String handle : people) {
             if (TextUtils.isEmpty(handle)) continue;
@@ -320,7 +322,6 @@
         return Integer.toString(userId) + ":" + handle;
     }
 
-    // VisibleForTesting
     public static String[] getExtraPeople(Bundle extras) {
         String[] peopleList = getExtraPeopleForKey(extras, Notification.EXTRA_PEOPLE_LIST);
         String[] legacyPeople = getExtraPeopleForKey(extras, Notification.EXTRA_PEOPLE);
@@ -417,101 +418,6 @@
         return null;
     }
 
-    private LookupResult resolvePhoneContact(Context context, final String number) {
-        Uri phoneUri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
-                Uri.encode(number));
-        return searchContacts(context, phoneUri);
-    }
-
-    private LookupResult resolveEmailContact(Context context, final String email) {
-        Uri numberUri = Uri.withAppendedPath(
-                ContactsContract.CommonDataKinds.Email.CONTENT_LOOKUP_URI,
-                Uri.encode(email));
-        return searchContacts(context, numberUri);
-    }
-
-    @VisibleForTesting
-    LookupResult searchContacts(Context context, Uri lookupUri) {
-        LookupResult lookupResult = new LookupResult();
-        final Uri corpLookupUri =
-                ContactsContract.Contacts.createCorpLookupUriFromEnterpriseLookupUri(lookupUri);
-        if (corpLookupUri == null) {
-            addContacts(lookupResult, context, lookupUri);
-        } else {
-            addWorkContacts(lookupResult, context, corpLookupUri);
-        }
-        return lookupResult;
-    }
-
-    @VisibleForTesting
-    // Performs a contacts search using searchContacts, and then follows up by looking up
-    // any phone numbers associated with the resulting contact information and merge those
-    // into the lookup result as well. Will have no additional effect if the contact does
-    // not have any phone numbers.
-    LookupResult searchContactsAndLookupNumbers(Context context, Uri lookupUri) {
-        LookupResult lookupResult = searchContacts(context, lookupUri);
-        String phoneLookupKey = lookupResult.getPhoneLookupKey();
-        if (phoneLookupKey != null) {
-            String selection = Contacts.LOOKUP_KEY + " = ?";
-            String[] selectionArgs = new String[] { phoneLookupKey };
-            try (Cursor cursor = context.getContentResolver().query(
-                    ContactsContract.CommonDataKinds.Phone.CONTENT_URI, PHONE_LOOKUP_PROJECTION,
-                    selection, selectionArgs, /* sortOrder= */ null)) {
-                if (cursor == null) {
-                    Slog.w(TAG, "Cursor is null when querying contact phone number.");
-                    return lookupResult;
-                }
-
-                while (cursor.moveToNext()) {
-                    lookupResult.mergePhoneNumber(cursor);
-                }
-            } catch (Throwable t) {
-                Slog.w(TAG, "Problem getting content resolver or querying phone numbers.", t);
-            }
-        }
-        return lookupResult;
-    }
-
-    private void addWorkContacts(LookupResult lookupResult, Context context, Uri corpLookupUri) {
-        final int workUserId = findWorkUserId(context);
-        if (workUserId == -1) {
-            Slog.w(TAG, "Work profile user ID not found for work contact: " + corpLookupUri);
-            return;
-        }
-        final Uri corpLookupUriWithUserId =
-                ContentProvider.maybeAddUserId(corpLookupUri, workUserId);
-        addContacts(lookupResult, context, corpLookupUriWithUserId);
-    }
-
-    /** Returns the user ID of the managed profile or -1 if none is found. */
-    private int findWorkUserId(Context context) {
-        final UserManager userManager = context.getSystemService(UserManager.class);
-        final int[] profileIds =
-                userManager.getProfileIds(context.getUserId(), /* enabledOnly= */ true);
-        for (int profileId : profileIds) {
-            if (userManager.isManagedProfile(profileId)) {
-                return profileId;
-            }
-        }
-        return -1;
-    }
-
-    /** Modifies the given lookup result to add contacts found at the given URI. */
-    private void addContacts(LookupResult lookupResult, Context context, Uri uri) {
-        try (Cursor c = context.getContentResolver().query(
-                uri, LOOKUP_PROJECTION, null, null, null)) {
-            if (c == null) {
-                Slog.w(TAG, "Null cursor from contacts query.");
-                return;
-            }
-            while (c.moveToNext()) {
-                lookupResult.mergeContact(c);
-            }
-        } catch (Throwable t) {
-            Slog.w(TAG, "Problem getting content resolver or performing contacts query.", t);
-        }
-    }
-
     @VisibleForTesting
     protected static class LookupResult {
         private static final long CONTACT_REFRESH_MILLIS = 60 * 60 * 1000;  // 1hr
@@ -619,19 +525,18 @@
         }
     }
 
-    private class PeopleRankingReconsideration extends RankingReconsideration {
+    @VisibleForTesting
+    class PeopleRankingReconsideration extends RankingReconsideration {
         private final LinkedList<String> mPendingLookups;
         private final Context mContext;
 
-        // Amount of time to wait for a result from the contacts db before rechecking affinity.
-        private static final long LOOKUP_TIME = 1000;
         private float mContactAffinity = NONE;
         private ArraySet<String> mPhoneNumbers = null;
         private NotificationRecord mRecord;
 
         private PeopleRankingReconsideration(Context context, String key,
                 LinkedList<String> pendingLookups) {
-            super(key, LOOKUP_TIME);
+            super(key);
             mContext = context;
             mPendingLookups = pendingLookups;
         }
@@ -642,7 +547,7 @@
             long timeStartMs = System.currentTimeMillis();
             for (final String handle: mPendingLookups) {
                 final String cacheKey = getCacheKey(mContext.getUserId(), handle);
-                LookupResult lookupResult = null;
+                LookupResult lookupResult;
                 boolean cacheHit = false;
                 synchronized (mPeopleCache) {
                     lookupResult = mPeopleCache.get(cacheKey);
@@ -703,6 +608,102 @@
             }
         }
 
+        private static LookupResult resolvePhoneContact(Context context, final String number) {
+            Uri phoneUri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
+                    Uri.encode(number));
+            return searchContacts(context, phoneUri);
+        }
+
+        private static LookupResult resolveEmailContact(Context context, final String email) {
+            Uri numberUri = Uri.withAppendedPath(
+                    ContactsContract.CommonDataKinds.Email.CONTENT_LOOKUP_URI,
+                    Uri.encode(email));
+            return searchContacts(context, numberUri);
+        }
+
+        @VisibleForTesting
+        static LookupResult searchContacts(Context context, Uri lookupUri) {
+            LookupResult lookupResult = new LookupResult();
+            final Uri corpLookupUri =
+                    ContactsContract.Contacts.createCorpLookupUriFromEnterpriseLookupUri(lookupUri);
+            if (corpLookupUri == null) {
+                addContacts(lookupResult, context, lookupUri);
+            } else {
+                addWorkContacts(lookupResult, context, corpLookupUri);
+            }
+            return lookupResult;
+        }
+
+        @VisibleForTesting
+        // Performs a contacts search using searchContacts, and then follows up by looking up
+        // any phone numbers associated with the resulting contact information and merge those
+        // into the lookup result as well. Will have no additional effect if the contact does
+        // not have any phone numbers.
+        static LookupResult searchContactsAndLookupNumbers(Context context, Uri lookupUri) {
+            LookupResult lookupResult = searchContacts(context, lookupUri);
+            String phoneLookupKey = lookupResult.getPhoneLookupKey();
+            if (phoneLookupKey != null) {
+                String selection = Contacts.LOOKUP_KEY + " = ?";
+                String[] selectionArgs = new String[] { phoneLookupKey };
+                try (Cursor cursor = context.getContentResolver().query(
+                        ContactsContract.CommonDataKinds.Phone.CONTENT_URI, PHONE_LOOKUP_PROJECTION,
+                        selection, selectionArgs, /* sortOrder= */ null)) {
+                    if (cursor == null) {
+                        Slog.w(TAG, "Cursor is null when querying contact phone number.");
+                        return lookupResult;
+                    }
+
+                    while (cursor.moveToNext()) {
+                        lookupResult.mergePhoneNumber(cursor);
+                    }
+                } catch (Throwable t) {
+                    Slog.w(TAG, "Problem getting content resolver or querying phone numbers.", t);
+                }
+            }
+            return lookupResult;
+        }
+
+        private static void addWorkContacts(LookupResult lookupResult, Context context,
+                Uri corpLookupUri) {
+            final int workUserId = findWorkUserId(context);
+            if (workUserId == -1) {
+                Slog.w(TAG, "Work profile user ID not found for work contact: " + corpLookupUri);
+                return;
+            }
+            final Uri corpLookupUriWithUserId =
+                    ContentProvider.maybeAddUserId(corpLookupUri, workUserId);
+            addContacts(lookupResult, context, corpLookupUriWithUserId);
+        }
+
+        /** Returns the user ID of the managed profile or -1 if none is found. */
+        private static int findWorkUserId(Context context) {
+            final UserManager userManager = context.getSystemService(UserManager.class);
+            final int[] profileIds =
+                    userManager.getProfileIds(context.getUserId(), /* enabledOnly= */ true);
+            for (int profileId : profileIds) {
+                if (userManager.isManagedProfile(profileId)) {
+                    return profileId;
+                }
+            }
+            return -1;
+        }
+
+        /** Modifies the given lookup result to add contacts found at the given URI. */
+        private static void addContacts(LookupResult lookupResult, Context context, Uri uri) {
+            try (Cursor c = context.getContentResolver().query(
+                    uri, LOOKUP_PROJECTION, null, null, null)) {
+                if (c == null) {
+                    Slog.w(TAG, "Null cursor from contacts query.");
+                    return;
+                }
+                while (c.moveToNext()) {
+                    lookupResult.mergeContact(c);
+                }
+            } catch (Throwable t) {
+                Slog.w(TAG, "Problem getting content resolver or performing contacts query.", t);
+            }
+        }
+
         @Override
         public void applyChangesLocked(NotificationRecord operand) {
             float affinityBound = operand.getContactAffinity();
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/IncrementalProgressListener.java b/services/core/java/com/android/server/pm/IncrementalProgressListener.java
index 703bbda..420e2e9 100644
--- a/services/core/java/com/android/server/pm/IncrementalProgressListener.java
+++ b/services/core/java/com/android/server/pm/IncrementalProgressListener.java
@@ -47,6 +47,8 @@
                     state -> state.setLoadingProgress(progress));
             // Only report the state change when loading state changes from loading to not
             if (Math.abs(1.0f - progress) < 0.00000001f) {
+                mPm.commitPackageStateMutation(null, mPackageName,
+                        state -> state.setLoadingCompletedTime(System.currentTimeMillis()));
                 // Unregister progress listener
                 mPm.mIncrementalManager
                         .unregisterLoadingProgressCallbacks(packageState.getPathString());
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 d3ee52c..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");
@@ -6811,7 +6812,8 @@
             if (ps == null) {
                 return null;
             }
-            return new IncrementalStatesInfo(ps.isLoading(), ps.getLoadingProgress());
+            return new IncrementalStatesInfo(ps.isLoading(), ps.getLoadingProgress(),
+                    ps.getLoadingCompletedTime());
         }
 
         @Override
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 2a1172c..839ff41 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -32,6 +32,7 @@
 import android.content.pm.UserInfo;
 import android.content.pm.overlay.OverlayPaths;
 import android.os.UserHandle;
+import android.os.incremental.IncrementalManager;
 import android.service.pm.PackageProto;
 import android.text.TextUtils;
 import android.util.ArrayMap;
@@ -140,6 +141,7 @@
     private String mPathString;
 
     private float mLoadingProgress;
+    private long mLoadingCompletedTime;
 
     @Nullable
     private String mPrimaryCpuAbi;
@@ -630,6 +632,7 @@
         super.copySettingBase(other);
         mSharedUserAppId = other.mSharedUserAppId;
         mLoadingProgress = other.mLoadingProgress;
+        mLoadingCompletedTime = other.mLoadingCompletedTime;
         legacyNativeLibraryPath = other.legacyNativeLibraryPath;
         mName = other.mName;
         mRealName = other.mRealName;
@@ -1146,6 +1149,9 @@
         return readUserState(userId).getSplashScreenTheme();
     }
 
+    public boolean isIncremental() {
+        return IncrementalManager.isIncrementalPath(mPathString);
+    }
     /**
      * @return True if package is still being loaded, false if the package is fully loaded.
      */
@@ -1159,6 +1165,12 @@
         return this;
     }
 
+    public PackageSetting setLoadingCompletedTime(long loadingCompletedTime) {
+        mLoadingCompletedTime = loadingCompletedTime;
+        onChanged();
+        return this;
+    }
+
     @NonNull
     @Override
     public long getVersionCode() {
@@ -1489,6 +1501,11 @@
     }
 
     @DataClass.Generated.Member
+    public long getLoadingCompletedTime() {
+        return mLoadingCompletedTime;
+    }
+
+    @DataClass.Generated.Member
     public @Nullable String getCpuAbiOverride() {
         return mCpuAbiOverride;
     }
@@ -1563,10 +1580,10 @@
     }
 
     @DataClass.Generated(
-            time = 1665779003744L,
+            time = 1678228625853L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/services/core/java/com/android/server/pm/PackageSetting.java",
-            inputSignatures = "private  int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate  int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate  float mLoadingProgress\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate  long mLastModifiedTime\nprivate  long lastUpdateTime\nprivate  long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate  boolean installPermissionsFixed\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate  int categoryOverride\nprivate  boolean updateAvailable\nprivate  boolean forceQueryableOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate  com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic  com.android.server.pm.PackageSetting snapshot()\npublic  void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic  com.android.server.pm.PackageSetting setAppId(int)\npublic  com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic  com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic  com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic  com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic  com.android.server.pm.PackageSetting setInstallerPackageName(java.lang.String)\npublic  com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n  com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic  com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic  com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic  com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic  com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic  com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic  boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic  com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic  com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic  com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic  void setSharedUserAppId(int)\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected  void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  void updateFrom(com.android.server.pm.PackageSetting)\n  com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic  com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic  boolean isPrivileged()\npublic  boolean isOem()\npublic  boolean isVendor()\npublic  boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic  boolean isSystemExt()\npublic  boolean isOdm()\npublic  boolean isSystem()\npublic  android.content.pm.SigningDetails getSigningDetails()\npublic  com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic  void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic  com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n  void setEnabled(int,int,java.lang.String)\n  int getEnabled(int)\n  void setInstalled(boolean,int)\n  boolean getInstalled(int)\n  int getInstallReason(int)\n  void setInstallReason(int,int)\n  int getUninstallReason(int)\n  void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n  boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n  boolean isAnyInstalled(int[])\n  int[] queryInstalledUsers(int[],boolean)\n  long getCeDataInode(int)\n  void setCeDataInode(long,int)\n  boolean getStopped(int)\n  void setStopped(boolean,int)\n  boolean getNotLaunched(int)\n  void setNotLaunched(boolean,int)\n  boolean getHidden(int)\n  void setHidden(boolean,int)\n  int getDistractionFlags(int)\n  void setDistractionFlags(int,int)\npublic  boolean getInstantApp(int)\n  void setInstantApp(boolean,int)\n  boolean getVirtualPreload(int)\n  void setVirtualPreload(boolean,int)\n  void setUserState(int,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long)\n  void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n  void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n  void addDisabledComponent(java.lang.String,int)\n  void addEnabledComponent(java.lang.String,int)\n  boolean enableComponentLPw(java.lang.String,int)\n  boolean disableComponentLPw(java.lang.String,int)\n  boolean restoreComponentLPw(java.lang.String,int)\n  int getCurrentEnabledStateLPr(java.lang.String,int)\n  void removeUser(int)\npublic  int[] getNotInstalledUserIds()\n  void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected  void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\n  com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic  void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic  boolean isLoading()\npublic  com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getUsesLibraries()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic  com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic  com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic  com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic  com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic  com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
+            inputSignatures = "private  int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate  int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate  float mLoadingProgress\nprivate  long mLoadingCompletedTime\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate  long mLastModifiedTime\nprivate  long lastUpdateTime\nprivate  long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate  boolean installPermissionsFixed\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate  int categoryOverride\nprivate  boolean updateAvailable\nprivate  boolean forceQueryableOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate  com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic  com.android.server.pm.PackageSetting snapshot()\npublic  void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic  com.android.server.pm.PackageSetting setAppId(int)\npublic  com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic  com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic  com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic  com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic  com.android.server.pm.PackageSetting setInstallerPackage(java.lang.String,int)\npublic  com.android.server.pm.PackageSetting setUpdateOwnerPackage(java.lang.String)\npublic  com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n  com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic  com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic  com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic  com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic  com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic  com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic  boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic  com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic  com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic  com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic  void setSharedUserAppId(int)\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected  void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  void updateFrom(com.android.server.pm.PackageSetting)\n  com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic  com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic  boolean isPrivileged()\npublic  boolean isOem()\npublic  boolean isVendor()\npublic  boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic  boolean isSystemExt()\npublic  boolean isOdm()\npublic  boolean isSystem()\npublic  android.content.pm.SigningDetails getSigningDetails()\npublic  com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic  void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic  com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n  void setEnabled(int,int,java.lang.String)\n  int getEnabled(int)\n  void setInstalled(boolean,int)\n  boolean getInstalled(int)\n  int getInstallReason(int)\n  void setInstallReason(int,int)\n  int getUninstallReason(int)\n  void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n  boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n  boolean isAnyInstalled(int[])\n  int[] queryInstalledUsers(int[],boolean)\n  long getCeDataInode(int)\n  void setCeDataInode(long,int)\n  boolean getStopped(int)\n  void setStopped(boolean,int)\n  boolean getNotLaunched(int)\n  void setNotLaunched(boolean,int)\n  boolean getHidden(int)\n  void setHidden(boolean,int)\n  int getDistractionFlags(int)\n  void setDistractionFlags(int,int)\npublic  boolean getInstantApp(int)\n  void setInstantApp(boolean,int)\n  boolean getVirtualPreload(int)\n  void setVirtualPreload(boolean,int)\n  void setUserState(int,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long)\n  void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n  void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n  void addDisabledComponent(java.lang.String,int)\n  void addEnabledComponent(java.lang.String,int)\n  boolean enableComponentLPw(java.lang.String,int)\n  boolean disableComponentLPw(java.lang.String,int)\n  boolean restoreComponentLPw(java.lang.String,int)\n  int getCurrentEnabledStateLPr(java.lang.String,int)\n  void removeUser(int)\npublic  int[] getNotInstalledUserIds()\n  void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected  void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\n  com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic  void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic  boolean isIncremental()\npublic  boolean isLoading()\npublic  com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic  com.android.server.pm.PackageSetting setLoadingCompletedTime(long)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getApexModuleName()\npublic  com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic  com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic  com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic  com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic  com.android.server.pm.PackageSetting setApexModuleName(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic  com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\npublic @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy @java.lang.Override int getHiddenApiEnforcementPolicy()\npublic @java.lang.Override boolean isApex()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index b6557d0..94a00d6e 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -2902,6 +2902,8 @@
             serializer.attributeInt(null, "sharedUserId", pkg.getAppId());
         }
         serializer.attributeFloat(null, "loadingProgress", pkg.getLoadingProgress());
+        serializer.attributeLongHex(null, "loadingCompletedTime",
+                pkg.getLoadingCompletedTime());
 
         writeUsesSdkLibLPw(serializer, pkg.getUsesSdkLibraries(),
                 pkg.getUsesSdkLibrariesVersionsMajor());
@@ -2988,6 +2990,7 @@
             serializer.attributeBoolean(null, "isLoading", true);
         }
         serializer.attributeFloat(null, "loadingProgress", pkg.getLoadingProgress());
+        serializer.attributeLongHex(null, "loadingCompletedTime", pkg.getLoadingCompletedTime());
 
         serializer.attribute(null, "domainSetId", pkg.getDomainSetId().toString());
 
@@ -3687,9 +3690,6 @@
             ps.setAppId(sharedUserAppId);
             ps.setSharedUserAppId(sharedUserAppId);
         }
-        final float loadingProgress =
-                parser.getAttributeFloat(null, "loadingProgress", 0);
-        ps.setLoadingProgress(loadingProgress);
 
         int outerDepth = parser.getDepth();
         int type;
@@ -3760,6 +3760,7 @@
         long versionCode = 0;
         boolean installedForceQueryable = false;
         float loadingProgress = 0;
+        long loadingCompletedTime = 0;
         UUID domainSetId;
         try {
             name = parser.getAttributeValue(null, ATTR_NAME);
@@ -3777,6 +3778,7 @@
             updateAvailable = parser.getAttributeBoolean(null, "updateAvailable", false);
             installedForceQueryable = parser.getAttributeBoolean(null, "forceQueryable", false);
             loadingProgress = parser.getAttributeFloat(null, "loadingProgress", 0);
+            loadingCompletedTime = parser.getAttributeLongHex(null, "loadingCompletedTime", 0);
 
             if (primaryCpuAbiString == null && legacyCpuAbiString != null) {
                 primaryCpuAbiString = legacyCpuAbiString;
@@ -3939,7 +3941,8 @@
                     .setSecondaryCpuAbi(secondaryCpuAbiString)
                     .setUpdateAvailable(updateAvailable)
                     .setForceQueryableOverride(installedForceQueryable)
-                    .setLoadingProgress(loadingProgress);
+                    .setLoadingProgress(loadingProgress)
+                    .setLoadingCompletedTime(loadingCompletedTime);
             // Handle legacy string here for single-user mode
             final String enabledStr = parser.getAttributeValue(null, ATTR_ENABLED);
             if (enabledStr != null) {
@@ -4900,9 +4903,11 @@
         }
         pw.print(prefix); pw.print("  packageSource=");
         pw.println(ps.getInstallSource().mPackageSource);
-        if (ps.isLoading()) {
+        if (ps.isIncremental()) {
             pw.print(prefix); pw.println("  loadingProgress=" +
                     (int) (ps.getLoadingProgress() * 100) + "%");
+            date.setTime(ps.getLoadingCompletedTime());
+            pw.print(prefix); pw.println("  loadingCompletedTime=" + sdf.format(date));
         }
         if (ps.getVolumeUuid() != null) {
             pw.print(prefix); pw.print("  volumeUuid=");
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index eb37302..721ad88 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -491,8 +491,11 @@
     public abstract boolean isUserVisible(@UserIdInt int userId, int displayId);
 
     /**
-     * Returns the display id assigned to the user, or {@code Display.INVALID_DISPLAY} if the
-     * user is not assigned to any display.
+     * Returns the main display id assigned to the user, or {@code Display.INVALID_DISPLAY} if the
+     * user is not assigned to any main display.
+     *
+     * <p>In the context of multi-user multi-display, there can be multiple main displays, at most
+     * one per each zone. Main displays are where UI is launched which a user interacts with.
      *
      * <p>The current foreground user and its running profiles are associated with the
      * {@link android.view.Display#DEFAULT_DISPLAY default display}, while other users would only be
@@ -503,9 +506,20 @@
      *
      * <p>If the user is a profile and is running, it's assigned to its parent display.
      */
+    // TODO(b/272366483) rename this method to avoid confusion with getDisplaysAssignedTOUser().
     public abstract int getDisplayAssignedToUser(@UserIdInt int userId);
 
     /**
+     * Returns all display ids assigned to the user including {@link
+     * #assignUserToExtraDisplay(int, int) extra displays}, or {@code null} if there is no display
+     * assigned to the specified user.
+     *
+     * <p>Note that this method is different from {@link #getDisplayAssignedToUser(int)}, which
+     * returns a main display only.
+     */
+    public abstract @Nullable int[] getDisplaysAssignedToUser(@UserIdInt int userId);
+
+    /**
      * Returns the main user (i.e., not a profile) that is assigned to the display, or the
      * {@link android.app.ActivityManager#getCurrentUser() current foreground user} if no user is
      * associated with the display.
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index cde8bd7..fdc2aff 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1545,7 +1545,8 @@
         // intentSender
         unlockIntent.putExtra(Intent.EXTRA_INTENT, pendingIntent.getIntentSender());
         unlockIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
-        mContext.startActivity(unlockIntent);
+        mContext.startActivityAsUser(
+                unlockIntent, UserHandle.of(getProfileParentIdUnchecked(userId)));
     }
 
     @Override
@@ -5864,20 +5865,24 @@
     }
 
     /**
-     * @deprecated Use {@link
-     * android.content.RestrictionsManager#getApplicationRestrictionsPerAdmin} instead.
+     * <p>Starting from Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+     * it is possible for there to be multiple managing agents on the device with the ability to set
+     * restrictions, e.g. an Enterprise DPC and a Supervision admin. This API will only to return
+     * the restrictions set by the DPCs. To retrieve restrictions set by all agents, use
+     * {@link android.content.RestrictionsManager#getApplicationRestrictionsPerAdmin} instead.
      */
-    @Deprecated
     @Override
     public Bundle getApplicationRestrictions(String packageName) {
         return getApplicationRestrictionsForUser(packageName, UserHandle.getCallingUserId());
     }
 
     /**
-     * @deprecated Use {@link
-     * android.content.RestrictionsManager#getApplicationRestrictionsPerAdmin} instead.
+     * <p>Starting from Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+     * it is possible for there to be multiple managing agents on the device with the ability to set
+     * restrictions, e.g. an Enterprise DPC and a Supervision admin. This API will only to return
+     * the restrictions set by the DPCs. To retrieve restrictions set by all agents, use
+     * {@link android.content.RestrictionsManager#getApplicationRestrictionsPerAdmin} instead.
      */
-    @Deprecated
     @Override
     public Bundle getApplicationRestrictionsForUser(String packageName, @UserIdInt int userId) {
         if (UserHandle.getCallingUserId() != userId
@@ -7189,6 +7194,11 @@
         }
 
         @Override
+        public @Nullable int[] getDisplaysAssignedToUser(@UserIdInt int userId) {
+            return mUserVisibilityMediator.getDisplaysAssignedToUser(userId);
+        }
+
+        @Override
         public @UserIdInt int getUserAssignedToDisplay(int displayId) {
             return mUserVisibilityMediator.getUserAssignedToDisplay(displayId);
         }
diff --git a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
index 12c9e98..2f99062 100644
--- a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
+++ b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
@@ -222,7 +222,7 @@
             final Set<String> userAllowlist = getInstallablePackagesForUserId(userId);
 
             pmInt.forEachPackageState(packageState -> {
-                if (packageState.getPkg() == null) {
+                if (packageState.getPkg() == null || !packageState.isSystem()) {
                     return;
                 }
                 boolean install = (userAllowlist == null
diff --git a/services/core/java/com/android/server/pm/UserVisibilityMediator.java b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
index a8615c2..3710af6 100644
--- a/services/core/java/com/android/server/pm/UserVisibilityMediator.java
+++ b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
@@ -63,19 +63,39 @@
 /**
  * Class responsible for deciding whether a user is visible (or visible for a given display).
  *
- * <p>Currently, it has 2 "modes" (set on constructor), which defines the class behavior (i.e, the
+ * <p>Currently, it has 3 "modes" (set on constructor), which defines the class behavior (i.e, the
  * logic that dictates the result of methods such as {@link #isUserVisible(int)} and
  * {@link #isUserVisible(int, int)}):
  *
  * <ul>
- *   <li>default: this is the most common mode (used by phones, tablets, foldables, automotives with
- *   just cluster and driver displayes, etc...), where the logic is based solely on the current
- *   foreground user (and its started profiles)
- *   <li>{@code MUMD}: mode for "(concurrent) Multiple Users on Multiple Displays", which is used on
- *   automotives with passenger display. In this mode, users started in background on the secondary
- *   display are stored in map.
+ *   <li>default (A.K.A {@code SUSD} - Single User on Single Display): this is the most common mode
+ *   (used by phones, tablets, foldables, cars with just cluster and driver displays, etc.),
+ *   where just the current foreground user and its profiles are visible; hence, most methods are
+ *   optimized to just check for the current user / profile. This mode is unit tested by
+ *   {@link com.android.server.pm.UserVisibilityMediatorSUSDTest} and CTS tested by
+ *   {@link android.multiuser.cts.UserVisibilityTest}.
+ *   <li>concurrent users (A.K.A. {@code MUMD} - Multiple Users on Multiple Displays): typically
+ *   used on automotive builds where the car has additional displays for passengers, it allows users
+ *   to be started in the background but visible on these displays; hence, it contains additional
+ *   maps to account for the visibility state. This mode is unit tested by
+ *   {@link com.android.server.pm.UserVisibilityMediatorMUMDTest} and CTS tested by
+ *   {@link android.multiuser.cts.UserVisibilityTest}.
+ *   <li>no driver (A.K.A. {@code MUPAND} - MUltiple PAssengers, No Driver): extension of the
+ *   previous mode and typically used on automotive builds where the car has additional displays for
+ *   passengers but uses a secondary Android system for the back passengers, so all "human" users
+ *   are started in the background (and the current foreground user is the system user), hence the
+ *   "no driver name". This mode is unit tested by
+ *   {@link com.android.server.pm.UserVisibilityMediatorMUPANDTest} and CTS tested by
+ *   {@link android.multiuser.cts.UserVisibilityVisibleBackgroundUsersOnDefaultDisplayTest}.
  * </ul>
  *
+ * <p>When you make changes in this class, you should run at least the 3 unit tests and
+ * {@link android.multiuser.cts.UserVisibilityTest} (which actually applies for all modes); for
+ * example, by calling {@code atest UserVisibilityMediatorSUSDTest UserVisibilityMediatorMUMDTest
+ * UserVisibilityMediatorMUPANDTest UserVisibilityTest}. Ideally, you should run the other 2 CTS
+ * tests as well (you can emulate these modes using {@code adb} commands; their javadoc provides
+ * instructions on how to do so).
+ *
  * <p>This class is thread safe.
  */
 public final class UserVisibilityMediator implements Dumpable {
@@ -786,6 +806,49 @@
         }
     }
 
+    /** See {@link UserManagerInternal#getDisplaysAssignedToUser(int)}. */
+    @Nullable
+    public int[] getDisplaysAssignedToUser(@UserIdInt int userId) {
+        int mainDisplayId = getDisplayAssignedToUser(userId);
+        if (mainDisplayId == INVALID_DISPLAY) {
+            // The user will not have any extra displays if they have no main display.
+            // Return null if no display is assigned to the user.
+            if (DBG) {
+                Slogf.d(TAG, "getDisplaysAssignedToUser(): returning null"
+                        + " because there is no display assigned to user %d", userId);
+            }
+            return null;
+        }
+
+        synchronized (mLock) {
+            if (mExtraDisplaysAssignedToUsers == null
+                    || mExtraDisplaysAssignedToUsers.size() == 0) {
+                return new int[]{mainDisplayId};
+            }
+
+            int count = 0;
+            int[] displayIds = new int[mExtraDisplaysAssignedToUsers.size() + 1];
+            displayIds[count++] = mainDisplayId;
+            for (int i = 0; i < mExtraDisplaysAssignedToUsers.size(); ++i) {
+                if (mExtraDisplaysAssignedToUsers.valueAt(i) == userId) {
+                    displayIds[count++] = mExtraDisplaysAssignedToUsers.keyAt(i);
+                }
+            }
+            // Return the array if the array length happens to be correct.
+            if (displayIds.length == count) {
+                return displayIds;
+            }
+
+            // Copy the results to a new array with the exact length. The size of displayIds[] is
+            // initialized to `1 + mExtraDisplaysAssignedToUsers.size()`, which is usually larger
+            // than the actual length, because mExtraDisplaysAssignedToUsers contains displayIds for
+            // other users. Therefore, we need to copy to a new array with the correct length.
+            int[] results = new int[count];
+            System.arraycopy(displayIds, 0, results, 0, count);
+            return results;
+        }
+    }
+
     /**
      * See {@link UserManagerInternal#getUserAssignedToDisplay(int)}.
      */
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/pm/pkg/PackageStateInternal.java b/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java
index 2f4c0277..3a0ff27 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java
@@ -81,6 +81,8 @@
 
     float getLoadingProgress();
 
+    long getLoadingCompletedTime();
+
     @NonNull
     PackageKeySetData getKeySetData();
 
diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
index 5947d47..8125b0f 100644
--- a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
+++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
@@ -274,6 +274,15 @@
 
         @NonNull
         @Override
+        public PackageStateWrite setLoadingCompletedTime(long loadingCompletedTime) {
+            if (mState != null) {
+                mState.setLoadingCompletedTime(loadingCompletedTime);
+            }
+            return this;
+        }
+
+        @NonNull
+        @Override
         public PackageStateWrite setOverrideSeInfo(@Nullable String newSeInfo) {
             if (mState != null) {
                 mState.getTransientState().setOverrideSeInfo(newSeInfo);
diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java
index c610c02..55d96f3 100644
--- a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java
+++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java
@@ -53,6 +53,9 @@
     PackageStateWrite setLoadingProgress(float progress);
 
     @NonNull
+    PackageStateWrite setLoadingCompletedTime(long loadingCompletedTime);
+
+    @NonNull
     PackageStateWrite setOverrideSeInfo(@Nullable String newSeInfo);
 
     @NonNull
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/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index da7aaa4..d0ed9bf 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -241,7 +241,7 @@
                 UUID.randomUUID().toString(),
                 Intent.ACTION_SCREEN_ON);
         // This allows the broadcast delivery to be delayed to apps in the Cached state.
-        options.setDeferUntilActive(true);
+        options.setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE);
         return options.toBundle();
     }
 
diff --git a/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java b/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java
index 8e8abf6..96f4a01 100644
--- a/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java
+++ b/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java
@@ -250,15 +250,7 @@
                 service.checkRecognitionSupport(recognizerIntent, attributionSource, callback));
     }
 
-    void triggerModelDownload(Intent recognizerIntent, AttributionSource attributionSource) {
-        if (!mConnected) {
-            Slog.e(TAG, "#downloadModel failed due to connection.");
-            return;
-        }
-        run(service -> service.triggerModelDownload(recognizerIntent, attributionSource));
-    }
-
-    void setModelDownloadListener(
+    void triggerModelDownload(
             Intent recognizerIntent,
             AttributionSource attributionSource,
             IModelDownloadListener listener) {
@@ -266,25 +258,12 @@
             try {
                 listener.onError(SpeechRecognizer.ERROR_SERVER_DISCONNECTED);
             } catch (RemoteException e) {
-                Slog.w(TAG, "Failed to report the connection broke to the caller.", e);
+                Slog.w(TAG, "#downloadModel failed due to connection.", e);
                 e.printStackTrace();
             }
             return;
         }
-
-        run(service ->
-                service.setModelDownloadListener(recognizerIntent, attributionSource, listener));
-    }
-
-    void clearModelDownloadListener(
-            Intent recognizerIntent,
-            AttributionSource attributionSource) {
-        if (!mConnected) {
-            return;
-        }
-
-        run(service ->
-                service.clearModelDownloadListener(recognizerIntent, attributionSource));
+        run(service -> service.triggerModelDownload(recognizerIntent, attributionSource, listener));
     }
 
     void shutdown(IBinder clientToken) {
diff --git a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
index bc73db1..bff6d50 100644
--- a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
@@ -193,25 +193,11 @@
                         @Override
                         public void triggerModelDownload(
                                 Intent recognizerIntent,
-                                AttributionSource attributionSource) {
-                            service.triggerModelDownload(recognizerIntent, attributionSource);
-                        }
-
-                        @Override
-                        public void setModelDownloadListener(
-                                Intent recognizerIntent,
                                 AttributionSource attributionSource,
-                                IModelDownloadListener listener) throws RemoteException {
-                            service.setModelDownloadListener(
+                                IModelDownloadListener listener) {
+                            service.triggerModelDownload(
                                     recognizerIntent, attributionSource, listener);
                         }
-
-                        @Override
-                        public void clearModelDownloadListener(
-                                Intent recognizerIntent,
-                                AttributionSource attributionSource) throws RemoteException {
-                            service.clearModelDownloadListener(recognizerIntent, attributionSource);
-                        }
                     });
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Error creating a speech recognition session", e);
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 601d0e2..3d8f538 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -34,6 +34,7 @@
 import static android.net.NetworkTemplate.OEM_MANAGED_PRIVATE;
 import static android.os.Debug.getIonHeapsSizeKb;
 import static android.os.Process.LAST_SHARED_APPLICATION_GID;
+import static android.os.Process.SYSTEM_UID;
 import static android.os.Process.getUidForPid;
 import static android.os.storage.VolumeInfo.TYPE_PRIVATE;
 import static android.os.storage.VolumeInfo.TYPE_PUBLIC;
@@ -89,8 +90,10 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.IncrementalStatesInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
 import android.content.pm.PermissionInfo;
 import android.content.pm.UserInfo;
 import android.hardware.biometrics.BiometricsProtoEnums;
@@ -4213,20 +4216,26 @@
 
     int pullInstalledIncrementalPackagesLocked(int atomTag, List<StatsEvent> pulledData) {
         final PackageManager pm = mContext.getPackageManager();
+        final PackageManagerInternal pmIntenral =
+                LocalServices.getService(PackageManagerInternal.class);
         if (!pm.hasSystemFeature(PackageManager.FEATURE_INCREMENTAL_DELIVERY)) {
             // Incremental is not enabled on this device. The result list will be empty.
             return StatsManager.PULL_SUCCESS;
         }
         final long token = Binder.clearCallingIdentity();
         try {
-            int[] userIds = LocalServices.getService(UserManagerInternal.class).getUserIds();
+            final int[] userIds = LocalServices.getService(UserManagerInternal.class).getUserIds();
             for (int userId : userIds) {
-                List<PackageInfo> installedPackages = pm.getInstalledPackagesAsUser(0, userId);
+                final List<PackageInfo> installedPackages = pm.getInstalledPackagesAsUser(
+                        0, userId);
                 for (PackageInfo pi : installedPackages) {
                     if (IncrementalManager.isIncrementalPath(
                             pi.applicationInfo.getBaseCodePath())) {
+                        final IncrementalStatesInfo info = pmIntenral.getIncrementalStatesInfo(
+                                pi.packageName, SYSTEM_UID, userId);
                         pulledData.add(
-                                FrameworkStatsLog.buildStatsEvent(atomTag, pi.applicationInfo.uid));
+                                FrameworkStatsLog.buildStatsEvent(atomTag, pi.applicationInfo.uid,
+                                        info.isLoading(), info.getLoadingCompletedTime()));
                     }
                 }
             }
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
index ed91775..2d3928c 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
@@ -1019,6 +1019,7 @@
         int inUseLowestPriorityFrHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
         // Priority max value is 1000
         int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
+        boolean isRequestFromSameProcess = false;
         // If the desired frontend id was specified, we only need to check the frontend.
         boolean hasDesiredFrontend = request.desiredId != TunerFrontendRequest.DEFAULT_DESIRED_ID;
         for (FrontendResource fr : getFrontendResources().values()) {
@@ -1048,6 +1049,8 @@
                     if (currentLowestPriority > priority) {
                         inUseLowestPriorityFrHandle = fr.getHandle();
                         currentLowestPriority = priority;
+                        isRequestFromSameProcess = (requestClient.getProcessId()
+                            == (getClientProfile(fr.getOwnerClientId())).getProcessId());
                     }
                 }
             }
@@ -1063,7 +1066,8 @@
         // When all the resources are occupied, grant the lowest priority resource if the
         // request client has higher priority.
         if (inUseLowestPriorityFrHandle != TunerResourceManager.INVALID_RESOURCE_HANDLE
-                && (requestClient.getPriority() > currentLowestPriority)) {
+            && ((requestClient.getPriority() > currentLowestPriority) || (
+            (requestClient.getPriority() == currentLowestPriority) && isRequestFromSameProcess))) {
             if (!reclaimResource(
                     getFrontendResource(inUseLowestPriorityFrHandle).getOwnerClientId(),
                     TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
@@ -1182,6 +1186,7 @@
         int inUseLowestPriorityLnbHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
         // Priority max value is 1000
         int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
+        boolean isRequestFromSameProcess = false;
         for (LnbResource lnb : getLnbResources().values()) {
             if (!lnb.isInUse()) {
                 // Grant the unused lnb with lower handle first
@@ -1194,6 +1199,8 @@
                 if (currentLowestPriority > priority) {
                     inUseLowestPriorityLnbHandle = lnb.getHandle();
                     currentLowestPriority = priority;
+                    isRequestFromSameProcess = (requestClient.getProcessId()
+                        == (getClientProfile(lnb.getOwnerClientId())).getProcessId());
                 }
             }
         }
@@ -1208,7 +1215,8 @@
         // When all the resources are occupied, grant the lowest priority resource if the
         // request client has higher priority.
         if (inUseLowestPriorityLnbHandle > TunerResourceManager.INVALID_RESOURCE_HANDLE
-                && (requestClient.getPriority() > currentLowestPriority)) {
+            && ((requestClient.getPriority() > currentLowestPriority) || (
+            (requestClient.getPriority() == currentLowestPriority) && isRequestFromSameProcess))) {
             if (!reclaimResource(getLnbResource(inUseLowestPriorityLnbHandle).getOwnerClientId(),
                     TunerResourceManager.TUNER_RESOURCE_TYPE_LNB)) {
                 return false;
@@ -1240,6 +1248,7 @@
         int lowestPriorityOwnerId = -1;
         // Priority max value is 1000
         int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
+        boolean isRequestFromSameProcess = false;
         if (!cas.isFullyUsed()) {
             casSessionHandle[0] = generateResourceHandle(
                     TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION, cas.getSystemId());
@@ -1252,12 +1261,15 @@
             if (currentLowestPriority > priority) {
                 lowestPriorityOwnerId = ownerId;
                 currentLowestPriority = priority;
+                isRequestFromSameProcess = (requestClient.getProcessId()
+                    == (getClientProfile(ownerId)).getProcessId());
             }
         }
 
         // When all the Cas sessions are occupied, reclaim the lowest priority client if the
         // request client has higher priority.
-        if (lowestPriorityOwnerId > -1 && (requestClient.getPriority() > currentLowestPriority)) {
+        if (lowestPriorityOwnerId > -1 && ((requestClient.getPriority() > currentLowestPriority)
+        || ((requestClient.getPriority() == currentLowestPriority) && isRequestFromSameProcess))) {
             if (!reclaimResource(lowestPriorityOwnerId,
                     TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION)) {
                 return false;
@@ -1289,6 +1301,7 @@
         int lowestPriorityOwnerId = -1;
         // Priority max value is 1000
         int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
+        boolean isRequestFromSameProcess = false;
         if (!ciCam.isFullyUsed()) {
             ciCamHandle[0] = generateResourceHandle(
                     TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM, ciCam.getCiCamId());
@@ -1301,12 +1314,16 @@
             if (currentLowestPriority > priority) {
                 lowestPriorityOwnerId = ownerId;
                 currentLowestPriority = priority;
+                isRequestFromSameProcess = (requestClient.getProcessId()
+                    == (getClientProfile(ownerId)).getProcessId());
             }
         }
 
         // When all the CiCam sessions are occupied, reclaim the lowest priority client if the
         // request client has higher priority.
-        if (lowestPriorityOwnerId > -1 && (requestClient.getPriority() > currentLowestPriority)) {
+        if (lowestPriorityOwnerId > -1 && ((requestClient.getPriority() > currentLowestPriority)
+            || ((requestClient.getPriority() == currentLowestPriority)
+                && isRequestFromSameProcess))) {
             if (!reclaimResource(lowestPriorityOwnerId,
                     TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM)) {
                 return false;
@@ -1424,6 +1441,7 @@
         int inUseLowestPriorityDrHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
         // Priority max value is 1000
         int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
+        boolean isRequestFromSameProcess = false;
         // If the desired demux id was specified, we only need to check the demux.
         boolean hasDesiredDemuxCap = request.desiredFilterTypes
                 != DemuxFilterMainType.UNDEFINED;
@@ -1448,6 +1466,8 @@
                         // update lowest priority
                         if (currentLowestPriority > priority) {
                             currentLowestPriority = priority;
+                            isRequestFromSameProcess = (requestClient.getProcessId()
+                                == (getClientProfile(dr.getOwnerClientId())).getProcessId());
                             shouldUpdate = true;
                         }
                         // update smallest caps
@@ -1473,7 +1493,8 @@
         // When all the resources are occupied, grant the lowest priority resource if the
         // request client has higher priority.
         if (inUseLowestPriorityDrHandle != TunerResourceManager.INVALID_RESOURCE_HANDLE
-                && (requestClient.getPriority() > currentLowestPriority)) {
+            && ((requestClient.getPriority() > currentLowestPriority) || (
+            (requestClient.getPriority() == currentLowestPriority) && isRequestFromSameProcess))) {
             if (!reclaimResource(
                     getDemuxResource(inUseLowestPriorityDrHandle).getOwnerClientId(),
                     TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index d108f0d..f14a432 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -251,11 +251,6 @@
                     // {@link #restartActivityProcessIfVisible}.
                     restartingName = r.app.mName;
                     restartingUid = r.app.mUid;
-                    // Make EnsureActivitiesVisibleHelper#makeVisibleAndRestartIfNeeded not skip
-                    // restarting non-top activity.
-                    if (r != r.getTask().topRunningActivity()) {
-                        r.setVisibleRequested(false);
-                    }
                 }
                 r.activityStopped(icicle, persistentState, description);
             }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index d15d094..324a0ad 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4135,9 +4135,7 @@
         } else if (!mVisibleRequested && launchCount > 2
                 && lastLaunchTime > (SystemClock.uptimeMillis() - 60000)) {
             // We have launched this activity too many times since it was able to run, so give up
-            // and remove it. (Note if the activity is visible, we don't remove the record. We leave
-            // the dead window on the screen but the process will not be restarted unless user
-            // explicitly tap on it.)
+            // and remove it.
             remove = true;
         } else {
             // The process may be gone, but the activity lives on!
@@ -4159,11 +4157,6 @@
             if (DEBUG_APP) {
                 Slog.v(TAG_APP, "Keeping entry during removeHistory for activity " + this);
             }
-            // Set nowVisible to previous visible state. If the app was visible while it died, we
-            // leave the dead window on screen so it's basically visible. This is needed when user
-            // later tap on the dead window, we need to stop other apps when user transfers focus
-            // to the restarted activity.
-            nowVisible = mVisibleRequested;
         }
         // upgrade transition trigger to task if this is the last activity since it means we are
         // closing the task.
@@ -5232,6 +5225,11 @@
             Slog.w(TAG_WM, "Attempted to set visibility of non-existing app token: " + token);
             return;
         }
+        if (visible == mVisibleRequested && visible == mVisible
+                && mTransitionController.isShellTransitionsEnabled()) {
+            // For shell transition, it is no-op if there is no state change.
+            return;
+        }
         if (visible) {
             mDeferHidingClient = false;
         }
@@ -5270,13 +5268,18 @@
 
         // Before setting mVisibleRequested so we can track changes.
         boolean isCollecting = false;
+        boolean inFinishingTransition = false;
         if (mTransitionController.isShellTransitionsEnabled()) {
             isCollecting = mTransitionController.isCollecting();
             if (isCollecting) {
                 mTransitionController.collect(this);
             } else {
-                Slog.e(TAG, "setVisibility=" + visible + " while transition is not collecting "
-                        + this + " caller=" + Debug.getCallers(8));
+                inFinishingTransition = mTransitionController.inFinishingTransition(this);
+                if (!inFinishingTransition) {
+                    Slog.e(TAG, "setVisibility=" + visible
+                            + " while transition is not collecting or finishing "
+                            + this + " caller=" + Debug.getCallers(8));
+                }
             }
         }
 
@@ -5290,10 +5293,6 @@
         mLastDeferHidingClient = deferHidingClient;
 
         if (!visible) {
-            // If the app is dead while it was visible, we kept its dead window on screen.
-            // Now that the app is going invisible, we can remove it. It will be restarted
-            // if made visible again.
-            removeDeadWindows();
             // If this activity is about to finish/stopped and now becomes invisible, remove it
             // from the unknownApp list in case the activity does not want to draw anything, which
             // keep the user waiting for the next transition to start.
@@ -5357,6 +5356,10 @@
             }
             return;
         }
+        if (inFinishingTransition) {
+            // Let the finishing transition commit the visibility.
+            return;
+        }
         // If we are preparing an app transition, then delay changing
         // the visibility of this token until we execute that transition.
         if (deferCommitVisibilityChange(visible)) {
@@ -6642,9 +6645,6 @@
         // stop tracking
         mSplashScreenStyleSolidColor = true;
 
-        // We now have a good window to show, remove dead placeholders
-        removeDeadWindows();
-
         if (mStartingWindow != null) {
             ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Finish starting %s"
                     + ": first real window is shown, no animation", win.mToken);
@@ -7380,20 +7380,6 @@
         }
     }
 
-    void removeDeadWindows() {
-        for (int winNdx = mChildren.size() - 1; winNdx >= 0; --winNdx) {
-            WindowState win = mChildren.get(winNdx);
-            if (win.mAppDied) {
-                ProtoLog.w(WM_DEBUG_ADD_REMOVE,
-                        "removeDeadWindows: %s", win);
-                // Set mDestroying, we don't want any animation or delayed removal here.
-                win.mDestroying = true;
-                // Also removes child windows.
-                win.removeIfPossible();
-            }
-        }
-    }
-
     void setWillReplaceWindows(boolean animate) {
         ProtoLog.d(WM_DEBUG_ADD_REMOVE,
                 "Marking app token %s with replacing windows.", this);
diff --git a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
index 5d038dc..be7d9b6 100644
--- a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
+++ b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
@@ -92,6 +92,7 @@
         } else {
             mInputWindowHandleWrapper.setInputConfigMasked(0, InputConfig.NOT_TOUCHABLE);
         }
+        mInputWindowHandleWrapper.setDisplayId(mActivityRecord.getDisplayId());
         return mInputWindowHandleWrapper;
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 211c230..ce29564 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1680,6 +1680,11 @@
                 targetTask.removeImmediately("bulky-task");
                 return START_ABORTED;
             }
+            // When running transient transition, the transient launch target should keep on top.
+            // So disallow the transient hide activity to move itself to front, e.g. trampoline.
+            if (!mAvoidMoveToFront && r.mTransitionController.isTransientHide(targetTask)) {
+                mAvoidMoveToFront = true;
+            }
             mPriorAboveTask = TaskDisplayArea.getRootTaskAbove(targetTask.getRootTask());
         }
 
@@ -1796,7 +1801,7 @@
                 // root-task to the will not update the focused root-task.  If starting the new
                 // activity now allows the task root-task to be focusable, then ensure that we
                 // now update the focused root-task accordingly.
-                if (mTargetRootTask.isTopActivityFocusable()
+                if (!mAvoidMoveToFront && mTargetRootTask.isTopActivityFocusable()
                         && !mRootWindowContainer.isTopDisplayFocusedRootTask(mTargetRootTask)) {
                     mTargetRootTask.moveToFront("startActivityInner");
                 }
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index f9f972c..b67bc62 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -862,8 +862,16 @@
                 WindowContainer target, boolean isOpen) {
             final BackWindowAnimationAdaptor adaptor =
                     new BackWindowAnimationAdaptor(target, isOpen);
-            target.startAnimation(target.getPendingTransaction(), adaptor, false /* hidden */,
-                    ANIMATION_TYPE_PREDICT_BACK);
+            final SurfaceControl.Transaction pt = target.getPendingTransaction();
+            target.startAnimation(pt, adaptor, false /* hidden */, ANIMATION_TYPE_PREDICT_BACK);
+            // Workaround to show TaskFragment which can be hide in Transitions and won't show
+            // during isAnimating.
+            if (isOpen && target.asActivityRecord() != null) {
+                final TaskFragment fragment = target.asActivityRecord().getTaskFragment();
+                if (fragment != null) {
+                    pt.show(fragment.mSurfaceControl);
+                }
+            }
             return adaptor;
         }
 
diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
index dde89e9..9cc311d 100644
--- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
+++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
@@ -193,7 +193,7 @@
             }
 
             if (!r.attachedToProcess()) {
-                makeVisibleAndRestartIfNeeded(mStarting, mConfigChanges, isTop,
+                makeVisibleAndRestartIfNeeded(mStarting, mConfigChanges,
                         resumeTopActivity && isTop, r);
             } else if (r.isVisibleRequested()) {
                 // If this activity is already visible, then there is nothing to do here.
@@ -243,15 +243,7 @@
     }
 
     private void makeVisibleAndRestartIfNeeded(ActivityRecord starting, int configChanges,
-            boolean isTop, boolean andResume, ActivityRecord r) {
-        // We need to make sure the app is running if it's the top, or it is just made visible from
-        // invisible. If the app is already visible, it must have died while it was visible. In this
-        // case, we'll show the dead window but will not restart the app. Otherwise we could end up
-        // thrashing.
-        if (!isTop && r.isVisibleRequested() && !r.isState(INITIALIZING)) {
-            return;
-        }
-
+            boolean andResume, ActivityRecord r) {
         // This activity needs to be visible, but isn't even running...
         // get it started and resume if no other root task in this root task is resumed.
         if (DEBUG_VISIBILITY) {
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 1e9d451..0a47fe0 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -25,7 +25,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.graphics.PointF;
 import android.os.Debug;
 import android.os.IBinder;
 import android.util.Slog;
@@ -221,11 +220,6 @@
     }
 
     @Override
-    public PointF getCursorPosition() {
-        return mService.getLatestMousePosition();
-    }
-
-    @Override
     public void onPointerDownOutsideFocus(IBinder touchedToken) {
         mService.mH.obtainMessage(ON_POINTER_DOWN_OUTSIDE_FOCUS, touchedToken).sendToTarget();
     }
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index f355f08..5db39fc 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -997,7 +997,8 @@
 
     @VisibleForTesting
     boolean shouldShowLetterboxUi(WindowState mainWindow) {
-        return isSurfaceVisible(mainWindow) && mainWindow.areAppWindowBoundsLetterboxed()
+        return (mActivityRecord.isInLetterboxAnimation() || isSurfaceVisible(mainWindow))
+                && mainWindow.areAppWindowBoundsLetterboxed()
                 // Check for FLAG_SHOW_WALLPAPER explicitly instead of using
                 // WindowContainer#showWallpaper because the later will return true when this
                 // activity is using blurred wallpaper for letterbox background.
@@ -1104,7 +1105,7 @@
     // for all corners for consistency and pick a minimal bottom one for consistency with a
     // taskbar rounded corners.
     int getRoundedCornersRadius(final WindowState mainWindow) {
-        if (!requiresRoundedCorners(mainWindow) || mActivityRecord.isInLetterboxAnimation()) {
+        if (!requiresRoundedCorners(mainWindow)) {
             return 0;
         }
 
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 969f65c..67ca844 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3398,8 +3398,10 @@
 
         final boolean isTopActivityResumed = top != null
                 && top.getOrganizedTask() == this && top.isState(RESUMED);
-        // Whether the direct top activity is in size compat mode on foreground.
-        info.topActivityInSizeCompat = isTopActivityResumed && top.inSizeCompatMode();
+        final boolean isTopActivityVisible = top != null
+                && top.getOrganizedTask() == this && top.isVisible();
+        // Whether the direct top activity is in size compat mode
+        info.topActivityInSizeCompat = isTopActivityVisible && top.inSizeCompatMode();
         if (info.topActivityInSizeCompat
                 && mWmService.mLetterboxConfiguration.isTranslucentLetterboxingEnabled()) {
             // We hide the restart button in case of transparent activities.
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 2ddb307..7c57dc1 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1012,6 +1012,10 @@
         if (isTopActivityLaunchedBehind()) {
             return TASK_FRAGMENT_VISIBILITY_VISIBLE;
         }
+        final Task thisTask = asTask();
+        if (thisTask != null && mTransitionController.isTransientHide(thisTask)) {
+            return TASK_FRAGMENT_VISIBILITY_VISIBLE;
+        }
 
         boolean gotTranslucentFullscreen = false;
         boolean gotTranslucentAdjacent = false;
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 4e0f120..370d304 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -194,6 +194,13 @@
      */
     private ArrayMap<ActivityRecord, Task> mTransientLaunches = null;
 
+    /**
+     * The tasks that may be occluded by the transient activity. Assume the task stack is
+     * [Home, A(opaque), B(opaque), C(translucent)] (bottom to top), then A is the restore-below
+     * task, and [B, C] are the transient-hide tasks.
+     */
+    private ArrayList<Task> mTransientHideTasks;
+
     /** Custom activity-level animation options and callbacks. */
     private TransitionInfo.AnimationOptions mOverrideOptions;
     private IRemoteCallback mClientAnimationStartCallback = null;
@@ -265,35 +272,51 @@
     void setTransientLaunch(@NonNull ActivityRecord activity, @Nullable Task restoreBelow) {
         if (mTransientLaunches == null) {
             mTransientLaunches = new ArrayMap<>();
+            mTransientHideTasks = new ArrayList<>();
         }
         mTransientLaunches.put(activity, restoreBelow);
         setTransientLaunchToChanges(activity);
 
         if (restoreBelow != null) {
-            final ChangeInfo info = mChanges.get(restoreBelow);
-            if (info != null) {
-                info.mFlags |= ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH;
+            // Collect all visible activities which can be occluded by the transient activity to
+            // make sure they are in the participants so their visibilities can be updated when
+            // finishing transition.
+            ((WindowContainer<?>) restoreBelow.getParent()).forAllTasks(t -> {
+                if (t.isVisibleRequested() && !t.isAlwaysOnTop()
+                        && !t.getWindowConfiguration().tasksAreFloating()) {
+                    if (t.isRootTask()) {
+                        mTransientHideTasks.add(t);
+                    }
+                    if (t.isLeafTask()) {
+                        t.forAllActivities(r -> {
+                            if (r.isVisibleRequested()) {
+                                collect(r);
+                            }
+                        });
+                    }
+                }
+                return t == restoreBelow;
+            });
+            // Add FLAG_ABOVE_TRANSIENT_LAUNCH to the tree of transient-hide tasks,
+            // so ChangeInfo#hasChanged() can return true to report the transition info.
+            for (int i = mChanges.size() - 1; i >= 0; --i) {
+                final WindowContainer<?> wc = mChanges.keyAt(i);
+                if (wc.asTaskFragment() == null && wc.asActivityRecord() == null) continue;
+                if (isInTransientHide(wc)) {
+                    mChanges.valueAt(i).mFlags |= ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH;
+                }
             }
         }
         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Transition %d: Set %s as "
                 + "transient-launch", mSyncId, activity);
     }
 
-    boolean isTransientHide(@NonNull Task task) {
-        if (mTransientLaunches == null) return false;
-        for (int i = 0; i < mTransientLaunches.size(); ++i) {
-            if (mTransientLaunches.valueAt(i) == task) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     /** @return whether `wc` is a descendent of a transient-hide window. */
     boolean isInTransientHide(@NonNull WindowContainer wc) {
-        if (mTransientLaunches == null) return false;
-        for (int i = 0; i < mTransientLaunches.size(); ++i) {
-            if (wc.isDescendantOf(mTransientLaunches.valueAt(i))) {
+        if (mTransientHideTasks == null) return false;
+        for (int i = mTransientHideTasks.size() - 1; i >= 0; --i) {
+            final Task task = mTransientHideTasks.get(i);
+            if (wc == task || wc.isDescendantOf(task)) {
                 return true;
             }
         }
@@ -814,6 +837,15 @@
         if (mState < STATE_PLAYING) {
             throw new IllegalStateException("Can't finish a non-playing transition " + mSyncId);
         }
+        mController.mFinishingTransition = this;
+
+        if (mTransientHideTasks != null && !mTransientHideTasks.isEmpty()) {
+            // The transient hide tasks could be occluded now, e.g. returning to home. So trigger
+            // the update to make the activities in the tasks invisible-requested, then the next
+            // step can continue to commit the visibility.
+            mController.mAtm.mRootWindowContainer.ensureActivitiesVisible(null /* starting */,
+                    0 /* configChanges */, true /* preserveWindows */);
+        }
 
         boolean hasParticipatedDisplay = false;
         boolean hasVisibleTransientLaunch = false;
@@ -980,6 +1012,7 @@
             dc.removeImeSurfaceImmediately();
             dc.handleCompleteDeferredRemoval();
         }
+        validateVisibility();
 
         mState = STATE_FINISHED;
         mController.mTransitionTracer.logState(this);
@@ -995,6 +1028,7 @@
 
         // Handle back animation if it's already started.
         mController.mAtm.mBackNavigationController.handleDeferredBackAnimation(mTargets);
+        mController.mFinishingTransition = null;
     }
 
     void abort() {
@@ -1173,14 +1207,13 @@
 
         // Record windowtokens (activity/wallpaper) that are expected to be visible after the
         // transition animation. This will be used in finishTransition to prevent prematurely
-        // committing visibility.
-        for (int i = mParticipants.size() - 1; i >= 0; --i) {
-            final WindowContainer wc = mParticipants.valueAt(i);
-            if (wc.asWindowToken() == null || !wc.isVisibleRequested()) continue;
-            // don't include transient launches, though, since those are only temporarily visible.
-            if (mTransientLaunches != null && wc.asActivityRecord() != null
-                    && mTransientLaunches.containsKey(wc.asActivityRecord())) continue;
-            mVisibleAtTransitionEndTokens.add(wc.asWindowToken());
+        // committing visibility. Skip transient launches since those are only temporarily visible.
+        if (mTransientLaunches == null) {
+            for (int i = mParticipants.size() - 1; i >= 0; --i) {
+                final WindowContainer wc = mParticipants.valueAt(i);
+                if (wc.asWindowToken() == null || !wc.isVisibleRequested()) continue;
+                mVisibleAtTransitionEndTokens.add(wc.asWindowToken());
+            }
         }
 
         // Take task snapshots before the animation so that we can capture IME before it gets
@@ -1274,7 +1307,7 @@
         if (mFinishTransaction != null) {
             mFinishTransaction.apply();
         }
-        mController.finishTransition(mToken);
+        mController.finishTransition(this);
     }
 
     private void cleanUpInternal() {
@@ -1888,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));
@@ -2145,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) {
@@ -2230,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/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index bacc6e6..86bb6b5 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -107,6 +107,9 @@
      */
     private final ArrayList<Transition> mPlayingTransitions = new ArrayList<>();
 
+    /** The currently finishing transition. */
+    Transition mFinishingTransition;
+
     /**
      * The windows that request to be invisible while it is in transition. After the transition
      * is finished and the windows are no longer animating, their surfaces will be destroyed.
@@ -313,6 +316,11 @@
         return false;
     }
 
+    /** Returns {@code true} if the `wc` is a participant of the finishing transition. */
+    boolean inFinishingTransition(WindowContainer<?> wc) {
+        return mFinishingTransition != null && mFinishingTransition.mParticipants.contains(wc);
+    }
+
     /** @return {@code true} if a transition is running */
     boolean inTransition() {
         // TODO(shell-transitions): eventually properly support multiple
@@ -358,11 +366,11 @@
     }
 
     boolean isTransientHide(@NonNull Task task) {
-        if (mCollectingTransition != null && mCollectingTransition.isTransientHide(task)) {
+        if (mCollectingTransition != null && mCollectingTransition.isInTransientHide(task)) {
             return true;
         }
         for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
-            if (mPlayingTransitions.get(i).isTransientHide(task)) return true;
+            if (mPlayingTransitions.get(i).isInTransientHide(task)) return true;
         }
         return false;
     }
@@ -672,14 +680,13 @@
     }
 
     /** @see Transition#finishTransition */
-    void finishTransition(@NonNull IBinder token) {
+    void finishTransition(Transition record) {
         // It is usually a no-op but make sure that the metric consumer is removed.
-        mTransitionMetricsReporter.reportAnimationStart(token, 0 /* startTime */);
+        mTransitionMetricsReporter.reportAnimationStart(record.getToken(), 0 /* startTime */);
         // It is a no-op if the transition did not change the display.
         mAtm.endLaunchPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
-        final Transition record = Transition.fromBinder(token);
-        if (record == null || !mPlayingTransitions.contains(record)) {
-            Slog.e(TAG, "Trying to finish a non-playing transition " + token);
+        if (!mPlayingTransitions.contains(record)) {
+            Slog.e(TAG, "Trying to finish a non-playing transition " + record);
             return;
         }
         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Finish Transition: %s", record);
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 45cdacd..42d23e7 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -186,7 +186,6 @@
 import android.graphics.Bitmap;
 import android.graphics.Matrix;
 import android.graphics.Point;
-import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.hardware.configstore.V1_0.OptionalBool;
@@ -7361,14 +7360,6 @@
                     .setPointerIconType(PointerIcon.TYPE_DEFAULT);
         }
     }
-
-    PointF getLatestMousePosition() {
-        synchronized (mMousePositionTracker) {
-            return new PointF(mMousePositionTracker.mLatestMouseX,
-                    mMousePositionTracker.mLatestMouseY);
-        }
-    }
-
     void setMousePointerDisplayId(int displayId) {
         mMousePositionTracker.setPointerDisplayId(displayId);
     }
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index c3c87af..17d4f1b 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -135,7 +135,7 @@
      */
     static final int CONTROLLABLE_CONFIGS = ActivityInfo.CONFIG_WINDOW_CONFIGURATION
             | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE | ActivityInfo.CONFIG_SCREEN_SIZE
-            | ActivityInfo.CONFIG_LAYOUT_DIRECTION;
+            | ActivityInfo.CONFIG_LAYOUT_DIRECTION | ActivityInfo.CONFIG_DENSITY;
     static final int CONTROLLABLE_WINDOW_CONFIGS = WINDOW_CONFIG_BOUNDS
             | WindowConfiguration.WINDOW_CONFIG_APP_BOUNDS;
 
@@ -391,9 +391,14 @@
                 // apply the incoming transaction before finish in case it alters the visibility
                 // of the participants.
                 if (t != null) {
+                    // Set the finishing transition before applyTransaction so the visibility
+                    // changes of the transition participants will only set visible-requested
+                    // and still let finishTransition handle the participants.
+                    mTransitionController.mFinishingTransition = transition;
                     applyTransaction(t, syncId, null /*transition*/, caller, transition);
                 }
-                getTransitionController().finishTransition(transitionToken);
+                mTransitionController.finishTransition(transition);
+                mTransitionController.mFinishingTransition = null;
                 if (syncId >= 0) {
                     setSyncReady(syncId);
                 }
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 694f1be..834b708 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -1381,6 +1381,13 @@
     }
 
     /**
+     * Destroys the WindwoProcessController, after the process has been removed.
+     */
+    void destroy() {
+        unregisterConfigurationListeners();
+    }
+
+    /**
      * Check if activity configuration override for the activity process needs an update and perform
      * if needed. By default we try to override the process configuration to match the top activity
      * config to increase app compatibility with multi-window and multi-display. The process will
diff --git a/services/core/java/com/android/server/wm/WindowProcessControllerMap.java b/services/core/java/com/android/server/wm/WindowProcessControllerMap.java
index 2767972..424b043 100644
--- a/services/core/java/com/android/server/wm/WindowProcessControllerMap.java
+++ b/services/core/java/com/android/server/wm/WindowProcessControllerMap.java
@@ -19,8 +19,8 @@
 import android.util.ArraySet;
 import android.util.SparseArray;
 
-import java.util.Map;
 import java.util.HashMap;
+import java.util.Map;
 
 final class WindowProcessControllerMap {
 
@@ -67,6 +67,7 @@
             mPidMap.remove(pid);
             // remove process from mUidMap
             removeProcessFromUidMap(proc);
+            proc.destroy();
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index f86b997..d6c0311 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -221,8 +221,6 @@
 import android.view.IWindowFocusObserver;
 import android.view.IWindowId;
 import android.view.InputChannel;
-import android.view.InputEvent;
-import android.view.InputEventReceiver;
 import android.view.InputWindowHandle;
 import android.view.InsetsSource;
 import android.view.InsetsState;
@@ -572,12 +570,6 @@
     boolean mRemoveOnExit;
 
     /**
-     * Whether the app died while it was visible, if true we might need
-     * to continue to show it until it's restarted.
-     */
-    boolean mAppDied;
-
-    /**
      * Set when the orientation is changing and this window has not yet
      * been updated for the new orientation.
      */
@@ -760,7 +752,6 @@
      */
     private InsetsState mFrozenInsetsState;
 
-    private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f;
     private KeyInterceptionInfo mKeyInterceptionInfo;
 
     /**
@@ -1504,13 +1495,6 @@
                 }
             }
 
-            // If it's a dead window left on screen, and the configuration changed, there is nothing
-            // we can do about it. Remove the window now.
-            if (mActivityRecord != null && mAppDied) {
-                mActivityRecord.removeDeadWindows();
-                return;
-            }
-
             onResizeHandled();
             mWmService.makeWindowFreezingScreenIfNeededLocked(this);
 
@@ -2009,7 +1993,7 @@
     boolean isInteresting() {
         final RecentsAnimationController recentsAnimationController =
                 mWmService.getRecentsAnimationController();
-        return mActivityRecord != null && !mAppDied
+        return mActivityRecord != null
                 && (!mActivityRecord.isFreezingScreen() || !mAppFreezing)
                 && mViewVisibility == View.VISIBLE
                 && (recentsAnimationController == null
@@ -2448,11 +2432,6 @@
 
     @Override
     void removeIfPossible() {
-        super.removeIfPossible();
-        removeIfPossible(false /*keepVisibleDeadWindow*/);
-    }
-
-    private void removeIfPossible(boolean keepVisibleDeadWindow) {
         mWindowRemovalAllowed = true;
         ProtoLog.v(WM_DEBUG_ADD_REMOVE,
                 "removeIfPossible: %s callers=%s", this, Debug.getCallers(5));
@@ -2527,21 +2506,6 @@
                 // If we are not currently running the exit animation, we need to see about starting one
                 wasVisible = isVisible();
 
-                if (keepVisibleDeadWindow) {
-                    ProtoLog.v(WM_DEBUG_ADD_REMOVE,
-                            "Not removing %s because app died while it's visible", this);
-
-                    mAppDied = true;
-                    setDisplayLayoutNeeded();
-                    mWmService.mWindowPlacerLocked.performSurfacePlacement();
-
-                    // Set up a replacement input channel since the app is now dead.
-                    // We need to catch tapping on the dead window to restart the app.
-                    openInputChannel(null);
-                    displayContent.getInputMonitor().updateInputWindowsLw(true /*force*/);
-                    return;
-                }
-
                 // Remove immediately if there is display transition because the animation is
                 // usually unnoticeable (e.g. covered by rotation animation) and the animation
                 // bounds could be inconsistent, such as depending on when the window applies
@@ -2715,19 +2679,7 @@
                 || (isVisible() && mActivityRecord != null && mActivityRecord.isVisible());
     }
 
-    private final class DeadWindowEventReceiver extends InputEventReceiver {
-        DeadWindowEventReceiver(InputChannel inputChannel) {
-            super(inputChannel, mWmService.mH.getLooper());
-        }
-        @Override
-        public void onInputEvent(InputEvent event) {
-            finishInputEvent(event, true);
-        }
-    }
-    /** Fake event receiver for windows that died visible. */
-    private DeadWindowEventReceiver mDeadWindowEventReceiver;
-
-    void openInputChannel(InputChannel outInputChannel) {
+    void openInputChannel(@NonNull InputChannel outInputChannel) {
         if (mInputChannel != null) {
             throw new IllegalStateException("Window already has an input channel.");
         }
@@ -2736,14 +2688,7 @@
         mInputChannelToken = mInputChannel.getToken();
         mInputWindowHandle.setToken(mInputChannelToken);
         mWmService.mInputToWindowMap.put(mInputChannelToken, this);
-        if (outInputChannel != null) {
-            mInputChannel.copyTo(outInputChannel);
-        } else {
-            // If the window died visible, we setup a fake input channel, so that taps
-            // can still detected by input monitor channel, and we can relaunch the app.
-            // Create fake event receiver that simply reports all events as handled.
-            mDeadWindowEventReceiver = new DeadWindowEventReceiver(mInputChannel);
-        }
+        mInputChannel.copyTo(outInputChannel);
     }
 
     /**
@@ -2754,10 +2699,6 @@
     }
 
     void disposeInputChannel() {
-        if (mDeadWindowEventReceiver != null) {
-            mDeadWindowEventReceiver.dispose();
-            mDeadWindowEventReceiver = null;
-        }
         if (mInputChannelToken != null) {
             // Unregister server channel first otherwise it complains about broken channel.
             mWmService.mInputManager.removeInputChannel(mInputChannelToken);
@@ -3084,11 +3025,10 @@
                             .windowForClientLocked(mSession, mClient, false);
                     Slog.i(TAG, "WIN DEATH: " + win);
                     if (win != null) {
-                        final DisplayContent dc = getDisplayContent();
                         if (win.mActivityRecord != null && win.mActivityRecord.findMainWindow() == win) {
                             mWmService.mTaskSnapshotController.onAppDied(win.mActivityRecord);
                         }
-                        win.removeIfPossible(shouldKeepVisibleDeadAppWindow());
+                        win.removeIfPossible();
                     } else if (mHasSurface) {
                         Slog.e(TAG, "!!! LEAK !!! Window removed but surface still valid.");
                         WindowState.this.removeIfPossible();
@@ -3100,32 +3040,6 @@
         }
     }
 
-    /**
-     * Returns true if this window is visible and belongs to a dead app and shouldn't be removed,
-     * because we want to preserve its location on screen to be re-activated later when the user
-     * interacts with it.
-     */
-    private boolean shouldKeepVisibleDeadAppWindow() {
-        if (!isVisible() || mActivityRecord == null || !mActivityRecord.isClientVisible()) {
-            // Not a visible app window or the app isn't dead.
-            return false;
-        }
-
-        if (mAttrs.token != mClient.asBinder()) {
-            // The window was add by a client using another client's app token. We don't want to
-            // keep the dead window around for this case since this is meant for 'real' apps.
-            return false;
-        }
-
-        if (mAttrs.type == TYPE_APPLICATION_STARTING) {
-            // We don't keep starting windows since they were added by the window manager before
-            // the app even launched.
-            return false;
-        }
-
-        return getWindowConfiguration().keepVisibleDeadAppWindowOnScreen();
-    }
-
     /** Returns {@code true} if this window desires key events. */
     boolean canReceiveKeys() {
         return canReceiveKeys(false /* fromUserTouch */);
@@ -3972,7 +3886,7 @@
     @Override
     public void notifyInsetsControlChanged() {
         ProtoLog.d(WM_DEBUG_WINDOW_INSETS, "notifyInsetsControlChanged for %s ", this);
-        if (mAppDied || mRemoved) {
+        if (mRemoved) {
             return;
         }
         final InsetsStateController stateController =
@@ -4278,7 +4192,6 @@
             pw.println(prefix + "mToken=" + mToken);
             if (mActivityRecord != null) {
                 pw.println(prefix + "mActivityRecord=" + mActivityRecord);
-                pw.print(prefix + "mAppDied=" + mAppDied);
                 pw.print(prefix + "drawnStateEvaluated=" + getDrawnStateEvaluated());
                 pw.println(prefix + "mightAffectAllDrawn=" + mightAffectAllDrawn());
             }
@@ -5407,10 +5320,7 @@
     }
 
     private void applyDims() {
-        if (!mAnimatingExit && mAppDied) {
-            mIsDimming = true;
-            getDimmer().dimAbove(getSyncTransaction(), this, DEFAULT_DIM_AMOUNT_DEAD_WINDOW);
-        } else if (((mAttrs.flags & FLAG_DIM_BEHIND) != 0 || shouldDrawBlurBehind())
+        if (((mAttrs.flags & FLAG_DIM_BEHIND) != 0 || shouldDrawBlurBehind())
                    && isVisibleNow() && !mHidden) {
             // Only show the Dimmer when the following is satisfied:
             // 1. The window has the flag FLAG_DIM_BEHIND or blur behind is requested
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index b4e2fb6..da44da4 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -308,6 +308,7 @@
     void setMotionClassifierEnabled(bool enabled);
     std::optional<std::string> getBluetoothAddress(int32_t deviceId);
     void setStylusButtonMotionEventsEnabled(bool enabled);
+    FloatPoint getMouseCursorPosition();
 
     /* --- InputReaderPolicyInterface implementation --- */
 
@@ -366,7 +367,7 @@
     virtual PointerIconStyle getDefaultPointerIconId();
     virtual PointerIconStyle getDefaultStylusIconId();
     virtual PointerIconStyle getCustomPointerIconId();
-    virtual void onPointerDisplayIdChanged(int32_t displayId, float xPos, float yPos);
+    virtual void onPointerDisplayIdChanged(int32_t displayId, const FloatPoint& position);
 
     /* --- If touch mode is enabled per display or global --- */
 
@@ -730,11 +731,11 @@
     return controller;
 }
 
-void NativeInputManager::onPointerDisplayIdChanged(int32_t pointerDisplayId, float xPos,
-                                                   float yPos) {
+void NativeInputManager::onPointerDisplayIdChanged(int32_t pointerDisplayId,
+                                                   const FloatPoint& position) {
     JNIEnv* env = jniEnv();
     env->CallVoidMethod(mServiceObj, gServiceClassInfo.onPointerDisplayIdChanged, pointerDisplayId,
-                        xPos, yPos);
+                        position.x, position.y);
     checkAndClearExceptionFromCallback(env, "onPointerDisplayIdChanged");
 }
 
@@ -1655,6 +1656,14 @@
     return static_cast<bool>(enabled);
 }
 
+FloatPoint NativeInputManager::getMouseCursorPosition() {
+    AutoMutex _l(mLock);
+    const auto pc = mLocked.pointerController.lock();
+    if (!pc) return {AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION};
+
+    return pc->getPosition();
+}
+
 // ----------------------------------------------------------------------------
 
 static NativeInputManager* getNativeInputManager(JNIEnv* env, jobject clazz) {
@@ -2547,6 +2556,15 @@
     im->setStylusButtonMotionEventsEnabled(enabled);
 }
 
+static jfloatArray nativeGetMouseCursorPosition(JNIEnv* env, jobject nativeImplObj) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+    const auto p = im->getMouseCursorPosition();
+    const std::array<float, 2> arr = {{p.x, p.y}};
+    jfloatArray outArr = env->NewFloatArray(2);
+    env->SetFloatArrayRegion(outArr, 0, arr.size(), arr.data());
+    return outArr;
+}
+
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod gInputManagerMethods[] = {
@@ -2640,6 +2658,7 @@
         {"getBluetoothAddress", "(I)Ljava/lang/String;", (void*)nativeGetBluetoothAddress},
         {"setStylusButtonMotionEventsEnabled", "(Z)V",
          (void*)nativeSetStylusButtonMotionEventsEnabled},
+        {"getMouseCursorPosition", "()[F", (void*)nativeGetMouseCursorPosition},
 };
 
 #define FIND_CLASS(var, className) \
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 ed42bb2..3ac10c9 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -19,7 +19,6 @@
 import static com.android.server.credentials.MetricUtilities.logApiCalled;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.content.ComponentName;
 import android.content.Context;
@@ -79,14 +78,14 @@
     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
     // TODO(b/271135048) - Replace this with a new atom per each browsing emit (V4)
-    @Nullable
-    protected List<CandidateBrowsingPhaseMetric> mCandidateBrowsingPhaseMetric;
+    @NonNull
+    protected List<CandidateBrowsingPhaseMetric> mCandidateBrowsingPhaseMetric = new ArrayList<>();
     // As emits occur in sequential order, increment this counter and utilize
     protected int mSequenceCounter = 0;
     protected final String mHybridService;
@@ -124,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,
@@ -171,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) {
@@ -234,6 +245,7 @@
         Log.i(TAG, "In getProviderDataAndInitiateUi providers size: " + mProviders.size());
 
         if (isSessionCancelled()) {
+            MetricUtilities.logApiCalled(mProviders, ++mSequenceCounter);
             finishSession(/*propagateCancellation=*/true);
             return;
         }
@@ -249,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);
         }
     }
 
@@ -265,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 6cd9f1c..a1789b2 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3224,7 +3224,7 @@
         intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
         Bundle options = new BroadcastOptions()
                 .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
-                .setDeferUntilActive(true)
+                .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
                 .toBundle();
         mInjector.binderWithCleanCallingIdentity(() ->
                 mContext.sendBroadcastAsUser(intent, new UserHandle(userHandle), null, options));
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index be2873e..b933508 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1793,6 +1793,10 @@
         }
         t.traceEnd();
 
+        t.traceBegin("StartAppHibernationService");
+        mSystemServiceManager.startService(APP_HIBERNATION_SERVICE_CLASS);
+        t.traceEnd();
+
         t.traceBegin("ArtManagerLocal");
         DexOptHelper.initializeArtManagerLocal(context, mPackageManagerService);
         t.traceEnd();
@@ -2316,10 +2320,6 @@
             mSystemServiceManager.startService(VOICE_RECOGNITION_MANAGER_SERVICE_CLASS);
             t.traceEnd();
 
-            t.traceBegin("StartAppHibernationService");
-            mSystemServiceManager.startService(APP_HIBERNATION_SERVICE_CLASS);
-            t.traceEnd();
-
             if (GestureLauncherService.isGestureLauncherEnabled(context.getResources())) {
                 t.traceBegin("StartGestureLauncher");
                 mSystemServiceManager.startService(GestureLauncherService.class);
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/PackageManagerServiceTests/apks/install_uses_sdk/Android.bp b/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/Android.bp
index 2894395..24e380c 100644
--- a/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/Android.bp
+++ b/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/Android.bp
@@ -32,9 +32,9 @@
 }
 
 android_test_helper_app {
-    name: "FrameworksServicesTests_install_uses_sdk_r1000",
+    name: "FrameworksServicesTests_install_uses_sdk_r10000",
     defaults: ["FrameworksServicesTests_apks_defaults"],
-    manifest: "AndroidManifest-r1000.xml",
+    manifest: "AndroidManifest-r10000.xml",
 }
 
 android_test_helper_app {
@@ -44,9 +44,9 @@
 }
 
 android_test_helper_app {
-    name: "FrameworksServicesTests_install_uses_sdk_r0_s1000",
+    name: "FrameworksServicesTests_install_uses_sdk_r0_s10000",
     defaults: ["FrameworksServicesTests_apks_defaults"],
-    manifest: "AndroidManifest-r0-s1000.xml",
+    manifest: "AndroidManifest-r0-s10000.xml",
 }
 
 android_test_helper_app {
diff --git a/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r0-s1000.xml b/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r0-s10000.xml
similarity index 97%
rename from services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r0-s1000.xml
rename to services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r0-s10000.xml
index 25743b8..383e60a 100644
--- a/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r0-s1000.xml
+++ b/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r0-s10000.xml
@@ -19,7 +19,7 @@
     <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29">
         <!-- This fails because 31 is not version 5 -->
         <extension-sdk android:sdkVersion="30" android:minExtensionVersion="0" />
-        <extension-sdk android:sdkVersion="31" android:minExtensionVersion="1000" />
+        <extension-sdk android:sdkVersion="31" android:minExtensionVersion="10000" />
     </uses-sdk>
 
     <application>
diff --git a/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r1000.xml b/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r10000.xml
similarity index 97%
rename from services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r1000.xml
rename to services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r10000.xml
index 9bf9254..fe7a212 100644
--- a/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r1000.xml
+++ b/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r10000.xml
@@ -18,7 +18,7 @@
 
     <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29">
         <!-- This will fail to install, because minExtensionVersion is not met -->
-        <extension-sdk android:sdkVersion="30" android:minExtensionVersion="1000" />
+        <extension-sdk android:sdkVersion="30" android:minExtensionVersion="10000" />
     </uses-sdk>
 
     <application>
diff --git a/services/tests/PackageManagerServiceTests/server/Android.bp b/services/tests/PackageManagerServiceTests/server/Android.bp
index e711cab..1146271 100644
--- a/services/tests/PackageManagerServiceTests/server/Android.bp
+++ b/services/tests/PackageManagerServiceTests/server/Android.bp
@@ -127,10 +127,10 @@
         ":FrameworksServicesTests_install_uses_sdk_q0",
         ":FrameworksServicesTests_install_uses_sdk_q0_r0",
         ":FrameworksServicesTests_install_uses_sdk_r0",
-        ":FrameworksServicesTests_install_uses_sdk_r1000",
+        ":FrameworksServicesTests_install_uses_sdk_r10000",
         ":FrameworksServicesTests_install_uses_sdk_r_none",
         ":FrameworksServicesTests_install_uses_sdk_r0_s0",
-        ":FrameworksServicesTests_install_uses_sdk_r0_s1000",
+        ":FrameworksServicesTests_install_uses_sdk_r0_s10000",
         ":FrameworksServicesTests_keyset_permdef_sa_unone",
         ":FrameworksServicesTests_keyset_permuse_sa_ua_ub",
         ":FrameworksServicesTests_keyset_permuse_sb_ua_ub",
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
index ebf309f..906cc83 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
@@ -575,10 +575,10 @@
         assertEquals(0, minExtVers.get(31, -1));
 
         Map<Pair<String, Integer>, Integer> appToError = new HashMap<>();
-        appToError.put(Pair.create("install_uses_sdk.apk_r1000", R.raw.install_uses_sdk_r1000),
+        appToError.put(Pair.create("install_uses_sdk.apk_r10000", R.raw.install_uses_sdk_r10000),
                        PackageManager.INSTALL_FAILED_OLDER_SDK);
         appToError.put(
-                Pair.create("install_uses_sdk.apk_r0_s1000", R.raw.install_uses_sdk_r0_s1000),
+                Pair.create("install_uses_sdk.apk_r0_s10000", R.raw.install_uses_sdk_r0_s10000),
                 PackageManager.INSTALL_FAILED_OLDER_SDK);
 
         appToError.put(Pair.create("install_uses_sdk.apk_q0", R.raw.install_uses_sdk_q0),
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/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
index 83441bf..1a75170 100644
--- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
@@ -68,6 +68,7 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Test RescueParty.
@@ -94,6 +95,9 @@
             "persist.device_config.configuration.disable_rescue_party";
     private static final String PROP_DISABLE_FACTORY_RESET_FLAG =
             "persist.device_config.configuration.disable_rescue_party_factory_reset";
+    private static final String PROP_LAST_FACTORY_RESET_TIME_MS = "persist.sys.last_factory_reset";
+
+    private static final int THROTTLING_DURATION_MIN = 10;
 
     private MockitoSession mSession;
     private HashMap<String, String> mSystemSettingsMap;
@@ -459,6 +463,53 @@
     }
 
     @Test
+    public void testThrottlingOnBootFailures() {
+        SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false));
+        long now = System.currentTimeMillis();
+        long beforeTimeout = now - TimeUnit.MINUTES.toMillis(THROTTLING_DURATION_MIN - 1);
+        SystemProperties.set(PROP_LAST_FACTORY_RESET_TIME_MS, Long.toString(beforeTimeout));
+        for (int i = 1; i <= LEVEL_FACTORY_RESET; i++) {
+            noteBoot(i);
+        }
+        assertFalse(RescueParty.isAttemptingFactoryReset());
+    }
+
+    @Test
+    public void testThrottlingOnAppCrash() {
+        SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false));
+        long now = System.currentTimeMillis();
+        long beforeTimeout = now - TimeUnit.MINUTES.toMillis(THROTTLING_DURATION_MIN - 1);
+        SystemProperties.set(PROP_LAST_FACTORY_RESET_TIME_MS, Long.toString(beforeTimeout));
+        for (int i = 0; i <= LEVEL_FACTORY_RESET; i++) {
+            noteAppCrash(i + 1, true);
+        }
+        assertFalse(RescueParty.isAttemptingFactoryReset());
+    }
+
+    @Test
+    public void testNotThrottlingAfterTimeoutOnBootFailures() {
+        SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false));
+        long now = System.currentTimeMillis();
+        long afterTimeout = now - TimeUnit.MINUTES.toMillis(THROTTLING_DURATION_MIN + 1);
+        SystemProperties.set(PROP_LAST_FACTORY_RESET_TIME_MS, Long.toString(afterTimeout));
+        for (int i = 1; i <= LEVEL_FACTORY_RESET; i++) {
+            noteBoot(i);
+        }
+        assertTrue(RescueParty.isAttemptingFactoryReset());
+    }
+    @Test
+    public void testNotThrottlingAfterTimeoutOnAppCrash() {
+        SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false));
+        long now = System.currentTimeMillis();
+        long afterTimeout = now - TimeUnit.MINUTES.toMillis(THROTTLING_DURATION_MIN + 1);
+        SystemProperties.set(PROP_LAST_FACTORY_RESET_TIME_MS, Long.toString(afterTimeout));
+        for (int i = 0; i <= LEVEL_FACTORY_RESET; i++) {
+            noteAppCrash(i + 1, true);
+        }
+        assertTrue(RescueParty.isAttemptingFactoryReset());
+    }
+
+    @Test
     public void testNativeRescuePartyResets() {
         doReturn(true).when(() -> SettingsToPropertiesMapper.isNativeFlagsResetPerformed());
         doReturn(FAKE_RESET_NATIVE_NAMESPACES).when(
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 b395f42..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)});
@@ -1303,7 +1294,8 @@
         final BroadcastOptions actualOptions = new BroadcastOptions(actualOptionsBundle);
         assertEquals(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT,
                 actualOptions.getDeliveryGroupPolicy());
-        assertTrue(actualOptions.isDeferUntilActive());
+        assertEquals(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE,
+                actualOptions.getDeferralPolicy());
     }
 
     @Test
@@ -2180,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)));
@@ -2221,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));
     }
 
@@ -2239,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));
     }
@@ -2403,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);
@@ -2430,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);
@@ -2454,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);
@@ -2469,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;
@@ -2482,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();
@@ -2503,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);
@@ -2528,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);
@@ -2554,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();
@@ -2574,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);
@@ -2600,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);
@@ -2624,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();
@@ -2650,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();
@@ -2700,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);
 
@@ -3025,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);
@@ -3038,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);
 
@@ -3051,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);
 
@@ -3067,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);
@@ -3327,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);
 
@@ -3335,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);
 
@@ -3343,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);
 
@@ -3362,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();
@@ -3509,7 +3515,7 @@
     }
 
     @Test
-    public void hasUseExactAlarmPermission() {
+    public void hasUseExactAlarmInternal() {
         mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, true);
 
         mockUseExactAlarmState(true);
@@ -3520,7 +3526,7 @@
     }
 
     @Test
-    public void hasUseExactAlarmPermissionChangeDisabled() {
+    public void hasUseExactAlarmInternalChangeDisabled() {
         mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, false);
 
         mockUseExactAlarmState(true);
@@ -3531,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/am/ApplicationExitInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
index 9263bff..d56229c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
@@ -989,9 +989,16 @@
     private ProcessRecord makeProcessRecord(int pid, int uid, int packageUid, Integer definingUid,
             int connectionGroup, int procState, long pss, long rss,
             String processName, String packageName) {
+        return makeProcessRecord(pid, uid, packageUid, definingUid, connectionGroup,
+                procState, pss, rss, processName, packageName, mAms);
+    }
+
+    static ProcessRecord makeProcessRecord(int pid, int uid, int packageUid, Integer definingUid,
+            int connectionGroup, int procState, long pss, long rss,
+            String processName, String packageName, ActivityManagerService ams) {
         ApplicationInfo ai = new ApplicationInfo();
         ai.packageName = packageName;
-        ProcessRecord app = new ProcessRecord(mAms, ai, processName, uid);
+        ProcessRecord app = new ProcessRecord(ams, ai, processName, uid);
         app.setPid(pid);
         app.info.uid = packageUid;
         if (definingUid != null) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
index 01e2768..2b6f217 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
@@ -25,6 +25,8 @@
 import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_BACKGROUND_RESTRICTED_ONLY;
 import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE;
 import static com.android.server.am.BroadcastRecord.calculateBlockedUntilTerminalCount;
+import static com.android.server.am.BroadcastRecord.calculateDeferUntilActive;
+import static com.android.server.am.BroadcastRecord.calculateUrgent;
 import static com.android.server.am.BroadcastRecord.isReceiverEquals;
 
 import static org.junit.Assert.assertArrayEquals;
@@ -38,6 +40,7 @@
 import android.app.ActivityManagerInternal;
 import android.app.BackgroundStartPrivileges;
 import android.app.BroadcastOptions;
+import android.content.IIntentReceiver;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ActivityInfo;
@@ -55,6 +58,7 @@
 import com.android.server.am.BroadcastDispatcher.DeferredBootCompletedBroadcastPerUser;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -86,6 +90,15 @@
     private static final String[] PACKAGE_LIST = new String[] {PACKAGE1, PACKAGE2, PACKAGE3,
             PACKAGE4};
 
+    private static final int SYSTEM_UID = android.os.Process.SYSTEM_UID;
+    private static final int APP_UID = android.os.Process.FIRST_APPLICATION_UID;
+
+    private static final BroadcastOptions OPT_DEFAULT = BroadcastOptions.makeBasic();
+    private static final BroadcastOptions OPT_NONE = BroadcastOptions.makeBasic()
+            .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_NONE);
+    private static final BroadcastOptions OPT_UNTIL_ACTIVE = BroadcastOptions.makeBasic()
+            .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE);
+
     @Mock ActivityManagerInternal mActivityManagerInternal;
     @Mock BroadcastQueue mQueue;
     @Mock ProcessRecord mProcess;
@@ -213,6 +226,75 @@
     }
 
     @Test
+    public void testCalculateUrgent() {
+        final Intent intent = new Intent();
+        final Intent intentForeground = new Intent()
+                .setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+
+        assertFalse(calculateUrgent(intent, null));
+        assertTrue(calculateUrgent(intentForeground, null));
+
+        {
+            final BroadcastOptions opts = BroadcastOptions.makeBasic();
+            assertFalse(calculateUrgent(intent, opts));
+        }
+        {
+            final BroadcastOptions opts = BroadcastOptions.makeBasic();
+            opts.setInteractive(true);
+            assertTrue(calculateUrgent(intent, opts));
+        }
+        {
+            final BroadcastOptions opts = BroadcastOptions.makeBasic();
+            opts.setAlarmBroadcast(true);
+            assertTrue(calculateUrgent(intent, opts));
+        }
+    }
+
+    @Test
+    public void testCalculateDeferUntilActive_App() {
+        // Verify non-urgent behavior
+        assertFalse(calculateDeferUntilActive(APP_UID, null, null, false, false));
+        assertFalse(calculateDeferUntilActive(APP_UID, OPT_DEFAULT, null, false, false));
+        assertFalse(calculateDeferUntilActive(APP_UID, OPT_NONE, null, false, false));
+        assertTrue(calculateDeferUntilActive(APP_UID, OPT_UNTIL_ACTIVE, null, false, false));
+
+        // Verify urgent behavior
+        assertFalse(calculateDeferUntilActive(APP_UID, null, null, false, true));
+        assertFalse(calculateDeferUntilActive(APP_UID, OPT_DEFAULT, null, false, true));
+        assertFalse(calculateDeferUntilActive(APP_UID, OPT_NONE, null, false, true));
+        assertTrue(calculateDeferUntilActive(APP_UID, OPT_UNTIL_ACTIVE, null, false, true));
+    }
+
+    @Test
+    public void testCalculateDeferUntilActive_System() {
+        BroadcastRecord.CORE_DEFER_UNTIL_ACTIVE = true;
+
+        // Verify non-urgent behavior
+        assertTrue(calculateDeferUntilActive(SYSTEM_UID, null, null, false, false));
+        assertTrue(calculateDeferUntilActive(SYSTEM_UID, OPT_DEFAULT, null, false, false));
+        assertFalse(calculateDeferUntilActive(SYSTEM_UID, OPT_NONE, null, false, false));
+        assertTrue(calculateDeferUntilActive(SYSTEM_UID, OPT_UNTIL_ACTIVE, null, false, false));
+
+        // Verify urgent behavior
+        assertFalse(calculateDeferUntilActive(SYSTEM_UID, null, null, false, true));
+        assertFalse(calculateDeferUntilActive(SYSTEM_UID, OPT_DEFAULT, null, false, true));
+        assertFalse(calculateDeferUntilActive(SYSTEM_UID, OPT_NONE, null, false, true));
+        assertTrue(calculateDeferUntilActive(SYSTEM_UID, OPT_UNTIL_ACTIVE, null, false, true));
+    }
+
+    @Test
+    public void testCalculateDeferUntilActive_Overrides() {
+        final IIntentReceiver resultTo = new IIntentReceiver.Default();
+
+        // Ordered broadcasts never deferred; requested option is ignored
+        assertFalse(calculateDeferUntilActive(APP_UID, OPT_UNTIL_ACTIVE, null, true, false));
+        assertFalse(calculateDeferUntilActive(APP_UID, OPT_UNTIL_ACTIVE, resultTo, true, false));
+
+        // Unordered with result is always deferred; requested option is ignored
+        assertTrue(calculateDeferUntilActive(APP_UID, OPT_NONE, resultTo, false, false));
+    }
+
+    @Test
     public void testCleanupDisabledPackageReceivers() {
         final int user0 = UserHandle.USER_SYSTEM;
         final int user1 = user0 + 1;
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ServiceTimeoutTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ServiceTimeoutTest.java
new file mode 100644
index 0000000..fd1b068
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ServiceTimeoutTest.java
@@ -0,0 +1,204 @@
+/*
+ * 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.am;
+
+import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
+
+import static com.android.server.am.ApplicationExitInfoTest.makeProcessRecord;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.after;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.IApplicationThread;
+import android.app.usage.UsageStatsManagerInternal;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManagerInternal;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.SystemClock;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.DropBoxManagerInternal;
+import com.android.server.LocalServices;
+import com.android.server.am.ActivityManagerService.Injector;
+import com.android.server.am.ApplicationExitInfoTest.ServiceThreadRule;
+import com.android.server.appop.AppOpsService;
+import com.android.server.wm.ActivityTaskManagerService;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+
+/**
+ * Test class for the service timeout.
+ *
+ * Build/Install/Run:
+ *  atest ServiceTimeoutTest
+ */
+@Presubmit
+public final class ServiceTimeoutTest {
+    private static final String TAG = ServiceTimeoutTest.class.getSimpleName();
+    private static final long DEFAULT_SERVICE_TIMEOUT = 2000;
+
+    @Rule
+    public final ServiceThreadRule mServiceThreadRule = new ServiceThreadRule();
+    private Context mContext;
+    private HandlerThread mHandlerThread;
+
+    @Mock
+    private AppOpsService mAppOpsService;
+    @Mock
+    private DropBoxManagerInternal mDropBoxManagerInt;
+    @Mock
+    private PackageManagerInternal mPackageManagerInt;
+    @Mock
+    private UsageStatsManagerInternal mUsageStatsManagerInt;
+
+    private ActivityManagerService mAms;
+    private ProcessList mProcessList;
+    private ActiveServices mActiveServices;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+        mHandlerThread = new HandlerThread(TAG);
+        mHandlerThread.start();
+        mProcessList = spy(new ProcessList());
+
+        LocalServices.removeServiceForTest(DropBoxManagerInternal.class);
+        LocalServices.addService(DropBoxManagerInternal.class, mDropBoxManagerInt);
+        LocalServices.removeServiceForTest(PackageManagerInternal.class);
+        LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt);
+        doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent();
+
+        final ActivityManagerService realAms = new ActivityManagerService(
+                new TestInjector(mContext), mServiceThreadRule.getThread());
+        realAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
+        realAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
+        realAms.mAtmInternal = spy(realAms.mActivityTaskManager.getAtmInternal());
+        realAms.mOomAdjuster.mCachedAppOptimizer = spy(realAms.mOomAdjuster.mCachedAppOptimizer);
+        realAms.mPackageManagerInt = mPackageManagerInt;
+        realAms.mUsageStatsService = mUsageStatsManagerInt;
+        realAms.mProcessesReady = true;
+        realAms.mConstants.SERVICE_TIMEOUT = DEFAULT_SERVICE_TIMEOUT;
+        realAms.mConstants.SERVICE_BACKGROUND_TIMEOUT = DEFAULT_SERVICE_TIMEOUT;
+        mAms = spy(realAms);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        LocalServices.removeServiceForTest(DropBoxManagerInternal.class);
+        LocalServices.removeServiceForTest(PackageManagerInternal.class);
+        mHandlerThread.quit();
+    }
+
+    @SuppressWarnings("GuardedBy")
+    @Test
+    public void testServiceTimeoutAndProcessKill() throws Exception {
+        final int pid = 12345;
+        final int uid = 10123;
+        final String name = "com.example.foo";
+        final ProcessRecord app = makeProcessRecord(
+                pid,                   // pid
+                uid,                   // uid
+                uid,                   // packageUid
+                null,                  // definingUid
+                0,                     // connectionGroup
+                PROCESS_STATE_SERVICE, // procstate
+                0,                     // pss
+                0,                     // rss
+                name,                  // processName
+                name,                  // packageName
+                mAms);
+        app.makeActive(mock(IApplicationThread.class), mAms.mProcessStats);
+        mProcessList.updateLruProcessLocked(app, false, null);
+
+        final long now = SystemClock.uptimeMillis();
+        final ServiceRecord sr = spy(ServiceRecord.newEmptyInstanceForTest(mAms));
+        doNothing().when(sr).dump(any(), anyString());
+        sr.startRequested = true;
+        sr.executingStart = now;
+
+        app.mServices.startExecutingService(sr);
+        mActiveServices.scheduleServiceTimeoutLocked(app);
+
+        verify(mActiveServices, timeout(DEFAULT_SERVICE_TIMEOUT * 2).times(1))
+                .serviceTimeout(eq(app));
+
+        clearInvocations(mActiveServices);
+
+        app.mServices.startExecutingService(sr);
+        mActiveServices.scheduleServiceTimeoutLocked(app);
+
+        app.killLocked(TAG, 42, false);
+        mAms.removeLruProcessLocked(app);
+
+        verify(mActiveServices, after(DEFAULT_SERVICE_TIMEOUT * 4)
+                .times(1)).serviceTimeout(eq(app));
+    }
+
+    private class TestInjector extends Injector {
+        TestInjector(Context context) {
+            super(context);
+        }
+
+        @Override
+        public AppOpsService getAppOpsService(File recentAccessesFile, File storageFile,
+                Handler handler) {
+            return mAppOpsService;
+        }
+
+        @Override
+        public Handler getUiHandler(ActivityManagerService service) {
+            return mHandlerThread.getThreadHandler();
+        }
+
+        @Override
+        public ProcessList getProcessList(ActivityManagerService service) {
+            return mProcessList;
+        }
+
+        @Override
+        public ActiveServices getActiveServices(ActivityManagerService service) {
+            if (mActiveServices == null) {
+                mActiveServices = spy(new ActiveServices(service));
+            }
+            return mActiveServices;
+        }
+    }
+}
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/pm/UserVisibilityMediatorTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
index 239b6fd..70b5ac0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
@@ -596,6 +596,7 @@
                 .that(mMediator.assignUserToExtraDisplay(userId, displayId))
                 .isTrue();
         expectUserIsVisibleOnDisplay(userId, displayId);
+        expectDisplaysAssignedToUserContainsDisplayId(userId, displayId);
 
         if (unassign) {
             Log.d(TAG, "Calling unassignUserFromExtraDisplay(" + userId + ", " + displayId + ")");
@@ -603,6 +604,7 @@
                     .that(mMediator.unassignUserFromExtraDisplay(userId, displayId))
                     .isTrue();
             expectUserIsNotVisibleOnDisplay(userId, displayId);
+            expectDisplaysAssignedToUserDoesNotContainDisplayId(userId, displayId);
         }
     }
 
@@ -668,6 +670,7 @@
         expectUserIsNotVisibleOnDisplay(userId, INVALID_DISPLAY);
         expectUserIsNotVisibleOnDisplay(userId, SECONDARY_DISPLAY_ID);
         expectUserIsNotVisibleOnDisplay(userId, OTHER_SECONDARY_DISPLAY_ID);
+        expectDisplaysAssignedToUserIsEmpty(userId);
     }
 
     protected void expectDisplayAssignedToUser(@UserIdInt int userId, int displayId) {
@@ -680,6 +683,24 @@
                 .that(mMediator.getDisplayAssignedToUser(userId)).isEqualTo(INVALID_DISPLAY);
     }
 
+    protected void expectDisplaysAssignedToUserContainsDisplayId(
+            @UserIdInt int userId, int displayId) {
+        expectWithMessage("getDisplaysAssignedToUser(%s)", userId)
+                .that(mMediator.getDisplaysAssignedToUser(userId)).asList().contains(displayId);
+    }
+
+    protected void expectDisplaysAssignedToUserDoesNotContainDisplayId(
+            @UserIdInt int userId, int displayId) {
+        expectWithMessage("getDisplaysAssignedToUser(%s)", userId)
+                .that(mMediator.getDisplaysAssignedToUser(userId)).asList()
+                .doesNotContain(displayId);
+    }
+
+    protected void expectDisplaysAssignedToUserIsEmpty(@UserIdInt int userId) {
+        expectWithMessage("getDisplaysAssignedToUser(%s)", userId)
+                .that(mMediator.getDisplaysAssignedToUser(userId)).isNull();
+    }
+
     protected void expectUserCannotBeUnassignedFromDisplay(@UserIdInt int userId, int displayId) {
         expectWithMessage("unassignUserFromExtraDisplay(%s, %s)", userId, displayId)
                 .that(mMediator.unassignUserFromExtraDisplay(userId, displayId)).isFalse();
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/companion/virtual/InputControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
index 7642e7b..6c6b608 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
@@ -28,7 +28,7 @@
 
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.input.IInputManager;
-import android.hardware.input.InputManager;
+import android.hardware.input.InputManagerGlobal;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
@@ -109,7 +109,7 @@
                 device1Id).isNotEqualTo(device2Id);
 
 
-        int[] deviceIds = InputManager.getInstance().getInputDeviceIds();
+        int[] deviceIds = InputManagerGlobal.getInstance().getInputDeviceIds();
         assertWithMessage("InputManager's deviceIds list should contain id of device 1").that(
                 deviceIds).asList().contains(device1Id);
         assertWithMessage("InputManager's deviceIds list should contain id of device 2").that(
@@ -153,7 +153,7 @@
                 deviceToken, /* displayId= */ 1, /* touchpadHeight= */ 50, /* touchpadWidth= */ 50);
 
         int deviceId = mInputController.getInputDeviceId(deviceToken);
-        int[] deviceIds = InputManager.getInstance().getInputDeviceIds();
+        int[] deviceIds = InputManagerGlobal.getInstance().getInputDeviceIds();
 
         assertWithMessage("InputManager's deviceIds list should contain id of the device").that(
             deviceIds).asList().contains(deviceId);
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/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java b/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java
index 6edef75..07b4345 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java
@@ -34,6 +34,7 @@
 import android.hardware.input.IInputDevicesChangedListener;
 import android.hardware.input.IInputManager;
 import android.hardware.input.InputManager;
+import android.hardware.input.InputManagerGlobal;
 import android.os.CombinedVibration;
 import android.os.Handler;
 import android.os.Process;
@@ -82,8 +83,8 @@
     @Before
     public void setUp() throws Exception {
         mTestLooper = new TestLooper();
-        mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
         InputManager inputManager = InputManager.resetInstance(mIInputManagerMock);
+        mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
 
         when(mContextSpy.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager);
         doAnswer(invocation -> mIInputDevicesChangedListener = invocation.getArgument(0))
@@ -314,7 +315,7 @@
             deviceIdsAndGenerations[i + 1] = 2; // update by increasing it's generation to 2.
         }
         // Force initialization of mIInputDevicesChangedListener, if it still haven't
-        InputManager.getInstance().getInputDeviceIds();
+        InputManagerGlobal.getInstance().getInputDeviceIds();
         mIInputDevicesChangedListener.onInputDevicesChanged(deviceIdsAndGenerations);
         // Makes sure all callbacks from InputDeviceDelegate are executed.
         mTestLooper.dispatchAll();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java
new file mode 100644
index 0000000..b94ed01
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java
@@ -0,0 +1,134 @@
+/*
+ * 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.notification;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import android.content.ComponentName;
+import android.content.ServiceConnection;
+import android.content.pm.IPackageManager;
+import android.net.Uri;
+import android.os.IInterface;
+import android.service.notification.Condition;
+
+import com.android.server.UiServiceTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class ConditionProvidersTest extends UiServiceTestCase {
+
+    private ConditionProviders mProviders;
+
+    @Mock
+    private IPackageManager mIpm;
+    @Mock
+    private ManagedServices.UserProfiles mUserProfiles;
+    @Mock
+    private ConditionProviders.Callback mCallback;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mProviders = new ConditionProviders(mContext, mUserProfiles, mIpm);
+        mProviders.setCallback(mCallback);
+    }
+
+    @Test
+    public void notifyConditions_findCondition() {
+        ComponentName cn = new ComponentName("package", "cls");
+        ManagedServices.ManagedServiceInfo msi = mProviders.new ManagedServiceInfo(
+                mock(IInterface.class), cn, 0, false, mock(ServiceConnection.class), 33, 100);
+        Condition[] conditions = new Condition[] {
+                new Condition(Uri.parse("a"), "summary", Condition.STATE_TRUE),
+                new Condition(Uri.parse("b"), "summary2", Condition.STATE_TRUE)
+        };
+
+        mProviders.notifyConditions("package", msi, conditions);
+
+        assertThat(mProviders.findCondition(cn, Uri.parse("a"))).isEqualTo(conditions[0]);
+        assertThat(mProviders.findCondition(cn, Uri.parse("b"))).isEqualTo(conditions[1]);
+        assertThat(mProviders.findCondition(null, Uri.parse("a"))).isNull();
+        assertThat(mProviders.findCondition(cn, null)).isNull();
+    }
+
+    @Test
+    public void notifyConditions_callbackOnConditionChanged() {
+        ManagedServices.ManagedServiceInfo msi = mProviders.new ManagedServiceInfo(
+                mock(IInterface.class), new ComponentName("package", "cls"), 0, false,
+                mock(ServiceConnection.class), 33, 100);
+        Condition[] conditionsToNotify = new Condition[] {
+                new Condition(Uri.parse("a"), "summary", Condition.STATE_TRUE),
+                new Condition(Uri.parse("b"), "summary2", Condition.STATE_TRUE),
+                new Condition(Uri.parse("c"), "summary3", Condition.STATE_TRUE)
+        };
+
+        mProviders.notifyConditions("package", msi, conditionsToNotify);
+
+        verify(mCallback).onConditionChanged(eq(Uri.parse("a")), eq(conditionsToNotify[0]));
+        verify(mCallback).onConditionChanged(eq(Uri.parse("b")), eq(conditionsToNotify[1]));
+        verify(mCallback).onConditionChanged(eq(Uri.parse("c")), eq(conditionsToNotify[2]));
+        verifyNoMoreInteractions(mCallback);
+    }
+
+    @Test
+    public void notifyConditions_duplicateIds_ignored() {
+        ManagedServices.ManagedServiceInfo msi = mProviders.new ManagedServiceInfo(
+                mock(IInterface.class), new ComponentName("package", "cls"), 0, false,
+                mock(ServiceConnection.class), 33, 100);
+        Condition[] conditionsToNotify = new Condition[] {
+                new Condition(Uri.parse("a"), "summary", Condition.STATE_TRUE),
+                new Condition(Uri.parse("b"), "summary2", Condition.STATE_TRUE),
+                new Condition(Uri.parse("a"), "summary3", Condition.STATE_FALSE),
+                new Condition(Uri.parse("a"), "summary4", Condition.STATE_FALSE)
+        };
+
+        mProviders.notifyConditions("package", msi, conditionsToNotify);
+
+        verify(mCallback).onConditionChanged(eq(Uri.parse("a")), eq(conditionsToNotify[0]));
+        verify(mCallback).onConditionChanged(eq(Uri.parse("b")), eq(conditionsToNotify[1]));
+
+        verifyNoMoreInteractions(mCallback);
+    }
+
+    @Test
+    public void notifyConditions_nullItems_ignored() {
+        ManagedServices.ManagedServiceInfo msi = mProviders.new ManagedServiceInfo(
+                mock(IInterface.class), new ComponentName("package", "cls"), 0, false,
+                mock(ServiceConnection.class), 33, 100);
+        Condition[] conditionsToNotify = new Condition[] {
+                new Condition(Uri.parse("a"), "summary", Condition.STATE_TRUE),
+                null,
+                null,
+                new Condition(Uri.parse("b"), "summary", Condition.STATE_TRUE)
+        };
+
+        mProviders.notifyConditions("package", msi, conditionsToNotify);
+
+        verify(mCallback).onConditionChanged(eq(Uri.parse("a")), eq(conditionsToNotify[0]));
+        verify(mCallback).onConditionChanged(eq(Uri.parse("b")), eq(conditionsToNotify[3]));
+        verifyNoMoreInteractions(mCallback);
+    }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 41a9504..5cbd120 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -10441,6 +10441,31 @@
     }
 
     @Test
+    public void fixCallNotification_withOnGoingFlag_shouldNotBeNonDismissible()
+            throws Exception {
+        // Given: a call notification has the flag FLAG_ONGOING_EVENT set
+        // feature flag: ALLOW_DISMISS_ONGOING is on
+        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
+        when(mTelecomManager.isInManagedCall()).thenReturn(true);
+
+        Person person = new Person.Builder()
+                .setName("caller")
+                .build();
+        Notification n = new Notification.Builder(mContext, "test")
+                .setOngoing(true)
+                .setStyle(Notification.CallStyle.forOngoingCall(
+                        person, mock(PendingIntent.class)))
+                .build();
+
+        // When: fix the notification with NotificationManagerService
+        mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE);
+
+        // Then: the notification's flag FLAG_NO_DISMISS should be set
+        assertNotSame(0, n.flags & Notification.FLAG_NO_DISMISS);
+    }
+
+
+    @Test
     public void fixNonExemptNotification_withOnGoingFlag_shouldBeDismissible() throws Exception {
         // Given: a non-exempt notification has the flag FLAG_ONGOING_EVENT set
         // feature flag: ALLOW_DISMISS_ONGOING is on
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java
index d72cfc7..0564a73 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java
@@ -15,6 +15,8 @@
  */
 package com.android.server.notification;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -51,6 +53,8 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.UiServiceTestCase;
+import com.android.server.notification.ValidateNotificationPeople.LookupResult;
+import com.android.server.notification.ValidateNotificationPeople.PeopleRankingReconsideration;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -60,6 +64,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -215,7 +220,7 @@
                 ContactsContract.Contacts.CONTENT_LOOKUP_URI,
                 ContactsContract.Contacts.ENTERPRISE_CONTACT_LOOKUP_PREFIX + contactId);
 
-        new ValidateNotificationPeople().searchContacts(mockContext, lookupUri);
+        PeopleRankingReconsideration.searchContacts(mockContext, lookupUri);
 
         ArgumentCaptor<Uri> queryUri = ArgumentCaptor.forClass(Uri.class);
         verify(mockContentResolver).query(
@@ -242,7 +247,7 @@
         final Uri lookupUri = Uri.withAppendedPath(
                 ContactsContract.Contacts.CONTENT_LOOKUP_URI, String.valueOf(contactId));
 
-        new ValidateNotificationPeople().searchContacts(mockContext, lookupUri);
+        PeopleRankingReconsideration.searchContacts(mockContext, lookupUri);
 
         ArgumentCaptor<Uri> queryUri = ArgumentCaptor.forClass(Uri.class);
         verify(mockContentResolver).query(
@@ -277,7 +282,7 @@
 
         // call searchContacts and then mergePhoneNumbers, make sure we never actually
         // query the content resolver for a phone number
-        new ValidateNotificationPeople().searchContactsAndLookupNumbers(mockContext, lookupUri);
+        PeopleRankingReconsideration.searchContactsAndLookupNumbers(mockContext, lookupUri);
         verify(mockContentResolver, never()).query(
                 eq(ContactsContract.CommonDataKinds.Phone.CONTENT_URI),
                 eq(ValidateNotificationPeople.PHONE_LOOKUP_PROJECTION),
@@ -320,7 +325,7 @@
 
         // call searchContacts and then mergePhoneNumbers, and check that we query
         // once for the
-        new ValidateNotificationPeople().searchContactsAndLookupNumbers(mockContext, lookupUri);
+        PeopleRankingReconsideration.searchContactsAndLookupNumbers(mockContext, lookupUri);
         verify(mockContentResolver, times(1)).query(
                 eq(ContactsContract.CommonDataKinds.Phone.CONTENT_URI),
                 eq(ValidateNotificationPeople.PHONE_LOOKUP_PROJECTION),
@@ -339,7 +344,7 @@
 
         // Create validator with empty cache
         ValidateNotificationPeople vnp = new ValidateNotificationPeople();
-        LruCache cache = new LruCache<String, ValidateNotificationPeople.LookupResult>(5);
+        LruCache<String, LookupResult> cache = new LruCache<>(5);
         vnp.initForTests(mockContext, mockNotificationUsageStats, cache);
 
         NotificationRecord record = getNotificationRecord();
@@ -366,9 +371,8 @@
         float affinity = 0.7f;
 
         // Create a fake LookupResult for the data we'll pass in
-        LruCache cache = new LruCache<String, ValidateNotificationPeople.LookupResult>(5);
-        ValidateNotificationPeople.LookupResult lr =
-                mock(ValidateNotificationPeople.LookupResult.class);
+        LruCache<String, LookupResult> cache = new LruCache<>(5);
+        LookupResult lr = mock(LookupResult.class);
         when(lr.getAffinity()).thenReturn(affinity);
         when(lr.getPhoneNumbers()).thenReturn(new ArraySet<>(new String[]{lookupTel}));
         when(lr.isExpired()).thenReturn(false);
@@ -392,6 +396,23 @@
         assertTrue(record.getPhoneNumbers().contains(lookupTel));
     }
 
+    @Test
+    public void validatePeople_reconsiderationWillNotBeDelayed() {
+        final Context mockContext = mock(Context.class);
+        final ContentResolver mockContentResolver = mock(ContentResolver.class);
+        when(mockContext.getContentResolver()).thenReturn(mockContentResolver);
+        ValidateNotificationPeople vnp = new ValidateNotificationPeople();
+        vnp.initForTests(mockContext, mock(NotificationUsageStats.class), new LruCache<>(5));
+        NotificationRecord record = getNotificationRecord();
+        String[] callNumber = new String[]{"tel:12345678910"};
+        setNotificationPeople(record, callNumber);
+
+        RankingReconsideration rr = vnp.validatePeople(mockContext, record);
+
+        assertThat(rr).isNotNull();
+        assertThat(rr.getDelay(TimeUnit.MILLISECONDS)).isEqualTo(0);
+    }
+
     // Creates a cursor that points to one item of Contacts data with the specified
     // columns.
     private Cursor makeMockCursor(int id, String lookupKey, int starred, int hasPhone) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 06b6ed8..da078a2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -459,8 +459,17 @@
         mainWindow.mInvGlobalScale = invGlobalScale;
         mLetterboxConfiguration.setLetterboxActivityCornersRadius(configurationRadius);
 
+        doReturn(true).when(mActivity).isInLetterboxAnimation();
         assertEquals(expectedRadius, mController.getRoundedCornersRadius(mainWindow));
 
+        doReturn(false).when(mActivity).isInLetterboxAnimation();
+        assertEquals(expectedRadius, mController.getRoundedCornersRadius(mainWindow));
+
+        doReturn(false).when(mainWindow).isOnScreen();
+        assertEquals(0, mController.getRoundedCornersRadius(mainWindow));
+
+        doReturn(true).when(mActivity).isInLetterboxAnimation();
+        assertEquals(expectedRadius, mController.getRoundedCornersRadius(mainWindow));
     }
 
     @Test
@@ -495,6 +504,7 @@
             insets.addSource(taskbar);
         }
         doReturn(mLetterboxedPortraitTaskBounds).when(mActivity).getBounds();
+        doReturn(false).when(mActivity).isInLetterboxAnimation();
         doReturn(true).when(mActivity).isVisible();
         doReturn(true).when(mActivity).isLetterboxedForFixedOrientationAndAspectRatio();
         doReturn(insets).when(mainWindow).getInsetsState();
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 753cc62..2cc46a9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -51,6 +51,7 @@
 import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE;
 import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED;
 import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE;
+import static com.android.server.wm.ActivityRecord.State.PAUSED;
 import static com.android.server.wm.ActivityRecord.State.RESTARTING_PROCESS;
 import static com.android.server.wm.ActivityRecord.State.RESUMED;
 import static com.android.server.wm.ActivityRecord.State.STOPPED;
@@ -3918,6 +3919,24 @@
         assertTrue(mActivity.inSizeCompatMode());
     }
 
+    @Test
+    public void testTopActivityInSizeCompatMode_pausedAndInSizeCompatMode_returnsTrue() {
+        setUpDisplaySizeWithApp(1000, 2500);
+        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+
+        spyOn(mActivity);
+        doReturn(mTask).when(mActivity).getOrganizedTask();
+        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+
+        rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
+        mActivity.setState(PAUSED, "test");
+
+        assertTrue(mActivity.inSizeCompatMode());
+        assertEquals(mActivity.getState(), PAUSED);
+        assertTrue(mActivity.isVisible());
+        assertTrue(mTask.getTaskInfo().topActivityInSizeCompat);
+    }
+
     /**
      * Tests that all three paths in which aspect ratio logic can be applied yield the same
      * result, which is that aspect ratio is respected on app bounds. The three paths are
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 3045812..616d528 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -40,6 +40,7 @@
 import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
 import static android.window.TransitionInfo.isIndependent;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -54,6 +55,7 @@
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
@@ -1391,7 +1393,7 @@
 
         verify(snapshotController, times(1)).recordSnapshot(eq(task2), eq(false));
 
-        openTransition.finishTransition();
+        controller.finishTransition(openTransition);
 
         // We are now going to simulate closing task1 to return back to (open) task2.
         final Transition closeTransition = controller.createTransition(TRANSIT_CLOSE);
@@ -1400,7 +1402,13 @@
         closeTransition.collectExistenceChange(activity1);
         closeTransition.collectExistenceChange(task2);
         closeTransition.collectExistenceChange(activity2);
-        closeTransition.setTransientLaunch(activity2, null /* restoreBelow */);
+        closeTransition.setTransientLaunch(activity2, task1);
+        final Transition.ChangeInfo task1ChangeInfo = closeTransition.mChanges.get(task1);
+        assertNotNull(task1ChangeInfo);
+        assertTrue(task1ChangeInfo.hasChanged());
+        final Transition.ChangeInfo activity1ChangeInfo = closeTransition.mChanges.get(activity1);
+        assertNotNull(activity1ChangeInfo);
+        assertTrue(activity1ChangeInfo.hasChanged());
 
         activity1.setVisibleRequested(false);
         activity2.setVisibleRequested(true);
@@ -1416,9 +1424,27 @@
         verify(snapshotController, times(0)).recordSnapshot(eq(task1), eq(false));
 
         enteringAnimReports.clear();
-        closeTransition.finishTransition();
+        doCallRealMethod().when(mWm.mRoot).ensureActivitiesVisible(any(),
+                anyInt(), anyBoolean(), anyBoolean());
+        final boolean[] wasInFinishingTransition = { false };
+        controller.registerLegacyListener(new WindowManagerInternal.AppTransitionListener() {
+            @Override
+            public void onAppTransitionFinishedLocked(IBinder token) {
+                final ActivityRecord r = ActivityRecord.forToken(token);
+                if (r != null) {
+                    wasInFinishingTransition[0] = controller.inFinishingTransition(r);
+                }
+            }
+        });
+        controller.finishTransition(closeTransition);
+        assertTrue(wasInFinishingTransition[0]);
+        assertNull(controller.mFinishingTransition);
 
+        assertTrue(activity2.isVisible());
         assertEquals(ActivityTaskManagerService.APP_SWITCH_DISALLOW, mAtm.getBalAppSwitchesState());
+        // Because task1 is occluded by task2, finishTransition should make activity1 invisible.
+        assertFalse(activity1.isVisibleRequested());
+        assertFalse(activity1.isVisible());
         assertFalse(activity1.app.hasActivityInVisibleTask());
 
         verify(snapshotController, times(1)).recordSnapshot(eq(task1), eq(false));
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 65631ea..984b868 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -396,7 +396,7 @@
         final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
         token.finishSync(t, false /* cancel */);
         transit.onTransactionReady(transit.getSyncId(), t);
-        dc.mTransitionController.finishTransition(transit.getToken());
+        dc.mTransitionController.finishTransition(transit);
         assertFalse(wallpaperWindow.isVisible());
         assertFalse(token.isVisible());
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index a68a573..17ad4e3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -1486,9 +1486,9 @@
         assertEquals(rootTask.mTaskId, info.taskId);
         assertTrue(info.topActivityInSizeCompat);
 
-        // Ensure task info show top activity that is not in foreground as not in size compat.
+        // Ensure task info show top activity that is not visible as not in size compat.
         clearInvocations(organizer);
-        doReturn(false).when(activity).isState(RESUMED);
+        doReturn(false).when(activity).isVisible();
         rootTask.onSizeCompatActivityChanged();
         mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
         verify(organizer).onTaskInfoChanged(infoCaptor.capture());
@@ -1498,7 +1498,7 @@
 
         // Ensure task info show non size compat top activity as not in size compat.
         clearInvocations(organizer);
-        doReturn(true).when(activity).isState(RESUMED);
+        doReturn(true).when(activity).isVisible();
         doReturn(false).when(activity).inSizeCompatMode();
         rootTask.onSizeCompatActivityChanged();
         mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerMapTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerMapTests.java
index c2ee079..2a3c9bc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerMapTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerMapTests.java
@@ -22,6 +22,8 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
 
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
@@ -129,4 +131,14 @@
         assertEquals(uid2processes.size(), 1);
         assertEquals(mProcessMap.getProcess(FAKE_PID1), pid1uid2);
     }
+
+    @Test
+    public void testRemove_callsDestroy() {
+        var proc = spy(pid1uid1);
+        mProcessMap.put(FAKE_PID1, proc);
+
+        mProcessMap.remove(FAKE_PID1);
+
+        verify(proc).destroy();
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index d6cfd00..cf83981 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -159,6 +159,17 @@
     }
 
     @Test
+    public void testDestroy_unregistersDisplayAreaListener() {
+        final TestDisplayContent testDisplayContent1 = createTestDisplayContentInContainer();
+        final DisplayArea imeContainer1 = testDisplayContent1.getImeContainer();
+        mWpc.registerDisplayAreaConfigurationListener(imeContainer1);
+
+        mWpc.destroy();
+
+        assertNull(mWpc.getDisplayArea());
+    }
+
+    @Test
     public void testSetRunningRecentsAnimation() {
         mWpc.setRunningRecentsAnimation(true);
         mWpc.setRunningRecentsAnimation(false);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index ce6cd90..b80500a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -1799,7 +1799,7 @@
         }
 
         public void finish() {
-            mController.finishTransition(mLastTransit.getToken());
+            mController.finishTransition(mLastTransit);
         }
     }
 }
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index bf12b9c..212dc41 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1628,6 +1628,7 @@
      * <li>  9: WiFi Calling</li>
      * <li> 10: VoWifi</li>
      * <li> 11: %s WiFi Calling</li>
+     * <li> 12: WiFi Call</li>
      * @hide
      */
     public static final String KEY_WFC_SPN_FORMAT_IDX_INT = "wfc_spn_format_idx_int";
@@ -1974,8 +1975,13 @@
     /**
      * Boolean indicating if LTE+ icon should be shown if available.
      */
-    public static final String KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL =
-            "hide_lte_plus_data_icon_bool";
+    public static final String KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL = "hide_lte_plus_data_icon_bool";
+
+    /**
+     * Boolean indicting if the 5G slice icon should be shown if available.
+     * @hide
+     */
+    public static final String KEY_SHOW_5G_SLICE_ICON_BOOL = "show_5g_slice_icon_bool";
 
     /**
      * The combined channel bandwidth threshold (non-inclusive) in KHz required to display the
@@ -9913,6 +9919,7 @@
         sDefaults.putString(KEY_OPERATOR_NAME_FILTER_PATTERN_STRING, "");
         sDefaults.putString(KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING, "");
         sDefaults.putBoolean(KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL, true);
+        sDefaults.putBoolean(KEY_SHOW_5G_SLICE_ICON_BOOL, true);
         sDefaults.putInt(KEY_LTE_PLUS_THRESHOLD_BANDWIDTH_KHZ_INT, 20000);
         sDefaults.putInt(KEY_NR_ADVANCED_THRESHOLD_BANDWIDTH_KHZ_INT, 0);
         sDefaults.putBoolean(KEY_INCLUDE_LTE_FOR_NR_ADVANCED_THRESHOLD_BANDWIDTH_BOOL, false);
diff --git a/telephony/java/android/telephony/data/QosBearerSession.java b/telephony/java/android/telephony/data/QosBearerSession.java
index dd08085..1668193 100644
--- a/telephony/java/android/telephony/data/QosBearerSession.java
+++ b/telephony/java/android/telephony/data/QosBearerSession.java
@@ -102,12 +102,11 @@
 
         QosBearerSession other = (QosBearerSession) o;
         return this.qosBearerSessionId == other.qosBearerSessionId
-                && this.qos.equals(other.qos)
+                && Objects.equals(this.qos, other.qos)
                 && this.qosBearerFilterList.size() == other.qosBearerFilterList.size()
                 && this.qosBearerFilterList.containsAll(other.qosBearerFilterList);
     }
 
-
     public static final @NonNull Parcelable.Creator<QosBearerSession> CREATOR =
             new Parcelable.Creator<QosBearerSession>() {
                 @Override
diff --git a/telephony/java/android/telephony/satellite/ISatelliteStateCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteStateCallback.aidl
index 98221c9..cd9d81e 100644
--- a/telephony/java/android/telephony/satellite/ISatelliteStateCallback.aidl
+++ b/telephony/java/android/telephony/satellite/ISatelliteStateCallback.aidl
@@ -22,13 +22,6 @@
  */
 oneway interface ISatelliteStateCallback {
     /**
-     * Indicates that the satellite has pending datagrams for the device to be pulled.
-     *
-     * @param count Number of pending datagrams.
-     */
-    void onPendingDatagramCount(in int count);
-
-    /**
      * Indicates that the satellite modem state has changed.
      *
      * @param state The current satellite modem state.
diff --git a/telephony/java/android/telephony/satellite/ISatellitePositionUpdateCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl
similarity index 96%
rename from telephony/java/android/telephony/satellite/ISatellitePositionUpdateCallback.aidl
rename to telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl
index d3f1091..2442083 100644
--- a/telephony/java/android/telephony/satellite/ISatellitePositionUpdateCallback.aidl
+++ b/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl
@@ -22,7 +22,7 @@
  * Interface for position update and datagram transfer state change callback.
  * @hide
  */
-oneway interface ISatellitePositionUpdateCallback {
+oneway interface ISatelliteTransmissionUpdateCallback {
     /**
      * Called when satellite datagram transfer state changed.
      *
diff --git a/telephony/java/android/telephony/satellite/PointingInfo.java b/telephony/java/android/telephony/satellite/PointingInfo.java
index a3c3f19..7c794473 100644
--- a/telephony/java/android/telephony/satellite/PointingInfo.java
+++ b/telephony/java/android/telephony/satellite/PointingInfo.java
@@ -30,31 +30,12 @@
     /** Satellite elevation in degrees */
     private float mSatelliteElevationDegrees;
 
-    /** Antenna azimuth in degrees */
-    private float mAntennaAzimuthDegrees;
-
-    /**
-     * Angle of rotation about the x axis. This value represents the angle between a plane
-     * parallel to the device's screen and a plane parallel to the ground.
-     */
-    private float mAntennaPitchDegrees;
-
-    /**
-     * Angle of rotation about the y axis. This value represents the angle between a plane
-     * perpendicular to the device's screen and a plane parallel to the ground.
-     */
-    private float mAntennaRollDegrees;
-
     /**
      * @hide
      */
-    public PointingInfo(float satelliteAzimuthDegrees, float satelliteElevationDegrees,
-            float antennaAzimuthDegrees, float antennaPitchDegrees, float antennaRollDegrees) {
+    public PointingInfo(float satelliteAzimuthDegrees, float satelliteElevationDegrees) {
         mSatelliteAzimuthDegrees = satelliteAzimuthDegrees;
         mSatelliteElevationDegrees = satelliteElevationDegrees;
-        mAntennaAzimuthDegrees = antennaAzimuthDegrees;
-        mAntennaPitchDegrees = antennaPitchDegrees;
-        mAntennaRollDegrees = antennaRollDegrees;
     }
 
     private PointingInfo(Parcel in) {
@@ -70,9 +51,6 @@
     public void writeToParcel(@NonNull Parcel out, int flags) {
         out.writeFloat(mSatelliteAzimuthDegrees);
         out.writeFloat(mSatelliteElevationDegrees);
-        out.writeFloat(mAntennaAzimuthDegrees);
-        out.writeFloat(mAntennaPitchDegrees);
-        out.writeFloat(mAntennaRollDegrees);
     }
 
     public static final @android.annotation.NonNull Creator<PointingInfo> CREATOR =
@@ -99,18 +77,6 @@
 
         sb.append("SatelliteElevationDegrees:");
         sb.append(mSatelliteElevationDegrees);
-        sb.append(",");
-
-        sb.append("AntennaAzimuthDegrees:");
-        sb.append(mAntennaAzimuthDegrees);
-        sb.append(",");
-
-        sb.append("AntennaPitchDegrees:");
-        sb.append(mAntennaPitchDegrees);
-        sb.append(",");
-
-        sb.append("AntennaRollDegrees:");
-        sb.append(mAntennaRollDegrees);
         return sb.toString();
     }
 
@@ -122,23 +88,8 @@
         return mSatelliteElevationDegrees;
     }
 
-    public float getAntennaAzimuthDegrees() {
-        return mAntennaAzimuthDegrees;
-    }
-
-    public float getAntennaPitchDegrees() {
-        return mAntennaPitchDegrees;
-    }
-
-    public float getAntennaRollDegrees() {
-        return mAntennaRollDegrees;
-    }
-
     private void readFromParcel(Parcel in) {
         mSatelliteAzimuthDegrees = in.readFloat();
         mSatelliteElevationDegrees = in.readFloat();
-        mAntennaAzimuthDegrees = in.readFloat();
-        mAntennaPitchDegrees = in.readFloat();
-        mAntennaRollDegrees = in.readFloat();
     }
 }
diff --git a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
index 889856b..df80159 100644
--- a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
+++ b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
@@ -33,31 +33,24 @@
     @NonNull @SatelliteManager.NTRadioTechnology private Set<Integer> mSupportedRadioTechnologies;
 
     /**
-     * Whether satellite modem is always on.
-     * This indicates the power impact of keeping it on is very minimal.
-     */
-    private boolean mIsAlwaysOn;
-
-    /**
      * Whether UE needs to point to a satellite to send and receive data.
      */
-    private boolean mNeedsPointingToSatellite;
+    private boolean mIsPointingRequired;
 
     /**
-     * Whether UE needs a separate SIM profile to communicate with the satellite network.
+     * The maximum number of bytes per datagram that can be sent over satellite.
      */
-    private boolean mNeedsSeparateSimProfile;
+    private int mMaxBytesPerOutgoingDatagram;
 
     /**
      * @hide
      */
-    public SatelliteCapabilities(Set<Integer> supportedRadioTechnologies, boolean isAlwaysOn,
-            boolean needsPointingToSatellite, boolean needsSeparateSimProfile) {
+    public SatelliteCapabilities(Set<Integer> supportedRadioTechnologies,
+            boolean isPointingRequired, int maxBytesPerOutgoingDatagram) {
         mSupportedRadioTechnologies = supportedRadioTechnologies == null
                 ? new HashSet<>() : supportedRadioTechnologies;
-        mIsAlwaysOn = isAlwaysOn;
-        mNeedsPointingToSatellite = needsPointingToSatellite;
-        mNeedsSeparateSimProfile = needsSeparateSimProfile;
+        mIsPointingRequired = isPointingRequired;
+        mMaxBytesPerOutgoingDatagram = maxBytesPerOutgoingDatagram;
     }
 
     private SatelliteCapabilities(Parcel in) {
@@ -80,9 +73,8 @@
             out.writeInt(0);
         }
 
-        out.writeBoolean(mIsAlwaysOn);
-        out.writeBoolean(mNeedsPointingToSatellite);
-        out.writeBoolean(mNeedsSeparateSimProfile);
+        out.writeBoolean(mIsPointingRequired);
+        out.writeInt(mMaxBytesPerOutgoingDatagram);
     }
 
     @NonNull public static final Creator<SatelliteCapabilities> CREATOR = new Creator<>() {
@@ -111,16 +103,12 @@
             sb.append("none,");
         }
 
-        sb.append("isAlwaysOn:");
-        sb.append(mIsAlwaysOn);
+        sb.append("isPointingRequired:");
+        sb.append(mIsPointingRequired);
         sb.append(",");
 
-        sb.append("needsPointingToSatellite:");
-        sb.append(mNeedsPointingToSatellite);
-        sb.append(",");
-
-        sb.append("needsSeparateSimProfile:");
-        sb.append(mNeedsSeparateSimProfile);
+        sb.append("maxBytesPerOutgoingDatagram");
+        sb.append(mMaxBytesPerOutgoingDatagram);
         return sb.toString();
     }
 
@@ -133,33 +121,22 @@
     }
 
     /**
-     * Get whether the satellite modem is always on.
-     * This indicates the power impact of keeping it on is very minimal.
-     *
-     * @return {@code true} if the satellite modem is always on and {@code false} otherwise.
-     */
-    public boolean isAlwaysOn() {
-        return mIsAlwaysOn;
-    }
-
-    /**
      * Get whether UE needs to point to a satellite to send and receive data.
      *
-     * @return {@code true} if UE needs to pointing to a satellite to send and receive data and
+     * @return {@code true} if UE needs to point to a satellite to send and receive data and
      *         {@code false} otherwise.
      */
-    public boolean needsPointingToSatellite() {
-        return mNeedsPointingToSatellite;
+    public boolean isPointingRequired() {
+        return mIsPointingRequired;
     }
 
     /**
-     * Get whether UE needs a separate SIM profile to communicate with the satellite network.
+     * The maximum number of bytes per datagram that can be sent over satellite.
      *
-     * @return {@code true} if UE needs a separate SIM profile to comunicate with the satellite
-     *         network and {@code false} otherwise.
+     * @return The maximum number of bytes per datagram that can be sent over satellite.
      */
-    public boolean needsSeparateSimProfile() {
-        return mNeedsSeparateSimProfile;
+    public int getMaxBytesPerOutgoingDatagram() {
+        return mMaxBytesPerOutgoingDatagram;
     }
 
     private void readFromParcel(Parcel in) {
@@ -171,8 +148,7 @@
             }
         }
 
-        mIsAlwaysOn = in.readBoolean();
-        mNeedsPointingToSatellite = in.readBoolean();
-        mNeedsSeparateSimProfile = in.readBoolean();
+        mIsPointingRequired = in.readBoolean();
+        mMaxBytesPerOutgoingDatagram = in.readInt();
     }
 }
diff --git a/telephony/java/android/telephony/satellite/SatelliteDatagram.java b/telephony/java/android/telephony/satellite/SatelliteDatagram.java
index cc5a9f4..d3cb8a0 100644
--- a/telephony/java/android/telephony/satellite/SatelliteDatagram.java
+++ b/telephony/java/android/telephony/satellite/SatelliteDatagram.java
@@ -17,7 +17,6 @@
 package android.telephony.satellite;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -28,7 +27,7 @@
     /**
      * Datagram to be sent or received over satellite.
      */
-    private byte[] mData;
+    @NonNull private byte[] mData;
 
     /**
      * @hide
@@ -51,8 +50,8 @@
         out.writeByteArray(mData);
     }
 
-    public static final @android.annotation.NonNull Creator<SatelliteDatagram> CREATOR =
-            new Creator<SatelliteDatagram>() {
+    @NonNull public static final Creator<SatelliteDatagram> CREATOR =
+            new Creator<>() {
                 @Override
                 public SatelliteDatagram createFromParcel(Parcel in) {
                     return new SatelliteDatagram(in);
@@ -64,8 +63,7 @@
                 }
             };
 
-    @Nullable
-    public byte[] getSatelliteDatagram() {
+    @NonNull public byte[] getSatelliteDatagram() {
         return mData;
     }
 
diff --git a/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java b/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
index 213b985..f237ada 100644
--- a/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
@@ -17,48 +17,18 @@
 package android.telephony.satellite;
 
 import android.annotation.NonNull;
-import android.os.Binder;
 
 import com.android.internal.telephony.ILongConsumer;
 
-import java.util.concurrent.Executor;
-
 /**
  * A callback class for listening to satellite datagrams.
  *
  * @hide
  */
-public class SatelliteDatagramCallback {
-    private final CallbackBinder mBinder = new CallbackBinder(this);
-
-    private static class CallbackBinder extends ISatelliteDatagramCallback.Stub {
-        private final SatelliteDatagramCallback mLocalCallback;
-        private Executor mExecutor;
-
-        private CallbackBinder(SatelliteDatagramCallback localCallback) {
-            mLocalCallback = localCallback;
-        }
-
-        @Override
-        public void onSatelliteDatagramReceived(long datagramId,
-                @NonNull SatelliteDatagram datagram, int pendingCount,
-                @NonNull ILongConsumer callback) {
-            final long callingIdentity = Binder.clearCallingIdentity();
-            try {
-                mExecutor.execute(() -> mLocalCallback.onSatelliteDatagramReceived(datagramId,
-                        datagram, pendingCount, callback));
-            } finally {
-                restoreCallingIdentity(callingIdentity);
-            }
-        }
-
-        private void setExecutor(Executor executor) {
-            mExecutor = executor;
-        }
-    }
-
+public interface SatelliteDatagramCallback {
     /**
      * Called when there is an incoming datagram to be received.
+     *
      * @param datagramId An id that uniquely identifies incoming datagram.
      * @param datagram Datagram to be received over satellite.
      * @param pendingCount Number of datagrams yet to be received by the app.
@@ -66,19 +36,6 @@
      *                 datagramId to Telephony. If the callback is not received within five minutes,
      *                 Telephony will resend the datagram.
      */
-    public void onSatelliteDatagramReceived(long datagramId, @NonNull SatelliteDatagram datagram,
-            int pendingCount, @NonNull ILongConsumer callback) {
-        // Base Implementation
-    }
-
-    /** @hide */
-    @NonNull
-    final ISatelliteDatagramCallback getBinder() {
-        return mBinder;
-    }
-
-    /** @hide */
-    public void setExecutor(@NonNull Executor executor) {
-        mBinder.setExecutor(executor);
-    }
+    void onSatelliteDatagramReceived(long datagramId, @NonNull SatelliteDatagram datagram,
+            int pendingCount, @NonNull ILongConsumer callback);
 }
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 9ec6929..d0abfbf 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -36,12 +36,15 @@
 import android.telephony.TelephonyFrameworkInitializer;
 
 import com.android.internal.telephony.IIntegerConsumer;
+import com.android.internal.telephony.ILongConsumer;
 import com.android.internal.telephony.ITelephony;
 import com.android.telephony.Rlog;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.time.Duration;
 import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
@@ -55,6 +58,17 @@
 public class SatelliteManager {
     private static final String TAG = "SatelliteManager";
 
+    private static final ConcurrentHashMap<SatelliteDatagramCallback, ISatelliteDatagramCallback>
+            sSatelliteDatagramCallbackMap = new ConcurrentHashMap<>();
+    private static final ConcurrentHashMap<SatelliteProvisionStateCallback,
+            ISatelliteProvisionStateCallback> sSatelliteProvisionStateCallbackMap =
+            new ConcurrentHashMap<>();
+    private static final ConcurrentHashMap<SatelliteStateCallback, ISatelliteStateCallback>
+            sSatelliteStateCallbackMap = new ConcurrentHashMap<>();
+    private static final ConcurrentHashMap<SatelliteTransmissionUpdateCallback,
+            ISatelliteTransmissionUpdateCallback> sSatelliteTransmissionUpdateCallbackMap =
+            new ConcurrentHashMap<>();
+
     private final int mSubId;
 
     /**
@@ -66,6 +80,7 @@
      * Create an instance of the SatelliteManager.
      *
      * @param context The context the SatelliteManager belongs to.
+     * @hide
      */
     public SatelliteManager(@Nullable Context context) {
         this(context, SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
@@ -116,7 +131,7 @@
 
     /**
      * Bundle key to get the response from
-     * {@link #requestIsSatelliteDemoModeEnabled(Executor, OutcomeReceiver)}.
+     * {@link #requestIsDemoModeEnabled(Executor, OutcomeReceiver)}.
      * @hide
      */
     public static final String KEY_DEMO_MODE_ENABLED = "demo_mode_enabled";
@@ -137,14 +152,6 @@
 
     /**
      * Bundle key to get the response from
-     * {@link #requestMaxSizePerSendingDatagram(Executor, OutcomeReceiver)} .
-     * @hide
-     */
-    public static final String KEY_MAX_CHARACTERS_PER_SATELLITE_TEXT =
-            "max_characters_per_satellite_text";
-
-    /**
-     * Bundle key to get the response from
      * {@link #requestIsSatelliteProvisioned(Executor, OutcomeReceiver)}.
      * @hide
      */
@@ -319,23 +326,25 @@
     public @interface NTRadioTechnology {}
 
     /**
-     * Request to enable or disable the satellite modem. If the satellite modem is enabled, this
-     * will also disable the cellular modem, and if the satellite modem is disabled, this will also
-     * re-enable the cellular modem.
+     * Request to enable or disable the satellite modem and demo mode. If the satellite modem is
+     * enabled, this may also disable the cellular modem, and if the satellite modem is disabled,
+     * this may also re-enable the cellular modem.
      *
-     * @param enable {@code true} to enable the satellite modem and {@code false} to disable.
+     * @param enableSatellite {@code true} to enable the satellite modem and
+     *                        {@code false} to disable.
+     * @param enableDemoMode {@code true} to enable demo mode and {@code false} to disable.
      * @param executor The executor on which the error code listener will be called.
-     * @param errorCodeListener Listener for the {@link SatelliteError} result of the operation.
+     * @param resultListener Listener for the {@link SatelliteError} result of the operation.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    public void requestSatelliteEnabled(
-            boolean enable, @NonNull @CallbackExecutor Executor executor,
-            @NonNull Consumer<Integer> errorCodeListener) {
+    public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode,
+            @NonNull @CallbackExecutor Executor executor,
+            @SatelliteError @NonNull Consumer<Integer> resultListener) {
         Objects.requireNonNull(executor);
-        Objects.requireNonNull(errorCodeListener);
+        Objects.requireNonNull(resultListener);
 
         try {
             ITelephony telephony = getITelephony();
@@ -344,10 +353,11 @@
                     @Override
                     public void accept(int result) {
                         executor.execute(() -> Binder.withCleanCallingIdentity(
-                                () -> errorCodeListener.accept(result)));
+                                () -> resultListener.accept(result)));
                     }
                 };
-                telephony.requestSatelliteEnabled(mSubId, enable, errorCallback);
+                telephony.requestSatelliteEnabled(mSubId, enableSatellite, enableDemoMode,
+                        errorCallback);
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
@@ -412,50 +422,13 @@
     }
 
     /**
-     * Request to enable or disable the satellite service demo mode.
-     *
-     * @param enable {@code true} to enable the satellite demo mode and {@code false} to disable.
-     * @param executor The executor on which the error code listener will be called.
-     * @param errorCodeListener Listener for the {@link SatelliteError} result of the operation.
-     *
-     * @throws SecurityException if the caller doesn't have required permission.
-     * @throws IllegalStateException if the Telephony process is not currently available.
-     */
-    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    public void requestSatelliteDemoModeEnabled(boolean enable,
-            @NonNull @CallbackExecutor Executor executor,
-            @NonNull Consumer<Integer> errorCodeListener) {
-        Objects.requireNonNull(executor);
-        Objects.requireNonNull(errorCodeListener);
-
-        try {
-            ITelephony telephony = getITelephony();
-            if (telephony != null) {
-                IIntegerConsumer errorCallback = new IIntegerConsumer.Stub() {
-                    @Override
-                    public void accept(int result) {
-                        executor.execute(() -> Binder.withCleanCallingIdentity(
-                                () -> errorCodeListener.accept(result)));
-                    }
-                };
-                telephony.requestSatelliteDemoModeEnabled(mSubId, enable, errorCallback);
-            } else {
-                throw new IllegalStateException("telephony service is null.");
-            }
-        } catch (RemoteException ex) {
-            Rlog.e(TAG, "requestSatelliteDemoModeEnabled() RemoteException: ", ex);
-            ex.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * Request to get whether the satellite service demo mode is enabled.
      *
      * @param executor The executor on which the callback will be called.
      * @param callback The callback object to which the result will be delivered.
      *                 If the request is successful, {@link OutcomeReceiver#onResult(Object)}
-     *                 will return a {@code boolean} with value {@code true} if the satellite
-     *                 demo mode is enabled and {@code false} otherwise.
+     *                 will return a {@code boolean} with value {@code true} if demo mode is enabled
+     *                 and {@code false} otherwise.
      *                 If the request is not successful, {@link OutcomeReceiver#onError(Throwable)}
      *                 will return a {@link SatelliteException} with the {@link SatelliteError}.
      *
@@ -463,7 +436,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    public void requestIsSatelliteDemoModeEnabled(@NonNull @CallbackExecutor Executor executor,
+    public void requestIsDemoModeEnabled(@NonNull @CallbackExecutor Executor executor,
             @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
         Objects.requireNonNull(executor);
         Objects.requireNonNull(callback);
@@ -492,12 +465,12 @@
                         }
                     }
                 };
-                telephony.requestIsSatelliteDemoModeEnabled(mSubId, receiver);
+                telephony.requestIsDemoModeEnabled(mSubId, receiver);
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
         } catch (RemoteException ex) {
-            loge("requestIsSatelliteDemoModeEnabled() RemoteException: " + ex);
+            loge("requestIsDemoModeEnabled() RemoteException: " + ex);
             ex.rethrowFromSystemServer();
         }
     }
@@ -742,145 +715,117 @@
     public @interface DatagramType {}
 
     /**
-     * Start receiving satellite position updates.
+     * Start receiving satellite transmission updates.
      * This can be called by the pointing UI when the user starts pointing to the satellite.
      * Modem should continue to report the pointing input as the device or satellite moves.
-     * Satellite position updates are started only on {@link #SATELLITE_ERROR_NONE}.
+     * Satellite transmission updates are started only on {@link #SATELLITE_ERROR_NONE}.
      * All other results indicate that this operation failed.
-     * Once satellite position updates begin, datagram transfer state updates will be sent
-     * through {@link SatellitePositionUpdateCallback}.
+     * Once satellite transmission updates begin, position and datagram transfer state updates
+     * will be sent through {@link SatelliteTransmissionUpdateCallback}.
      *
      * @param executor The executor on which the callback and error code listener will be called.
-     * @param errorCodeListener Listener for the {@link SatelliteError} result of the operation.
-     * @param callback The callback to notify of changes in satellite position.
+     * @param resultListener Listener for the {@link SatelliteError} result of the operation.
+     * @param callback The callback to notify of satellite transmission updates.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    public void startSatellitePositionUpdates(@NonNull @CallbackExecutor Executor executor,
-            @NonNull Consumer<Integer> errorCodeListener,
-            @NonNull SatellitePositionUpdateCallback callback) {
+    public void startSatelliteTransmissionUpdates(@NonNull @CallbackExecutor Executor executor,
+            @SatelliteError @NonNull Consumer<Integer> resultListener,
+            @NonNull SatelliteTransmissionUpdateCallback callback) {
         Objects.requireNonNull(executor);
-        Objects.requireNonNull(errorCodeListener);
+        Objects.requireNonNull(resultListener);
         Objects.requireNonNull(callback);
 
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                callback.setExecutor(executor);
                 IIntegerConsumer errorCallback = new IIntegerConsumer.Stub() {
                     @Override
                     public void accept(int result) {
                         executor.execute(() -> Binder.withCleanCallingIdentity(
-                                () -> errorCodeListener.accept(result)));
+                                () -> resultListener.accept(result)));
                     }
                 };
-                telephony.startSatellitePositionUpdates(
-                        mSubId, errorCallback, callback.getBinder());
+                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)));
+                            }
+                        };
+                sSatelliteTransmissionUpdateCallbackMap.put(callback, internalCallback);
+                telephony.startSatelliteTransmissionUpdates(mSubId, errorCallback,
+                        internalCallback);
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
         } catch (RemoteException ex) {
-            loge("startSatellitePositionUpdates() RemoteException: " + ex);
+            loge("startSatelliteTransmissionUpdates() RemoteException: " + ex);
             ex.rethrowFromSystemServer();
         }
     }
 
     /**
-     * Stop receiving satellite position updates.
+     * Stop receiving satellite transmission updates.
      * This can be called by the pointing UI when the user stops pointing to the satellite.
-     * Satellite position updates are stopped and the callback is unregistered only on
+     * Satellite transmission updates are stopped and the callback is unregistered only on
      * {@link #SATELLITE_ERROR_NONE}. All other results that this operation failed.
      *
-     * @param callback The callback that was passed to
-     * {@link #startSatellitePositionUpdates(Executor, Consumer, SatellitePositionUpdateCallback)}.
+     * @param callback The callback that was passed to {@link
+     * #startSatelliteTransmissionUpdates(Executor, Consumer, SatelliteTransmissionUpdateCallback)}.
      * @param executor The executor on which the error code listener will be called.
-     * @param errorCodeListener Listener for the {@link SatelliteError} result of the operation.
+     * @param resultListener Listener for the {@link SatelliteError} result of the operation.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    public void stopSatellitePositionUpdates(@NonNull SatellitePositionUpdateCallback callback,
+    public void stopSatelliteTransmissionUpdates(
+            @NonNull SatelliteTransmissionUpdateCallback callback,
             @NonNull @CallbackExecutor Executor executor,
-            @NonNull Consumer<Integer> errorCodeListener) {
+            @SatelliteError @NonNull Consumer<Integer> resultListener) {
         Objects.requireNonNull(callback);
         Objects.requireNonNull(executor);
-        Objects.requireNonNull(errorCodeListener);
+        Objects.requireNonNull(resultListener);
+        ISatelliteTransmissionUpdateCallback internalCallback =
+                sSatelliteTransmissionUpdateCallbackMap.remove(callback);
 
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                IIntegerConsumer errorCallback = new IIntegerConsumer.Stub() {
-                    @Override
-                    public void accept(int result) {
-                        executor.execute(() -> Binder.withCleanCallingIdentity(
-                                () -> errorCodeListener.accept(result)));
-                    }
-                };
-                telephony.stopSatellitePositionUpdates(mSubId, errorCallback,
-                        callback.getBinder());
-                // TODO: Notify SmsHandler that pointing UI stopped
-            } else {
-                throw new IllegalStateException("telephony service is null.");
-            }
-        } catch (RemoteException ex) {
-            loge("stopSatellitePositionUpdates() RemoteException: " + ex);
-            ex.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Request to get the maximum number of bytes per datagram that can be sent to satellite.
-     *
-     * @param executor The executor on which the callback will be called.
-     * @param callback The callback object to which the result will be delivered.
-     *                 If the request is successful, {@link OutcomeReceiver#onResult(Object)}
-     *                 will return the maximum number of bytes per datagram that can be sent to
-     *                 satellite.
-     *                 If the request is not successful, {@link OutcomeReceiver#onError(Throwable)}
-     *
-     * @throws SecurityException if the caller doesn't have required permission.
-     * @throws IllegalStateException if the Telephony process is not currently available.
-     */
-    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    public void requestMaxSizePerSendingDatagram(
-            @NonNull @CallbackExecutor Executor executor,
-            @NonNull OutcomeReceiver<Integer, SatelliteException> callback) {
-        Objects.requireNonNull(executor);
-        Objects.requireNonNull(callback);
-
-        try {
-            ITelephony telephony = getITelephony();
-            if (telephony != null) {
-                ResultReceiver receiver = new ResultReceiver(null) {
-                    @Override
-                    protected void onReceiveResult(int resultCode, Bundle resultData) {
-                        if (resultCode == SATELLITE_ERROR_NONE) {
-                            if (resultData.containsKey(KEY_MAX_CHARACTERS_PER_SATELLITE_TEXT)) {
-                                int maxCharacters =
-                                        resultData.getInt(KEY_MAX_CHARACTERS_PER_SATELLITE_TEXT);
-                                executor.execute(() -> Binder.withCleanCallingIdentity(() ->
-                                        callback.onResult(maxCharacters)));
-                            } else {
-                                loge("KEY_MAX_CHARACTERS_PER_SATELLITE_TEXT does not exist.");
-                                executor.execute(() -> Binder.withCleanCallingIdentity(() ->
-                                        callback.onError(
-                                                new SatelliteException(SATELLITE_REQUEST_FAILED))));
-                            }
-                        } else {
-                            executor.execute(() -> Binder.withCleanCallingIdentity(() ->
-                                    callback.onError(new SatelliteException(resultCode))));
+                if (internalCallback != null) {
+                    IIntegerConsumer errorCallback = new IIntegerConsumer.Stub() {
+                        @Override
+                        public void accept(int result) {
+                            executor.execute(() -> Binder.withCleanCallingIdentity(
+                                    () -> resultListener.accept(result)));
                         }
-                    }
-                };
-                telephony.requestMaxSizePerSendingDatagram(mSubId, receiver);
+                    };
+                    telephony.stopSatelliteTransmissionUpdates(mSubId, errorCallback,
+                            internalCallback);
+                    // TODO: Notify SmsHandler that pointing UI stopped
+                } else {
+                    loge("stopSatelliteTransmissionUpdates: No internal callback.");
+                    executor.execute(() -> Binder.withCleanCallingIdentity(
+                            () -> resultListener.accept(SATELLITE_INVALID_ARGUMENTS)));
+                }
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
         } catch (RemoteException ex) {
-            loge("requestMaxCharactersPerSatelliteTextMessage() RemoteException: " + ex);
+            loge("stopSatelliteTransmissionUpdates() RemoteException: " + ex);
             ex.rethrowFromSystemServer();
         }
     }
@@ -891,23 +836,24 @@
      *
      * @param token The token to be used as a unique identifier for provisioning with satellite
      *              gateway.
+     * @param regionId The region ID for the device's current location.
      * @param cancellationSignal The optional signal used by the caller to cancel the provision
      *                           request. Even when the cancellation is signaled, Telephony will
      *                           still trigger the callback to return the result of this request.
      * @param executor The executor on which the error code listener will be called.
-     * @param errorCodeListener Listener for the {@link SatelliteError} result of the operation.
+     * @param resultListener Listener for the {@link SatelliteError} result of the operation.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    public void provisionSatelliteService(@NonNull String token,
+    public void provisionSatelliteService(@NonNull String token, @NonNull String regionId,
             @Nullable CancellationSignal cancellationSignal,
             @NonNull @CallbackExecutor Executor executor,
-            @SatelliteError @NonNull Consumer<Integer> errorCodeListener) {
+            @SatelliteError @NonNull Consumer<Integer> resultListener) {
         Objects.requireNonNull(token);
         Objects.requireNonNull(executor);
-        Objects.requireNonNull(errorCodeListener);
+        Objects.requireNonNull(resultListener);
 
         ICancellationSignal cancelRemote = null;
         try {
@@ -917,10 +863,11 @@
                     @Override
                     public void accept(int result) {
                         executor.execute(() -> Binder.withCleanCallingIdentity(
-                                () -> errorCodeListener.accept(result)));
+                                () -> resultListener.accept(result)));
                     }
                 };
-                cancelRemote = telephony.provisionSatelliteService(mSubId, token, errorCallback);
+                cancelRemote = telephony.provisionSatelliteService(mSubId, token, regionId,
+                        errorCallback);
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
@@ -942,7 +889,7 @@
      * {@link #provisionSatelliteService(String, CancellationSignal, Executor, Consumer)}.
      *
      * @param token The token of the device/subscription to be deprovisioned.
-     * @param errorCodeListener Listener for the {@link SatelliteError} result of the operation.
+     * @param resultListener Listener for the {@link SatelliteError} result of the operation.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
@@ -950,10 +897,10 @@
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     public void deprovisionSatelliteService(@NonNull String token,
             @NonNull @CallbackExecutor Executor executor,
-            @SatelliteError @NonNull Consumer<Integer> errorCodeListener) {
+            @SatelliteError @NonNull Consumer<Integer> resultListener) {
         Objects.requireNonNull(token);
         Objects.requireNonNull(executor);
-        Objects.requireNonNull(errorCodeListener);
+        Objects.requireNonNull(resultListener);
 
         try {
             ITelephony telephony = getITelephony();
@@ -962,7 +909,7 @@
                     @Override
                     public void accept(int result) {
                         executor.execute(() -> Binder.withCleanCallingIdentity(
-                                () -> errorCodeListener.accept(result)));
+                                () -> resultListener.accept(result)));
                     }
                 };
                 telephony.deprovisionSatelliteService(mSubId, token, errorCallback);
@@ -996,9 +943,18 @@
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                callback.setExecutor(executor);
+                ISatelliteProvisionStateCallback internalCallback =
+                        new ISatelliteProvisionStateCallback.Stub() {
+                            @Override
+                            public void onSatelliteProvisionStateChanged(boolean provisioned) {
+                                executor.execute(() -> Binder.withCleanCallingIdentity(
+                                        () -> callback.onSatelliteProvisionStateChanged(
+                                                provisioned)));
+                            }
+                        };
+                sSatelliteProvisionStateCallbackMap.put(callback, internalCallback);
                 return telephony.registerForSatelliteProvisionStateChanged(
-                        mSubId, callback.getBinder());
+                        mSubId, internalCallback);
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
@@ -1023,11 +979,17 @@
     public void unregisterForSatelliteProvisionStateChanged(
             @NonNull SatelliteProvisionStateCallback callback) {
         Objects.requireNonNull(callback);
+        ISatelliteProvisionStateCallback internalCallback =
+                sSatelliteProvisionStateCallbackMap.remove(callback);
 
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                telephony.unregisterForSatelliteProvisionStateChanged(mSubId, callback.getBinder());
+                if (internalCallback != null) {
+                    telephony.unregisterForSatelliteProvisionStateChanged(mSubId, internalCallback);
+                } else {
+                    loge("unregisterForSatelliteProvisionStateChanged: No internal callback.");
+                }
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
@@ -1112,9 +1074,15 @@
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                callback.setExecutor(executor);
-                return telephony.registerForSatelliteModemStateChanged(mSubId,
-                        callback.getBinder());
+                ISatelliteStateCallback internalCallback = new ISatelliteStateCallback.Stub() {
+                    @Override
+                    public void onSatelliteModemStateChanged(int state) {
+                        executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+                                callback.onSatelliteModemStateChanged(state)));
+                    }
+                };
+                sSatelliteStateCallbackMap.put(callback, internalCallback);
+                return telephony.registerForSatelliteModemStateChanged(mSubId, internalCallback);
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
@@ -1138,11 +1106,16 @@
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     public void unregisterForSatelliteModemStateChanged(@NonNull SatelliteStateCallback callback) {
         Objects.requireNonNull(callback);
+        ISatelliteStateCallback internalCallback = sSatelliteStateCallbackMap.remove(callback);
 
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                telephony.unregisterForSatelliteModemStateChanged(mSubId, callback.getBinder());
+                if (internalCallback != null) {
+                    telephony.unregisterForSatelliteModemStateChanged(mSubId, internalCallback);
+                } else {
+                    loge("unregisterForSatelliteModemStateChanged: No internal callback.");
+                }
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
@@ -1155,8 +1128,6 @@
     /**
      * Register to receive incoming datagrams over satellite.
      *
-     * @param datagramType datagram type indicating whether the datagram is of type
-     *                     SOS_SMS or LOCATION_SHARING.
      * @param executor The executor on which the callback will be called.
      * @param callback The callback to handle incoming datagrams over satellite.
      *                 This callback with be invoked when a new datagram is received from satellite.
@@ -1167,7 +1138,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    @SatelliteError public int registerForSatelliteDatagram(@DatagramType int datagramType,
+    @SatelliteError public int registerForSatelliteDatagram(
             @NonNull @CallbackExecutor Executor executor,
             @NonNull SatelliteDatagramCallback callback) {
         Objects.requireNonNull(executor);
@@ -1176,9 +1147,19 @@
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                callback.setExecutor(executor);
-                return telephony.registerForSatelliteDatagram(mSubId, datagramType,
-                            callback.getBinder());
+                ISatelliteDatagramCallback internalCallback =
+                        new ISatelliteDatagramCallback.Stub() {
+                            @Override
+                            public void onSatelliteDatagramReceived(long datagramId,
+                                    @NonNull SatelliteDatagram datagram, int pendingCount,
+                                    @NonNull ILongConsumer ack) {
+                                executor.execute(() -> Binder.withCleanCallingIdentity(
+                                        () -> callback.onSatelliteDatagramReceived(
+                                                datagramId, datagram, pendingCount, ack)));
+                            }
+                        };
+                sSatelliteDatagramCallbackMap.put(callback, internalCallback);
+                return telephony.registerForSatelliteDatagram(mSubId, internalCallback);
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
@@ -1194,7 +1175,7 @@
      * If callback was not registered before, the request will be ignored.
      *
      * @param callback The callback that was passed to
-     * {@link #registerForSatelliteDatagram(int, Executor, SatelliteDatagramCallback)}.
+     * {@link #registerForSatelliteDatagram(Executor, SatelliteDatagramCallback)}.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
@@ -1202,11 +1183,17 @@
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     public void unregisterForSatelliteDatagram(@NonNull SatelliteDatagramCallback callback) {
         Objects.requireNonNull(callback);
+        ISatelliteDatagramCallback internalCallback =
+                sSatelliteDatagramCallbackMap.remove(callback);
 
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                telephony.unregisterForSatelliteDatagram(mSubId, callback.getBinder());
+                if (internalCallback != null) {
+                    telephony.unregisterForSatelliteDatagram(mSubId, internalCallback);
+                } else {
+                    loge("unregisterForSatelliteDatagram: No internal callback.");
+                }
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
@@ -1222,7 +1209,7 @@
      * This method requests modem to check if there are any pending datagrams to be received over
      * satellite. If there are any incoming datagrams, they will be received via
      * {@link SatelliteDatagramCallback#onSatelliteDatagramReceived(long, SatelliteDatagram, int,
-     *          ISatelliteDatagramReceiverAck)}
+     *        ILongConsumer)}
      *
      * @param executor The executor on which the result listener will be called.
      * @param resultListener Listener for the {@link SatelliteError} result of the operation.
@@ -1371,9 +1358,8 @@
     }
 
     /**
-     * Request to get the time after which the satellite will be visible. This is an
-     * {@code int} representing the duration in seconds after which the satellite will be visible.
-     * This will return {@code 0} if the satellite is currently visible.
+     * Request to get the duration in seconds after which the satellite will be visible.
+     * This will be {@link Duration#ZERO} if the satellite is currently visible.
      *
      * @param executor The executor on which the callback will be called.
      * @param callback The callback object to which the result will be delivered.
@@ -1387,7 +1373,7 @@
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     public void requestTimeForNextSatelliteVisibility(@NonNull @CallbackExecutor Executor executor,
-            @NonNull OutcomeReceiver<Integer, SatelliteException> callback) {
+            @NonNull OutcomeReceiver<Duration, SatelliteException> callback) {
         Objects.requireNonNull(executor);
         Objects.requireNonNull(callback);
 
@@ -1402,7 +1388,8 @@
                                 int nextVisibilityDuration =
                                         resultData.getInt(KEY_SATELLITE_NEXT_VISIBILITY);
                                 executor.execute(() -> Binder.withCleanCallingIdentity(() ->
-                                        callback.onResult(nextVisibilityDuration)));
+                                        callback.onResult(
+                                                Duration.ofSeconds(nextVisibilityDuration))));
                             } else {
                                 loge("KEY_SATELLITE_NEXT_VISIBILITY does not exist.");
                                 executor.execute(() -> Binder.withCleanCallingIdentity(() ->
diff --git a/telephony/java/android/telephony/satellite/SatellitePositionUpdateCallback.java b/telephony/java/android/telephony/satellite/SatellitePositionUpdateCallback.java
deleted file mode 100644
index d44a84d..0000000
--- a/telephony/java/android/telephony/satellite/SatellitePositionUpdateCallback.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * 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.telephony.satellite;
-
-import android.annotation.NonNull;
-import android.os.Binder;
-
-import java.util.concurrent.Executor;
-
-/**
- * A callback class for monitoring satellite position update and datagram transfer state change
- * events.
- *
- * @hide
- */
-public class SatellitePositionUpdateCallback {
-    private final CallbackBinder mBinder = new CallbackBinder(this);
-
-    private static class CallbackBinder extends ISatellitePositionUpdateCallback.Stub {
-        private final SatellitePositionUpdateCallback mLocalCallback;
-        private Executor mExecutor;
-
-        private CallbackBinder(SatellitePositionUpdateCallback localCallback) {
-            mLocalCallback = localCallback;
-        }
-
-        @Override
-        public void onSatellitePositionChanged(@NonNull PointingInfo pointingInfo) {
-            final long callingIdentity = Binder.clearCallingIdentity();
-            try {
-                mExecutor.execute(() ->
-                        mLocalCallback.onSatellitePositionChanged(pointingInfo));
-            } finally {
-                restoreCallingIdentity(callingIdentity);
-            }
-        }
-
-        @Override
-        public void onDatagramTransferStateChanged(
-                @SatelliteManager.SatelliteDatagramTransferState int state, int sendPendingCount,
-                int receivePendingCount, @SatelliteManager.SatelliteError int errorCode) {
-            final long callingIdentity = Binder.clearCallingIdentity();
-            try {
-                mExecutor.execute(() ->
-                        mLocalCallback.onDatagramTransferStateChanged(
-                                state, sendPendingCount, receivePendingCount, errorCode));
-            } finally {
-                restoreCallingIdentity(callingIdentity);
-            }
-        }
-
-        private void setExecutor(Executor executor) {
-            mExecutor = executor;
-        }
-    }
-
-    /**
-     * Called when the satellite position changed.
-     *
-     * @param pointingInfo The pointing info containing the satellite location.
-     */
-    public void onSatellitePositionChanged(@NonNull PointingInfo pointingInfo) {
-        // Base Implementation
-    }
-
-    /**
-     * Called when satellite datagram transfer state changed.
-     *
-     * @param state The new 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.
-     */
-    public void onDatagramTransferStateChanged(
-            @SatelliteManager.SatelliteDatagramTransferState int state, int sendPendingCount,
-            int receivePendingCount, @SatelliteManager.SatelliteError int errorCode) {
-        // Base Implementation
-    }
-
-    /**@hide*/
-    @NonNull
-    final ISatellitePositionUpdateCallback getBinder() {
-        return mBinder;
-    }
-
-    /**@hide*/
-    public void setExecutor(@NonNull Executor executor) {
-        mBinder.setExecutor(executor);
-    }
-}
diff --git a/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java
index 2b6a5d9..a62eb8b 100644
--- a/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java
@@ -16,61 +16,17 @@
 
 package android.telephony.satellite;
 
-import android.annotation.NonNull;
-import android.os.Binder;
-
-import java.util.concurrent.Executor;
-
 /**
  * A callback class for monitoring satellite provision state change events.
  *
  * @hide
  */
-public class SatelliteProvisionStateCallback {
-    private final CallbackBinder mBinder = new CallbackBinder(this);
-
-    private static class CallbackBinder extends ISatelliteProvisionStateCallback.Stub {
-        private final SatelliteProvisionStateCallback mLocalCallback;
-        private Executor mExecutor;
-
-        private CallbackBinder(SatelliteProvisionStateCallback localCallback) {
-            mLocalCallback = localCallback;
-        }
-
-        @Override
-        public void onSatelliteProvisionStateChanged(boolean provisioned) {
-            final long callingIdentity = Binder.clearCallingIdentity();
-            try {
-                mExecutor.execute(() ->
-                        mLocalCallback.onSatelliteProvisionStateChanged(provisioned));
-            } finally {
-                restoreCallingIdentity(callingIdentity);
-            }
-        }
-
-        private void setExecutor(Executor executor) {
-            mExecutor = executor;
-        }
-    }
-
+public interface SatelliteProvisionStateCallback {
     /**
      * Called when satellite provision state changes.
      *
      * @param provisioned The new provision state. {@code true} means satellite is provisioned
      *                    {@code false} means satellite is not provisioned.
      */
-    public void onSatelliteProvisionStateChanged(boolean provisioned) {
-        // Base Implementation
-    }
-
-    /**@hide*/
-    @NonNull
-    final ISatelliteProvisionStateCallback getBinder() {
-        return mBinder;
-    }
-
-    /**@hide*/
-    public void setExecutor(@NonNull Executor executor) {
-        mBinder.setExecutor(executor);
-    }
+    void onSatelliteProvisionStateChanged(boolean provisioned);
 }
diff --git a/telephony/java/android/telephony/satellite/SatelliteStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteStateCallback.java
index 17d05b7..d9ecaa3 100644
--- a/telephony/java/android/telephony/satellite/SatelliteStateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteStateCallback.java
@@ -16,80 +16,15 @@
 
 package android.telephony.satellite;
 
-import android.annotation.NonNull;
-import android.os.Binder;
-
-import java.util.concurrent.Executor;
-
 /**
  * A callback class for monitoring satellite modem state change events.
  *
  * @hide
  */
-public class SatelliteStateCallback {
-    private final CallbackBinder mBinder = new CallbackBinder(this);
-
-    private static class CallbackBinder extends ISatelliteStateCallback.Stub {
-        private final SatelliteStateCallback mLocalCallback;
-        private Executor mExecutor;
-
-        private CallbackBinder(SatelliteStateCallback localCallback) {
-            mLocalCallback = localCallback;
-        }
-
-        @Override
-        public void onSatelliteModemStateChanged(@SatelliteManager.SatelliteModemState int state) {
-            final long callingIdentity = Binder.clearCallingIdentity();
-            try {
-                mExecutor.execute(() ->
-                        mLocalCallback.onSatelliteModemStateChanged(state));
-            } finally {
-                restoreCallingIdentity(callingIdentity);
-            }
-        }
-
-        @Override
-        public void onPendingDatagramCount(int count) {
-            final long callingIdentity = Binder.clearCallingIdentity();
-            try {
-                mExecutor.execute(() ->
-                        mLocalCallback.onPendingDatagramCount(count));
-            } finally {
-                restoreCallingIdentity(callingIdentity);
-            }
-        }
-
-        private void setExecutor(Executor executor) {
-            mExecutor = executor;
-        }
-    }
-
+public interface SatelliteStateCallback {
     /**
      * Called when satellite modem state changes.
      * @param state The new satellite modem state.
      */
-    public void onSatelliteModemStateChanged(@SatelliteManager.SatelliteModemState int state) {
-        // Base Implementation
-    }
-
-    /**
-     * Called when there are pending datagrams to be received from satellite.
-     * @param count Pending datagram count.
-     */
-    public void onPendingDatagramCount(int count) {
-        // Base Implementation
-    }
-
-    //TODO: Add an API for datagram transfer state update here.
-
-    /**@hide*/
-    @NonNull
-    final ISatelliteStateCallback getBinder() {
-        return mBinder;
-    }
-
-    /**@hide*/
-    public void setExecutor(@NonNull Executor executor) {
-        mBinder.setExecutor(executor);
-    }
+    void onSatelliteModemStateChanged(@SatelliteManager.SatelliteModemState int state);
 }
diff --git a/telephony/java/android/telephony/satellite/ISatellitePositionUpdateCallback.aidl b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
similarity index 68%
copy from telephony/java/android/telephony/satellite/ISatellitePositionUpdateCallback.aidl
copy to telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
index d3f1091..0efbd1f 100644
--- a/telephony/java/android/telephony/satellite/ISatellitePositionUpdateCallback.aidl
+++ b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2023 The Android Open Source Project
+ * 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.
@@ -16,13 +16,22 @@
 
 package android.telephony.satellite;
 
-import android.telephony.satellite.PointingInfo;
+import android.annotation.NonNull;
 
 /**
- * Interface for position update and datagram transfer state change callback.
+ * A callback class for monitoring satellite position update and datagram transfer state change
+ * events.
+ *
  * @hide
  */
-oneway interface ISatellitePositionUpdateCallback {
+public interface SatelliteTransmissionUpdateCallback {
+    /**
+     * Called when the satellite position changed.
+     *
+     * @param pointingInfo The pointing info containing the satellite location.
+     */
+    void onSatellitePositionChanged(@NonNull PointingInfo pointingInfo);
+
     /**
      * Called when satellite datagram transfer state changed.
      *
@@ -31,13 +40,7 @@
      * @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);
-
-    /**
-     * Called when the satellite position changed.
-     *
-     * @param pointingInfo The pointing info containing the satellite location.
-     */
-    void onSatellitePositionChanged(in PointingInfo pointingInfo);
+    void onDatagramTransferStateChanged(@SatelliteManager.SatelliteDatagramTransferState int state,
+            int sendPendingCount, int receivePendingCount,
+            @SatelliteManager.SatelliteError int errorCode);
 }
diff --git a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
index d93ee21..a780cb9 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
@@ -49,10 +49,9 @@
      * Listening mode allows the satellite service to listen for incoming pages.
      *
      * @param enable True to enable satellite listening mode and false to disable.
-     * @param isDemoMode Whether demo mode is enabled.
      * @param timeout How long the satellite modem should wait for the next incoming page before
      *                disabling listening mode.
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param resultCallback The callback to receive the error code result of the operation.
      *
      * Valid error codes returned:
      *   SatelliteError:ERROR_NONE
@@ -64,16 +63,17 @@
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      *   SatelliteError:NO_RESOURCES
      */
-    void requestSatelliteListeningEnabled(in boolean enable, in boolean isDemoMode, in int timeout,
-            in IIntegerConsumer errorCallback);
+    void requestSatelliteListeningEnabled(in boolean enable, in int timeout,
+            in IIntegerConsumer resultCallback);
 
     /**
-     * Request to enable or disable the satellite modem. If the satellite modem is enabled,
-     * this will also disable the cellular modem, and if the satellite modem is disabled,
-     * this will also re-enable the cellular modem.
+     * Request to enable or disable the satellite modem and demo mode. If the satellite modem
+     * is enabled, this may also disable the cellular modem, and if the satellite modem is disabled,
+     * this may also re-enable the cellular modem.
      *
-     * @param enable True to enable the satellite modem and false to disable.
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param enableSatellite True to enable the satellite modem and false to disable.
+     * @param enableDemoMode True to enable demo mode and false to disable.
+     * @param resultCallback The callback to receive the error code result of the operation.
      *
      * Valid error codes returned:
      *   SatelliteError:ERROR_NONE
@@ -85,13 +85,14 @@
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      *   SatelliteError:NO_RESOURCES
      */
-    void requestSatelliteEnabled(in boolean enabled, in IIntegerConsumer errorCallback);
+    void requestSatelliteEnabled(in boolean enableSatellite, in boolean enableDemoMode,
+            in IIntegerConsumer resultCallback);
 
     /**
      * Request to get whether the satellite modem is enabled.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
-     *                      This must only be sent when the result is not SatelliteError#ERROR_NONE.
+     * @param resultCallback The callback to receive the error code result of the operation.
+     *                       This must only be sent when the error is not SatelliteError#ERROR_NONE.
      * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
      *                 whether the satellite modem is enabled.
      *
@@ -105,13 +106,13 @@
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      *   SatelliteError:NO_RESOURCES
      */
-    void requestIsSatelliteEnabled(in IIntegerConsumer errorCallback, in IBooleanConsumer callback);
+    void requestIsSatelliteEnabled(in IIntegerConsumer resultCallback, in IBooleanConsumer callback);
 
     /**
      * Request to get whether the satellite service is supported on the device.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
-     *                      This must only be sent when the result is not SatelliteError#ERROR_NONE.
+     * @param resultCallback The callback to receive the error code result of the operation.
+     *                       This must only be sent when the error is not SatelliteError#ERROR_NONE.
      * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
      *                 whether the satellite service is supported on the device.
      *
@@ -125,14 +126,14 @@
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      *   SatelliteError:NO_RESOURCES
      */
-    void requestIsSatelliteSupported(in IIntegerConsumer errorCallback,
+    void requestIsSatelliteSupported(in IIntegerConsumer resultCallback,
             in IBooleanConsumer callback);
 
     /**
      * Request to get the SatelliteCapabilities of the satellite service.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
-     *                      This must only be sent when the result is not SatelliteError#ERROR_NONE.
+     * @param resultCallback The callback to receive the error code result of the operation.
+     *                       This must only be sent when the error is not SatelliteError#ERROR_NONE.
      * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
      *                 the SatelliteCapabilities of the satellite service.
      *
@@ -146,7 +147,7 @@
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      *   SatelliteError:NO_RESOURCES
      */
-    void requestSatelliteCapabilities(in IIntegerConsumer errorCallback,
+    void requestSatelliteCapabilities(in IIntegerConsumer resultCallback,
             in ISatelliteCapabilitiesConsumer callback);
 
     /**
@@ -154,7 +155,7 @@
      * The satellite service should report the satellite pointing info via
      * ISatelliteListener#onSatellitePositionChanged as the user device/satellite moves.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param resultCallback The callback to receive the error code result of the operation.
      *
      * Valid error codes returned:
      *   SatelliteError:ERROR_NONE
@@ -166,13 +167,13 @@
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      *   SatelliteError:NO_RESOURCES
      */
-    void startSendingSatellitePointingInfo(in IIntegerConsumer errorCallback);
+    void startSendingSatellitePointingInfo(in IIntegerConsumer resultCallback);
 
     /**
      * User stopped pointing to the satellite.
      * The satellite service should stop reporting satellite pointing info to the framework.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param resultCallback The callback to receive the error code result of the operation.
      *
      * Valid error codes returned:
      *   SatelliteError:ERROR_NONE
@@ -184,28 +185,7 @@
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      *   SatelliteError:NO_RESOURCES
      */
-    void stopSendingSatellitePointingInfo(in IIntegerConsumer errorCallback);
-
-    /**
-     * Request to get the maximum number of characters per MO text message on satellite.
-     *
-     * @param errorCallback The callback to receive the error code result of the operation.
-     *                      This must only be sent when the result is not SatelliteError#ERROR_NONE.
-     * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
-     *                 the maximum number of characters per MO text message on satellite.
-     *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
-     */
-    void requestMaxCharactersPerMOTextMessage(in IIntegerConsumer errorCallback,
-            in IIntegerConsumer callback);
+    void stopSendingSatellitePointingInfo(in IIntegerConsumer resultCallback);
 
     /**
      * Provision the device with a satellite provider.
@@ -214,7 +194,8 @@
      *
      * @param token The token to be used as a unique identifier for provisioning with satellite
      *              gateway.
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param regionId The region ID for the device's current location.
+     * @param resultCallback The callback to receive the error code result of the operation.
      *
      * Valid error codes returned:
      *   SatelliteError:ERROR_NONE
@@ -229,7 +210,8 @@
      *   SatelliteError:REQUEST_ABORTED
      *   SatelliteError:NETWORK_TIMEOUT
      */
-    void provisionSatelliteService(in String token, in IIntegerConsumer errorCallback);
+    void provisionSatelliteService(in String token, in String regionId,
+            in IIntegerConsumer resultCallback);
 
     /**
      * Deprovision the device with the satellite provider.
@@ -237,7 +219,7 @@
      * Once deprovisioned, ISatelliteListener#onSatelliteProvisionStateChanged should report false.
      *
      * @param token The token of the device/subscription to be deprovisioned.
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param resultCallback The callback to receive the error code result of the operation.
      *
      * Valid error codes returned:
      *   SatelliteError:ERROR_NONE
@@ -252,13 +234,13 @@
      *   SatelliteError:REQUEST_ABORTED
      *   SatelliteError:NETWORK_TIMEOUT
      */
-    void deprovisionSatelliteService(in String token, in IIntegerConsumer errorCallback);
+    void deprovisionSatelliteService(in String token, in IIntegerConsumer resultCallback);
 
     /**
      * Request to get whether this device is provisioned with a satellite provider.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
-     *                      This must only be sent when the result is not SatelliteError#ERROR_NONE.
+     * @param resultCallback The callback to receive the error code result of the operation.
+     *                       This must only be sent when the error is not SatelliteError#ERROR_NONE.
      * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
      *                 whether this device is provisioned with a satellite provider.
      *
@@ -272,7 +254,7 @@
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      *   SatelliteError:NO_RESOURCES
      */
-    void requestIsSatelliteProvisioned(in IIntegerConsumer errorCallback,
+    void requestIsSatelliteProvisioned(in IIntegerConsumer resultCallback,
             in IBooleanConsumer callback);
 
     /**
@@ -280,7 +262,7 @@
      * The satellite service should check if there are any pending datagrams to be received over
      * satellite and report them via ISatelliteListener#onSatelliteDatagramsReceived.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param resultCallback The callback to receive the error code result of the operation.
      *
      * Valid error codes returned:
      *   SatelliteError:ERROR_NONE
@@ -297,15 +279,14 @@
      *   SatelliteError:SATELLITE_NOT_REACHABLE
      *   SatelliteError:NOT_AUTHORIZED
      */
-    void pollPendingSatelliteDatagrams(in IIntegerConsumer errorCallback);
+    void pollPendingSatelliteDatagrams(in IIntegerConsumer resultCallback);
 
     /**
      * Send datagram over satellite.
      *
      * @param datagram Datagram to send in byte format.
-     * @param isDemoMode Whether demo mode is enabled.
      * @param isEmergency Whether this is an emergency datagram.
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param resultCallback The callback to receive the error code result of the operation.
      *
      * Valid error codes returned:
      *   SatelliteError:ERROR_NONE
@@ -323,16 +304,16 @@
      *   SatelliteError:SATELLITE_NOT_REACHABLE
      *   SatelliteError:NOT_AUTHORIZED
      */
-    void sendSatelliteDatagram(in SatelliteDatagram datagram, in boolean isDemoMode,
-            in boolean isEmergency, in IIntegerConsumer errorCallback);
+    void sendSatelliteDatagram(in SatelliteDatagram datagram, in boolean isEmergency,
+            in IIntegerConsumer resultCallback);
 
     /**
      * Request the current satellite modem state.
      * The satellite service should report the current satellite modem state via
      * ISatelliteListener#onSatelliteModemStateChanged.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
-     *                      This must only be sent when the result is not SatelliteError#ERROR_NONE.
+     * @param resultCallback The callback to receive the error code result of the operation.
+     *                       This must only be sent when the error is not SatelliteError#ERROR_NONE.
      * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
      *                 the current satellite modem state.
      *
@@ -346,14 +327,14 @@
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      *   SatelliteError:NO_RESOURCES
      */
-    void requestSatelliteModemState(in IIntegerConsumer errorCallback,
+    void requestSatelliteModemState(in IIntegerConsumer resultCallback,
             in IIntegerConsumer callback);
 
     /**
      * Request to get whether satellite communication is allowed for the current location.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
-     *                      This must only be sent when the result is not SatelliteError#ERROR_NONE.
+     * @param resultCallback The callback to receive the error code result of the operation.
+     *                       This must only be sent when the error is not SatelliteError#ERROR_NONE.
      * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
      *                 whether satellite communication is allowed for the current location.
      *
@@ -368,15 +349,15 @@
      *   SatelliteError:NO_RESOURCES
      */
     void requestIsSatelliteCommunicationAllowedForCurrentLocation(
-            in IIntegerConsumer errorCallback, in IBooleanConsumer callback);
+            in IIntegerConsumer resultCallback, in IBooleanConsumer callback);
 
     /**
      * Request to get the time after which the satellite will be visible. This is an int
      * representing the duration in seconds after which the satellite will be visible.
      * This will return 0 if the satellite is currently visible.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
-     *                      This must only be sent when the result is not SatelliteError#ERROR_NONE.
+     * @param resultCallback The callback to receive the error code result of the operation.
+     *                       This must only be sent when the error is not SatelliteError#ERROR_NONE.
      * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
      *                 the time after which the satellite will be visible.
      *
@@ -390,6 +371,6 @@
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      *   SatelliteError:NO_RESOURCES
      */
-    void requestTimeForNextSatelliteVisibility(in IIntegerConsumer errorCallback,
+    void requestTimeForNextSatelliteVisibility(in IIntegerConsumer resultCallback,
             in IIntegerConsumer callback);
 }
diff --git a/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl b/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
index d966868..5e69215 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
@@ -43,10 +43,8 @@
 
     /**
      * Indicates that the satellite has pending datagrams for the device to be pulled.
-     *
-     * @param count Number of pending datagrams.
      */
-    void onPendingDatagramCount(in int count);
+    void onPendingDatagrams();
 
     /**
      * Indicates that the satellite pointing input has changed.
@@ -61,11 +59,4 @@
      * @param state The current satellite modem state.
      */
     void onSatelliteModemStateChanged(in SatelliteModemState state);
-
-    /**
-     * Indicates that the satellite radio technology has changed.
-     *
-     * @param technology The current satellite radio technology.
-     */
-    void onSatelliteRadioTechnologyChanged(in NTRadioTechnology technology);
 }
diff --git a/telephony/java/android/telephony/satellite/stub/PointingInfo.aidl b/telephony/java/android/telephony/satellite/stub/PointingInfo.aidl
index 83392dd..52a36d8 100644
--- a/telephony/java/android/telephony/satellite/stub/PointingInfo.aidl
+++ b/telephony/java/android/telephony/satellite/stub/PointingInfo.aidl
@@ -29,21 +29,4 @@
      * Satellite elevation in degrees.
      */
     float satelliteElevation;
-
-    /**
-     * Antenna azimuth in degrees.
-     */
-    float antennaAzimuth;
-
-    /**
-     * Angle of rotation about the x axis. This value represents the angle between a plane
-     * parallel to the device's screen and a plane parallel to the ground.
-     */
-    float antennaPitch;
-
-    /**
-     * Angle of rotation about the y axis. This value represents the angle between a plane
-     * perpendicular to the device's screen and a plane parallel to the ground.
-     */
-    float antennaRoll;
 }
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl b/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl
index 10c2ea3..cd69da1 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl
@@ -28,18 +28,12 @@
     NTRadioTechnology[] supportedRadioTechnologies;
 
     /**
-     * Whether satellite modem is always on.
-     * This indicates the power impact of keeping it on is very minimal.
-     */
-    boolean isAlwaysOn;
-
-    /**
      * Whether UE needs to point to a satellite to send and receive data.
      */
-    boolean needsPointingToSatellite;
+    boolean isPointingRequired;
 
     /**
-     * Whether UE needs a separate SIM profile to communicate with the satellite network.
+     * The maximum number of bytes per datagram that can be sent over satellite.
      */
-    boolean needsSeparateSimProfile;
+    int maxBytesPerOutgoingDatagram;
 }
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
index 711dcbe..debb394 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
@@ -70,20 +70,21 @@
         }
 
         @Override
-        public void requestSatelliteListeningEnabled(boolean enable, boolean isDemoMode,
-                int timeout, IIntegerConsumer errorCallback) throws RemoteException {
+        public void requestSatelliteListeningEnabled(boolean enable, int timeout,
+                IIntegerConsumer errorCallback) throws RemoteException {
             executeMethodAsync(
                     () -> SatelliteImplBase.this
-                            .requestSatelliteListeningEnabled(
-                                    enable, isDemoMode, timeout, errorCallback),
+                            .requestSatelliteListeningEnabled(enable, timeout, errorCallback),
                     "requestSatelliteListeningEnabled");
         }
 
         @Override
-        public void requestSatelliteEnabled(boolean enable, IIntegerConsumer errorCallback)
-                throws RemoteException {
+        public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode,
+                IIntegerConsumer errorCallback) throws RemoteException {
             executeMethodAsync(
-                    () -> SatelliteImplBase.this.requestSatelliteEnabled(enable, errorCallback),
+                    () -> SatelliteImplBase.this
+                            .requestSatelliteEnabled(
+                                    enableSatellite, enableDemoMode, errorCallback),
                     "requestSatelliteEnabled");
         }
 
@@ -131,19 +132,11 @@
         }
 
         @Override
-        public void requestMaxCharactersPerMOTextMessage(IIntegerConsumer errorCallback,
-                IIntegerConsumer callback) throws RemoteException {
+        public void provisionSatelliteService(String token, String regionId,
+                IIntegerConsumer errorCallback) throws RemoteException {
             executeMethodAsync(
                     () -> SatelliteImplBase.this
-                            .requestMaxCharactersPerMOTextMessage(errorCallback, callback),
-                    "requestMaxCharactersPerMOTextMessage");
-        }
-
-        @Override
-        public void provisionSatelliteService(String token, IIntegerConsumer errorCallback)
-                throws RemoteException {
-            executeMethodAsync(
-                    () -> SatelliteImplBase.this.provisionSatelliteService(token, errorCallback),
+                            .provisionSatelliteService(token, regionId, errorCallback),
                     "provisionSatelliteService");
         }
 
@@ -173,12 +166,11 @@
         }
 
         @Override
-        public void sendSatelliteDatagram(SatelliteDatagram datagram, boolean isDemoMode,
-                boolean isEmergency, IIntegerConsumer errorCallback) throws RemoteException {
+        public void sendSatelliteDatagram(SatelliteDatagram datagram, boolean isEmergency,
+                IIntegerConsumer errorCallback) throws RemoteException {
             executeMethodAsync(
                     () -> SatelliteImplBase.this
-                            .sendSatelliteDatagram(
-                                    datagram, isDemoMode, isEmergency, errorCallback),
+                            .sendSatelliteDatagram(datagram, isEmergency, errorCallback),
                     "sendSatelliteDatagram");
         }
 
@@ -249,7 +241,6 @@
      * Listening mode allows the satellite service to listen for incoming pages.
      *
      * @param enable True to enable satellite listening mode and false to disable.
-     * @param isDemoMode Whether demo mode is enabled.
      * @param timeout How long the satellite modem should wait for the next incoming page before
      *                disabling listening mode.
      * @param errorCallback The callback to receive the error code result of the operation.
@@ -264,17 +255,18 @@
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      *   SatelliteError:NO_RESOURCES
      */
-    public void requestSatelliteListeningEnabled(boolean enable, boolean isDemoMode, int timeout,
+    public void requestSatelliteListeningEnabled(boolean enable, int timeout,
             @NonNull IIntegerConsumer errorCallback) {
         // stub implementation
     }
 
     /**
-     * Request to enable or disable the satellite modem. If the satellite modem is enabled,
-     * this will also disable the cellular modem, and if the satellite modem is disabled,
-     * this will also re-enable the cellular modem.
+     * Request to enable or disable the satellite modem and demo mode. If the satellite modem is
+     * enabled, this may also disable the cellular modem, and if the satellite modem is disabled,
+     * this may also re-enable the cellular modem.
      *
-     * @param enable True to enable the satellite modem and false to disable.
+     * @param enableSatellite True to enable the satellite modem and false to disable.
+     * @param enableDemoMode True to enable demo mode and false to disable.
      * @param errorCallback The callback to receive the error code result of the operation.
      *
      * Valid error codes returned:
@@ -287,7 +279,8 @@
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      *   SatelliteError:NO_RESOURCES
      */
-    public void requestSatelliteEnabled(boolean enable, @NonNull IIntegerConsumer errorCallback) {
+    public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode,
+            @NonNull IIntegerConsumer errorCallback) {
         // stub implementation
     }
 
@@ -402,35 +395,13 @@
     }
 
     /**
-     * Request to get the maximum number of characters per MO text message on satellite.
-     *
-     * @param errorCallback The callback to receive the error code result of the operation.
-     *                      This must only be sent when the result is not SatelliteError#ERROR_NONE.
-     * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
-     *                 the maximum number of characters per MO text message on satellite.
-     *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
-     */
-    public void requestMaxCharactersPerMOTextMessage(@NonNull IIntegerConsumer errorCallback,
-            @NonNull IIntegerConsumer callback) {
-        // stub implementation
-    }
-
-    /**
      * Provision the device with a satellite provider.
      * This is needed if the provider allows dynamic registration.
      * Once provisioned, ISatelliteListener#onSatelliteProvisionStateChanged should report true.
      *
      * @param token The token to be used as a unique identifier for provisioning with satellite
      *              gateway.
+     * @param regionId The region ID for the device's current location.
      * @param errorCallback The callback to receive the error code result of the operation.
      *
      * Valid error codes returned:
@@ -446,7 +417,7 @@
      *   SatelliteError:REQUEST_ABORTED
      *   SatelliteError:NETWORK_TIMEOUT
      */
-    public void provisionSatelliteService(@NonNull String token,
+    public void provisionSatelliteService(@NonNull String token, @NonNull String regionId,
             @NonNull IIntegerConsumer errorCallback) {
         // stub implementation
     }
@@ -530,7 +501,6 @@
      * Send datagram over satellite.
      *
      * @param datagram Datagram to send in byte format.
-     * @param isDemoMode Whether demo mode is enabled.
      * @param isEmergency Whether this is an emergency datagram.
      * @param errorCallback The callback to receive the error code result of the operation.
      *
@@ -550,8 +520,8 @@
      *   SatelliteError:SATELLITE_NOT_REACHABLE
      *   SatelliteError:NOT_AUTHORIZED
      */
-    public void sendSatelliteDatagram(@NonNull SatelliteDatagram datagram, boolean isDemoMode,
-            boolean isEmergency, @NonNull IIntegerConsumer errorCallback) {
+    public void sendSatelliteDatagram(@NonNull SatelliteDatagram datagram, boolean isEmergency,
+            @NonNull IIntegerConsumer errorCallback) {
         // stub implementation
     }
 
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 323fa6f..d0de3ac 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -68,7 +68,7 @@
 import android.telephony.ims.aidl.IImsRegistrationCallback;
 import android.telephony.ims.aidl.IRcsConfigCallback;
 import android.telephony.satellite.ISatelliteDatagramCallback;
-import android.telephony.satellite.ISatellitePositionUpdateCallback;
+import android.telephony.satellite.ISatelliteTransmissionUpdateCallback;
 import android.telephony.satellite.ISatelliteProvisionStateCallback;
 import android.telephony.satellite.ISatelliteStateCallback;
 import android.telephony.satellite.SatelliteCapabilities;
@@ -2726,11 +2726,13 @@
      *
      * @param subId The subId of the subscription to enable or disable the satellite modem for.
      * @param enable True to enable the satellite modem and false to disable.
-     * @param callback The callback to get the error code of the request.
+     * @param isDemoModeEnabled True if demo mode is enabled and false otherwise.
+     * @param callback The callback to get the result of the request.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void requestSatelliteEnabled(int subId, boolean enable, in IIntegerConsumer callback);
+    void requestSatelliteEnabled(int subId, boolean enable, boolean isDemoModeEnabled,
+            in IIntegerConsumer callback);
 
     /**
      * Request to get whether the satellite modem is enabled.
@@ -2744,17 +2746,6 @@
     void requestIsSatelliteEnabled(int subId, in ResultReceiver receiver);
 
     /**
-     * Request to enable or disable the satellite service demo mode.
-     *
-     * @param subId The subId of the subscription to enable or disable the satellite demo mode for.
-     * @param enable True to enable the satellite demo mode and false to disable.
-     * @param callback The callback to get the error code of the request.
-     */
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
-            + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void requestSatelliteDemoModeEnabled(int subId, boolean enable, in IIntegerConsumer callback);
-
-    /**
      * Request to get whether the satellite service demo mode is enabled.
      *
      * @param subId The subId of the subscription to request whether the satellite demo mode is
@@ -2764,7 +2755,7 @@
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void requestIsSatelliteDemoModeEnabled(int subId, in ResultReceiver receiver);
+    void requestIsDemoModeEnabled(int subId, in ResultReceiver receiver);
 
     /**
      * Request to get whether the satellite service is supported on the device.
@@ -2787,39 +2778,28 @@
     void requestSatelliteCapabilities(int subId, in ResultReceiver receiver);
 
     /**
-     * Start receiving satellite pointing updates.
+     * Start receiving satellite transmission updates.
      *
-     * @param subId The subId of the subscription to stop satellite position updates for.
-     * @param errorCallback The callback to get the error code of the request.
-     * @param callback The callback to handle position updates.
+     * @param subId The subId of the subscription to stop satellite transmission updates for.
+     * @param resultCallback The callback to get the result of the request.
+     * @param callback The callback to handle transmission updates.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void startSatellitePositionUpdates(int subId, in IIntegerConsumer errorCallback,
-            in ISatellitePositionUpdateCallback callback);
+    void startSatelliteTransmissionUpdates(int subId, in IIntegerConsumer resultCallback,
+            in ISatelliteTransmissionUpdateCallback callback);
 
     /**
-     * Stop receiving satellite pointing updates.
+     * Stop receiving satellite transmission updates.
      *
-     * @param subId The subId of the subscritpion to stop satellite position updates for.
-     * @param errorCallback The callback to get the error code of the request.
-     * @param callback The callback that was passed to startSatellitePositionUpdates.
+     * @param subId The subId of the subscritpion to stop satellite transmission updates for.
+     * @param resultCallback The callback to get the result of the request.
+     * @param callback The callback that was passed to startSatelliteTransmissionUpdates.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void stopSatellitePositionUpdates(int subId, in IIntegerConsumer errorCallback,
-            in ISatellitePositionUpdateCallback callback);
-
-    /**
-     * Request to get the maximum number of bytes per datagram that can be sent to satellite.
-     *
-     * @param subId The subId of the subscription to get the maximum number of characters for.
-     * @param receiver Result receiver to get the error code of the request and the requested
-     *                 maximum number of bytes per datagram that can be sent to satellite.
-     */
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
-            + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void requestMaxSizePerSendingDatagram(int subId, in ResultReceiver receiver);
+    void stopSatelliteTransmissionUpdates(int subId, in IIntegerConsumer resultCallback,
+            in ISatelliteTransmissionUpdateCallback callback);
 
     /**
      * Register the subscription with a satellite provider.
@@ -2828,13 +2808,14 @@
      * @param subId The subId of the subscription to be provisioned.
      * @param token The token to be used as a unique identifier for provisioning with satellite
      *              gateway.
-     * @param callback The callback to get the error code of the request.
+     * @param regionId The region ID for the device's current location.
+     * @param callback The callback to get the result of the request.
      *
      * @return The signal transport used by callers to cancel the provision request.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    ICancellationSignal provisionSatelliteService(int subId, in String token,
+    ICancellationSignal provisionSatelliteService(int subId, in String token, in String regionId,
             in IIntegerConsumer callback);
 
     /**
@@ -2846,7 +2827,7 @@
      *
      * @param subId The subId of the subscription to be deprovisioned.
      * @param token The token of the device/subscription to be deprovisioned.
-     * @param callback The callback to get the error code of the request.
+     * @param callback The callback to get the result of the request.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
@@ -2916,15 +2897,13 @@
      * Register to receive incoming datagrams over satellite.
      *
      * @param subId The subId of the subscription to register for incoming satellite datagrams.
-     * @param datagramType Type of datagram.
      * @param callback The callback to handle the incoming datagrams.
      *
      * @return The {@link SatelliteError} result of the operation.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    int registerForSatelliteDatagram(
-            int subId, int datagramType, ISatelliteDatagramCallback callback);
+    int registerForSatelliteDatagram(int subId, ISatelliteDatagramCallback callback);
 
    /**
      * Unregister to stop receiving incoming datagrams over satellite.
@@ -2941,7 +2920,7 @@
     * Poll pending satellite datagrams over satellite.
     *
     * @param subId The subId of the subscription used for receiving datagrams.
-    * @param callback The callback to get the error code of the request.
+    * @param callback The callback to get the result of the request.
     */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
                 + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
@@ -2955,7 +2934,7 @@
     * @param datagram Datagram to send over satellite.
     * @param needFullScreenPointingUI this is used to indicate pointingUI app to open in
     *                                 full screen mode.
-    * @param callback The callback to get the error code of the request.
+    * @param callback The callback to get the result of the request.
     */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
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/EnforcePermission/Android.bp b/tests/EnforcePermission/Android.bp
new file mode 100644
index 0000000..719a898
--- /dev/null
+++ b/tests/EnforcePermission/Android.bp
@@ -0,0 +1,22 @@
+// 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 {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+filegroup {
+    name: "frameworks-enforce-permission-test-aidl",
+    srcs: ["aidl/**/*.aidl"],
+}
diff --git a/tests/EnforcePermission/TEST_MAPPING b/tests/EnforcePermission/TEST_MAPPING
new file mode 100644
index 0000000..a1bf42a
--- /dev/null
+++ b/tests/EnforcePermission/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "EnforcePermissionTests"
+    }
+  ]
+}
diff --git a/tests/EnforcePermission/aidl/android/tests/enforcepermission/INested.aidl b/tests/EnforcePermission/aidl/android/tests/enforcepermission/INested.aidl
new file mode 100644
index 0000000..1eb773d
--- /dev/null
+++ b/tests/EnforcePermission/aidl/android/tests/enforcepermission/INested.aidl
@@ -0,0 +1,25 @@
+/*
+ * 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.tests.enforcepermission;
+
+interface INested {
+    @EnforcePermission("ACCESS_NETWORK_STATE")
+    void ProtectedByAccessNetworkState();
+
+    @EnforcePermission("READ_SYNC_SETTINGS")
+    void ProtectedByReadSyncSettings();
+}
diff --git a/tests/EnforcePermission/aidl/android/tests/enforcepermission/IProtected.aidl b/tests/EnforcePermission/aidl/android/tests/enforcepermission/IProtected.aidl
new file mode 100644
index 0000000..18e3aec
--- /dev/null
+++ b/tests/EnforcePermission/aidl/android/tests/enforcepermission/IProtected.aidl
@@ -0,0 +1,34 @@
+/*
+ * 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.tests.enforcepermission;
+
+interface IProtected {
+    @EnforcePermission("INTERNET")
+    void ProtectedByInternet();
+
+    @EnforcePermission("VIBRATE")
+    void ProtectedByVibrate();
+
+    @EnforcePermission("INTERNET")
+    void ProtectedByInternetAndVibrateImplicitly();
+
+    @EnforcePermission("INTERNET")
+    void ProtectedByInternetAndAccessNetworkStateImplicitly();
+
+    @EnforcePermission("INTERNET")
+    void ProtectedByInternetAndReadSyncSettingsImplicitly();
+}
diff --git a/tests/EnforcePermission/service-app/Android.bp b/tests/EnforcePermission/service-app/Android.bp
new file mode 100644
index 0000000..a4ac1d7
--- /dev/null
+++ b/tests/EnforcePermission/service-app/Android.bp
@@ -0,0 +1,32 @@
+// 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 {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test_helper_app {
+    name: "EnforcePermissionTestHelper",
+    srcs: [
+        "src/**/*.java",
+        ":frameworks-enforce-permission-test-aidl",
+    ],
+    platform_apis: true,
+    certificate: "platform",
+}
diff --git a/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r1000.xml b/tests/EnforcePermission/service-app/AndroidManifest.xml
similarity index 64%
copy from services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r1000.xml
copy to tests/EnforcePermission/service-app/AndroidManifest.xml
index 9bf9254..ddafe15 100644
--- a/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r1000.xml
+++ b/tests/EnforcePermission/service-app/AndroidManifest.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 The Android Open Source Project
+<!-- 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.
@@ -14,13 +14,14 @@
      limitations under the License.
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.frameworks.servicestests.install_uses_sdk">
-
-    <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29">
-        <!-- This will fail to install, because minExtensionVersion is not met -->
-        <extension-sdk android:sdkVersion="30" android:minExtensionVersion="1000" />
-    </uses-sdk>
-
+  package="android.tests.enforcepermission.service">
     <application>
+        <service
+          android:name=".TestService"
+          android:exported="true" />
+
+        <service
+          android:name=".NestedTestService"
+          android:exported="true" />
     </application>
 </manifest>
diff --git a/tests/EnforcePermission/service-app/src/android/tests/enforcepermission/service/NestedTestService.java b/tests/EnforcePermission/service-app/src/android/tests/enforcepermission/service/NestedTestService.java
new file mode 100644
index 0000000..7879a12
--- /dev/null
+++ b/tests/EnforcePermission/service-app/src/android/tests/enforcepermission/service/NestedTestService.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 android.tests.enforcepermission.service;
+
+import android.annotation.EnforcePermission;
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.tests.enforcepermission.INested;
+import android.util.Log;
+
+public class NestedTestService extends Service {
+    private static final String TAG = "EnforcePermission.NestedTestService";
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        Log.i(TAG, "onBind");
+        return mBinder;
+    }
+
+    private final INested.Stub mBinder = new INested.Stub() {
+        @Override
+        @EnforcePermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+        public void ProtectedByAccessNetworkState() {
+            ProtectedByAccessNetworkState_enforcePermission();
+        }
+
+        @Override
+        @EnforcePermission(android.Manifest.permission.READ_SYNC_SETTINGS)
+        public void ProtectedByReadSyncSettings() {
+            ProtectedByReadSyncSettings_enforcePermission();
+        }
+    };
+}
diff --git a/tests/EnforcePermission/service-app/src/android/tests/enforcepermission/service/TestService.java b/tests/EnforcePermission/service-app/src/android/tests/enforcepermission/service/TestService.java
new file mode 100644
index 0000000..e9b897d
--- /dev/null
+++ b/tests/EnforcePermission/service-app/src/android/tests/enforcepermission/service/TestService.java
@@ -0,0 +1,119 @@
+/**
+ * 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.tests.enforcepermission.service;
+
+import android.annotation.EnforcePermission;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.tests.enforcepermission.INested;
+import android.tests.enforcepermission.IProtected;
+import android.util.Log;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+public class TestService extends Service {
+
+    private static final String TAG = "EnforcePermission.TestService";
+    private volatile ServiceConnection mNestedServiceConnection;
+
+    @Override
+    public void onCreate() {
+        mNestedServiceConnection = new ServiceConnection();
+        Intent intent = new Intent(this, NestedTestService.class);
+        boolean bound = bindService(intent, mNestedServiceConnection, Context.BIND_AUTO_CREATE);
+        if (!bound) {
+            Log.wtf(TAG, "bindService() on NestedTestService failed");
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        unbindService(mNestedServiceConnection);
+    }
+
+    private static final class ServiceConnection implements android.content.ServiceConnection {
+        private volatile CompletableFuture<INested> mFuture = new CompletableFuture<>();
+
+        public INested get() {
+            try {
+                return mFuture.get(1, TimeUnit.SECONDS);
+            } catch (ExecutionException | InterruptedException | TimeoutException e) {
+                throw new RuntimeException("Unable to reach NestedTestService: " + e.getMessage());
+            }
+        }
+
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            mFuture.complete(INested.Stub.asInterface(service));
+        }
+
+        public void onServiceDisconnected(ComponentName className) {
+            mFuture = new CompletableFuture<>();
+        }
+    };
+
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+
+    private final IProtected.Stub mBinder = new IProtected.Stub() {
+        @Override
+        @EnforcePermission(android.Manifest.permission.INTERNET)
+        public void ProtectedByInternet() {
+            ProtectedByInternet_enforcePermission();
+        }
+
+        @Override
+        @EnforcePermission(android.Manifest.permission.VIBRATE)
+        public void ProtectedByVibrate() {
+            ProtectedByVibrate_enforcePermission();
+        }
+
+        @Override
+        @EnforcePermission(android.Manifest.permission.INTERNET)
+        public void ProtectedByInternetAndVibrateImplicitly() {
+            ProtectedByInternetAndVibrateImplicitly_enforcePermission();
+
+            ProtectedByVibrate();
+        }
+
+        @Override
+        @EnforcePermission(android.Manifest.permission.INTERNET)
+        public void ProtectedByInternetAndAccessNetworkStateImplicitly() throws RemoteException {
+            ProtectedByInternetAndAccessNetworkStateImplicitly_enforcePermission();
+
+            mNestedServiceConnection.get().ProtectedByAccessNetworkState();
+
+        }
+
+        @Override
+        @EnforcePermission(android.Manifest.permission.INTERNET)
+        public void ProtectedByInternetAndReadSyncSettingsImplicitly() throws RemoteException {
+            ProtectedByInternetAndReadSyncSettingsImplicitly_enforcePermission();
+
+            mNestedServiceConnection.get().ProtectedByReadSyncSettings();
+        }
+    };
+}
diff --git a/tests/EnforcePermission/test-app/Android.bp b/tests/EnforcePermission/test-app/Android.bp
new file mode 100644
index 0000000..cd53854
--- /dev/null
+++ b/tests/EnforcePermission/test-app/Android.bp
@@ -0,0 +1,38 @@
+// 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 {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+    name: "EnforcePermissionTests",
+    srcs: [
+        "src/**/*.java",
+        ":frameworks-enforce-permission-test-aidl",
+    ],
+    static_libs: [
+        "androidx.test.rules",
+    ],
+    libs: [
+        "android.test.base",
+        "android.test.runner",
+    ],
+    data: [
+        ":EnforcePermissionTestHelper",
+    ],
+    platform_apis: true,
+    certificate: "platform",
+    test_suites: ["device-tests"],
+}
diff --git a/tests/EnforcePermission/test-app/AndroidManifest.xml b/tests/EnforcePermission/test-app/AndroidManifest.xml
new file mode 100644
index 0000000..4a0c6a8
--- /dev/null
+++ b/tests/EnforcePermission/test-app/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+  package="android.tests.enforcepermission.tests">
+
+    <!-- Expected for the tests (not actually used) -->
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
+
+    <queries>
+        <package android:name="android.tests.enforcepermission.service" />
+    </queries>
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.tests.enforcepermission.tests"/>
+</manifest>
diff --git a/tests/EnforcePermission/test-app/AndroidTest.xml b/tests/EnforcePermission/test-app/AndroidTest.xml
new file mode 100644
index 0000000..120381a
--- /dev/null
+++ b/tests/EnforcePermission/test-app/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+<configuration description="Runs EnforcePermission End-to-End Tests">
+    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+      <option name="test-file-name" value="EnforcePermissionTestHelper.apk"/>
+      <option name="test-file-name" value="EnforcePermissionTests.apk"/>
+      <option name="cleanup-apks" value="true" />
+    </target_preparer>
+
+    <option name="test-tag" value="EnforcePermissionTests"/>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="android.tests.enforcepermission.tests"/>
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner"/>
+    </test>
+</configuration>
diff --git a/tests/EnforcePermission/test-app/src/android/tests/enforcepermission/tests/ServiceTest.java b/tests/EnforcePermission/test-app/src/android/tests/enforcepermission/tests/ServiceTest.java
new file mode 100644
index 0000000..d2a4a03
--- /dev/null
+++ b/tests/EnforcePermission/test-app/src/android/tests/enforcepermission/tests/ServiceTest.java
@@ -0,0 +1,129 @@
+/**
+ * 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.tests.enforcepermission.tests;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.tests.enforcepermission.IProtected;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+@RunWith(AndroidJUnit4.class)
+public class ServiceTest {
+
+    private static final String TAG = "EnforcePermission.Tests";
+    private static final String SERVICE_NAME = "android.tests.enforcepermission.service";
+    private static final int SERVICE_TIMEOUT_SEC = 5;
+
+    private Context mContext;
+    private volatile ServiceConnection mServiceConnection;
+
+    @Before
+    public void bindTestService() throws Exception {
+        Log.d(TAG, "bindTestService");
+        mContext = InstrumentationRegistry.getTargetContext();
+        mServiceConnection = new ServiceConnection();
+        Intent intent = new Intent();
+        intent.setClassName(SERVICE_NAME, SERVICE_NAME + ".TestService");
+        assertTrue(mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE));
+    }
+
+    @After
+    public void unbindTestService() throws Exception {
+        mContext.unbindService(mServiceConnection);
+    }
+
+    private static final class ServiceConnection implements android.content.ServiceConnection {
+        private volatile CompletableFuture<IProtected> mFuture = new CompletableFuture<>();
+
+        @Override
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            mFuture.complete(IProtected.Stub.asInterface(service));
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName className) {
+            mFuture = new CompletableFuture<>();
+        }
+
+        public IProtected get() {
+            try {
+                return mFuture.get(SERVICE_TIMEOUT_SEC, TimeUnit.SECONDS);
+            } catch (ExecutionException | InterruptedException | TimeoutException e) {
+                throw new RuntimeException("Unable to reach TestService: " + e.toString());
+            }
+        }
+    }
+
+    @Test
+    public void testImmediatePermissionGranted_succeeds()
+            throws RemoteException {
+        mServiceConnection.get().ProtectedByInternet();
+    }
+
+    @Test
+    public void testImmediatePermissionNotGranted_fails()
+            throws RemoteException {
+        final Exception ex = assertThrows(SecurityException.class,
+                () -> mServiceConnection.get().ProtectedByVibrate());
+        assertThat(ex.getMessage(), containsString("VIBRATE"));
+    }
+
+    @Test
+    public void testImmediatePermissionGrantedButImplicitLocalNotGranted_fails()
+            throws RemoteException {
+        final Exception ex = assertThrows(SecurityException.class,
+                () -> mServiceConnection.get().ProtectedByInternetAndVibrateImplicitly());
+        assertThat(ex.getMessage(), containsString("VIBRATE"));
+    }
+
+    @Test
+    public void testImmediatePermissionGrantedButImplicitNestedNotGranted_fails()
+            throws RemoteException {
+        final Exception ex = assertThrows(SecurityException.class,
+                () -> mServiceConnection.get()
+                      .ProtectedByInternetAndAccessNetworkStateImplicitly());
+        assertThat(ex.getMessage(), containsString("ACCESS_NETWORK_STATE"));
+    }
+
+    @Test
+    public void testImmediatePermissionGrantedAndImplicitNestedGranted_succeeds()
+            throws RemoteException {
+        mServiceConnection.get().ProtectedByInternetAndReadSyncSettingsImplicitly();
+    }
+}
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/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
index 8fdbb64..d0dc42f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.server.wm.flicker.launch
 
+import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.annotation.FlickerServiceCompatible
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
@@ -72,6 +73,10 @@
     /** {@inheritDoc} */
     @Presubmit @Test override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
 
+    @Postsubmit
+    @Test
+    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
+
     companion object {
         /**
          * Creates the test configurations.
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTestCfArm.kt
new file mode 100644
index 0000000..f75d9ee
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTestCfArm.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.FlakyTest
+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.Test
+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 OpenAppColdTestCfArm(flicker: FlickerTest) : OpenAppColdTest(flicker) {
+    @FlakyTest(bugId = 273696733)
+    @Test
+    override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
+
+    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/Internal/src/com/android/internal/app/LocaleStoreTest.java b/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java
index e704cc2..46ebfed 100644
--- a/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java
+++ b/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java
@@ -35,6 +35,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.IllformedLocaleException;
 import java.util.List;
 import java.util.Locale;
@@ -141,14 +142,118 @@
 
         HashMap<String, LocaleInfo> result =
                 LocaleStore.convertExplicitLocales(locales, supportedLocale);
-
         assertEquals("en", result.get("en").getId());
         assertEquals("en-US", result.get("en-US").getId());
         assertNull(result.get("en-Latn-US"));
     }
 
+    @Test
+    public void getLevelLocales_languageTier_returnAllSupportLanguages() {
+        LocaleList testSupportedLocales =
+                LocaleList.forLanguageTags(
+                        "en-US,zh-Hant-TW,ja-JP,en-GB,bn-IN-u-nu-arab,ks-Arab-IN,bn-IN");
+
+        Set<String> ignorableLocales = new HashSet<>();
+        ignorableLocales.add("zh-Hant-HK");
+        LocaleInfo parent = null;
+
+        Set<LocaleInfo> localeInfos = LocaleStore.getLevelLocales(
+                null, ignorableLocales, parent, false, testSupportedLocales);
+
+        assertEquals(5, localeInfos.size());
+        localeInfos.forEach(localeInfo -> {
+            assertTrue(localeInfo.getLocale().getCountry().isEmpty());
+        });
+        assertTrue(localeInfos.stream().anyMatch(
+                info -> info.getLocale().toLanguageTag().equals("en")));
+        assertTrue(localeInfos.stream().anyMatch(
+                info -> info.getLocale().toLanguageTag().equals("zh-Hant")));
+        assertTrue(localeInfos.stream().anyMatch(
+                info -> info.getLocale().toLanguageTag().equals("ja")));
+        assertTrue(localeInfos.stream().anyMatch(
+                info -> info.getLocale().toLanguageTag().equals("bn")));
+        assertTrue(localeInfos.stream().anyMatch(
+                info -> info.getLocale().toLanguageTag().equals("ks-Arab")));
+    }
+
+    @Test
+    public void getLevelLocales_regionTierAndParentIsEn_returnEnLocales() {
+        LocaleList testSupportedLocales =
+                LocaleList.forLanguageTags(
+                        "en-US,en-GB,bn-IN-u-nu-arab,ks-Arab-IN,en-ZA,bn-IN");
+        Set<String> ignorableLocales = new HashSet<>();
+        ignorableLocales.add("zh-Hant-HK");
+        LocaleInfo parent = LocaleStore.fromLocale(Locale.forLanguageTag("en"));
+
+        Set<LocaleInfo> localeInfos = LocaleStore.getLevelLocales(
+                null, ignorableLocales, parent, false, testSupportedLocales);
+
+        assertEquals(3, localeInfos.size());
+        localeInfos.forEach(localeInfo -> {
+            assertEquals("en", localeInfo.getLocale().getLanguage());
+        });
+        assertTrue(localeInfos.stream().anyMatch(
+                info -> info.getLocale().toLanguageTag().equals("en-US")));
+        assertTrue(localeInfos.stream().anyMatch(
+                info -> info.getLocale().toLanguageTag().equals("en-GB")));
+        assertTrue(localeInfos.stream().anyMatch(
+                info -> info.getLocale().toLanguageTag().equals("en-ZA")));
+    }
+
+    @Test
+    public void getLevelLocales_numberingTierAndParentIsBnIn_returnBnInLocales() {
+        LocaleList testSupportedLocales =
+                LocaleList.forLanguageTags(
+                        "en-US,zh-Hant-TW,bn-IN-u-nu-arab,ks-Arab-IN,en-ZA,bn-IN,bn-IN-u-nu-adlm");
+        Set<String> ignorableLocales = new HashSet<>();
+        ignorableLocales.add("zh-Hant-HK");
+        LocaleInfo parent = LocaleStore.fromLocale(Locale.forLanguageTag("bn"));
+
+        Set<LocaleInfo> localeInfos = LocaleStore.getLevelLocales(
+                null, ignorableLocales, parent, false, testSupportedLocales);
+
+        assertEquals(1, localeInfos.size());
+        assertEquals("bn-IN", localeInfos.iterator().next().getLocale().toLanguageTag());
+    }
+
+    @Test
+    public void getLevelLocales_regionTierAndParentIsBnInAndIgnoreBn_returnEmpty() {
+        LocaleList testSupportedLocales =
+                LocaleList.forLanguageTags(
+                        "en-US,zh-Hant-TW,bn-IN-u-nu-arab,ks-Arab-IN,en-ZA,bn-IN,bn-IN-u-nu-adlm");
+        Set<String> ignorableLocales = new HashSet<>();
+        ignorableLocales.add("bn-IN");
+        LocaleInfo parent = LocaleStore.fromLocale(Locale.forLanguageTag("bn-IN"));
+
+        Set<LocaleInfo> localeInfos = LocaleStore.getLevelLocales(
+                null, ignorableLocales, parent, false, testSupportedLocales);
+
+        assertEquals(0, localeInfos.size());
+    }
+
+    @Test
+    public void getLevelLocales_regionTierAndParentIsBnIn_returnBnLocaleFamily() {
+        LocaleList testSupportedLocales =
+                LocaleList.forLanguageTags(
+                        "en-US,zh-Hant-TW,bn-IN-u-nu-arab,ks-Arab-IN,en-ZA,bn-IN,bn-IN-u-nu-adlm");
+        Set<String> ignorableLocales = new HashSet<>();
+        ignorableLocales.add("en-US");
+        LocaleInfo parent = LocaleStore.fromLocale(Locale.forLanguageTag("bn-IN"));
+
+        Set<LocaleInfo> localeInfos = LocaleStore.getLevelLocales(
+                null, ignorableLocales, parent, false, testSupportedLocales);
+
+        assertEquals(3, localeInfos.size());
+        assertTrue(localeInfos.stream().anyMatch(
+                info -> info.getLocale().toLanguageTag().equals("bn-IN-u-nu-adlm")));
+        assertTrue(localeInfos.stream().anyMatch(
+                info -> info.getLocale().toLanguageTag().equals("bn-IN-u-nu-arab")));
+        assertTrue(localeInfos.stream().anyMatch(
+                info -> info.getLocale().toLanguageTag().equals("bn-IN")));
+    }
+
     private ArrayList<LocaleInfo> getFakeSupportedLocales() {
-        String[] locales = {"en-US", "zh-Hant-TW", "ja-JP", "en-GB"};
+        String[] locales = {"en-US", "zh-Hant-TW", "ja-JP", "en-GB", "en-US-u-nu-arab"};
         ArrayList<LocaleInfo> supportedLocales = new ArrayList<>();
         for (String localeTag : locales) {
             supportedLocales.add(LocaleStore.fromLocale(Locale.forLanguageTag(localeTag)));
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/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java
index 4809bef..63e471b 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 Intent mInstantTetherSettingsIntent;
+        private final Context mContext;
         private Bundle mExtras;
 
-        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,22 @@
          */
         @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, Bundle extras) {
+
         mInstantTetherEnabled = instantTetherEnabled;
+        mInstantTetherSettingsPendingIntent = pendingIntent;
         mExtras = extras;
     }
 
@@ -96,6 +129,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 +152,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 +169,7 @@
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
+        mInstantTetherSettingsPendingIntent.writeToParcel(dest, 0);
         dest.writeBoolean(mInstantTetherEnabled);
         dest.writeBundle(mExtras);
     }
@@ -135,8 +181,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 +204,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/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();
+    }
 }