Merge "Log power & face auth wake reasons." 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/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index 7f02cb3..2f6b689 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -432,6 +432,7 @@
     @UnsupportedAppUsage
     private final ComponentName service;
     private final int constraintFlags;
+    private final int mPreferredConstraintFlags;
     private final TriggerContentUri[] triggerContentUris;
     private final long triggerContentUpdateDelay;
     private final long triggerContentMaxDelay;
@@ -522,6 +523,30 @@
     }
 
     /**
+     * @hide
+     * @see JobInfo.Builder#setPrefersBatteryNotLow(boolean)
+     */
+    public boolean isPreferBatteryNotLow() {
+        return (mPreferredConstraintFlags & CONSTRAINT_FLAG_BATTERY_NOT_LOW) != 0;
+    }
+
+    /**
+     * @hide
+     * @see JobInfo.Builder#setPrefersCharging(boolean)
+     */
+    public boolean isPreferCharging() {
+        return (mPreferredConstraintFlags & CONSTRAINT_FLAG_CHARGING) != 0;
+    }
+
+    /**
+     * @hide
+     * @see JobInfo.Builder#setPrefersDeviceIdle(boolean)
+     */
+    public boolean isPreferDeviceIdle() {
+        return (mPreferredConstraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0;
+    }
+
+    /**
      * @see JobInfo.Builder#setRequiresCharging(boolean)
      */
     public boolean isRequireCharging() {
@@ -557,6 +582,13 @@
     }
 
     /**
+     * @hide
+     */
+    public int getPreferredConstraintFlags() {
+        return mPreferredConstraintFlags;
+    }
+
+    /**
      * Which content: URIs must change for the job to be scheduled.  Returns null
      * if there are none required.
      * @see JobInfo.Builder#addTriggerContentUri(TriggerContentUri)
@@ -800,6 +832,9 @@
         if (constraintFlags != j.constraintFlags) {
             return false;
         }
+        if (mPreferredConstraintFlags != j.mPreferredConstraintFlags) {
+            return false;
+        }
         if (!Arrays.equals(triggerContentUris, j.triggerContentUris)) {
             return false;
         }
@@ -880,6 +915,7 @@
             hashCode = 31 * hashCode + service.hashCode();
         }
         hashCode = 31 * hashCode + constraintFlags;
+        hashCode = 31 * hashCode + mPreferredConstraintFlags;
         if (triggerContentUris != null) {
             hashCode = 31 * hashCode + Arrays.hashCode(triggerContentUris);
         }
@@ -922,6 +958,7 @@
         }
         service = in.readParcelable(null);
         constraintFlags = in.readInt();
+        mPreferredConstraintFlags = in.readInt();
         triggerContentUris = in.createTypedArray(TriggerContentUri.CREATOR);
         triggerContentUpdateDelay = in.readLong();
         triggerContentMaxDelay = in.readLong();
@@ -956,6 +993,7 @@
         clipGrantFlags = b.mClipGrantFlags;
         service = b.mJobService;
         constraintFlags = b.mConstraintFlags;
+        mPreferredConstraintFlags = b.mPreferredConstraintFlags;
         triggerContentUris = b.mTriggerContentUris != null
                 ? b.mTriggerContentUris.toArray(new TriggerContentUri[b.mTriggerContentUris.size()])
                 : null;
@@ -999,6 +1037,7 @@
         }
         out.writeParcelable(service, flags);
         out.writeInt(constraintFlags);
+        out.writeInt(mPreferredConstraintFlags);
         out.writeTypedArray(triggerContentUris, flags);
         out.writeLong(triggerContentUpdateDelay);
         out.writeLong(triggerContentMaxDelay);
@@ -1146,6 +1185,7 @@
         private int mFlags;
         // Requirements.
         private int mConstraintFlags;
+        private int mPreferredConstraintFlags;
         private NetworkRequest mNetworkRequest;
         private long mNetworkDownloadBytes = NETWORK_BYTES_UNKNOWN;
         private long mNetworkUploadBytes = NETWORK_BYTES_UNKNOWN;
@@ -1199,6 +1239,7 @@
             mBias = job.getBias();
             mFlags = job.getFlags();
             mConstraintFlags = job.getConstraintFlags();
+            mPreferredConstraintFlags = job.getPreferredConstraintFlags();
             mNetworkRequest = job.getRequiredNetwork();
             mNetworkDownloadBytes = job.getEstimatedNetworkDownloadBytes();
             mNetworkUploadBytes = job.getEstimatedNetworkUploadBytes();
@@ -1341,9 +1382,6 @@
          * Calling this method will override any requirements previously defined
          * by {@link #setRequiredNetwork(NetworkRequest)}; you typically only
          * want to call one of these methods.
-         * <p> Starting in Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
-         * {@link JobScheduler} may try to shift the execution of jobs requiring
-         * {@link #NETWORK_TYPE_ANY} to when there is access to an un-metered network.
          * <p class="note">
          * When your job executes in
          * {@link JobService#onStartJob(JobParameters)}, be sure to use the
@@ -1505,10 +1543,105 @@
         }
 
         /**
-         * Specify that to run this job, the device must be charging (or be a
+         * Specify that this job would prefer to be run when the device's battery is not low.
+         * This defaults to {@code false}.
+         *
+         * <p>The system may attempt to delay this job until the device's battery is not low,
+         * but may choose to run it even if the device's battery is low. JobScheduler will not stop
+         * this job if this constraint is no longer satisfied after the job has started running.
+         * If this job must only run when the device's battery is not low,
+         * use {@link #setRequiresBatteryNotLow(boolean)} instead.
+         *
+         * <p>
+         * Because it doesn't make sense for a constraint to be both preferred and required,
+         * calling both this and {@link #setRequiresBatteryNotLow(boolean)} with {@code true}
+         * will result in an {@link java.lang.IllegalArgumentException} when
+         * {@link android.app.job.JobInfo.Builder#build()} is called.
+         *
+         * @param prefersBatteryNotLow Pass {@code true} to prefer that the device's battery level
+         *                             not be low in order to run the job.
+         * @return This object for method chaining
+         * @see JobInfo#isPreferBatteryNotLow()
+         * @hide
+         */
+        @NonNull
+        public Builder setPrefersBatteryNotLow(boolean prefersBatteryNotLow) {
+            mPreferredConstraintFlags =
+                    (mPreferredConstraintFlags & ~CONSTRAINT_FLAG_BATTERY_NOT_LOW)
+                            | (prefersBatteryNotLow ? CONSTRAINT_FLAG_BATTERY_NOT_LOW : 0);
+            return this;
+        }
+
+        /**
+         * Specify that this job would prefer to be run when the device is charging (or be a
          * non-battery-powered device connected to permanent power, such as Android TV
          * devices). This defaults to {@code false}.
          *
+         * <p>
+         * The system may attempt to delay this job until the device is charging, but may
+         * choose to run it even if the device is not charging. JobScheduler will not stop
+         * this job if this constraint is no longer satisfied after the job has started running.
+         * If this job must only run when the device is charging,
+         * use {@link #setRequiresCharging(boolean)} instead.
+         *
+         * <p>
+         * Because it doesn't make sense for a constraint to be both preferred and required,
+         * calling both this and {@link #setRequiresCharging(boolean)} with {@code true}
+         * will result in an {@link java.lang.IllegalArgumentException} when
+         * {@link android.app.job.JobInfo.Builder#build()} is called.
+         *
+         * @param prefersCharging Pass {@code true} to prefer that the device be
+         *                        charging in order to run the job.
+         * @return This object for method chaining
+         * @see JobInfo#isPreferCharging()
+         * @hide
+         */
+        @NonNull
+        public Builder setPrefersCharging(boolean prefersCharging) {
+            mPreferredConstraintFlags = (mPreferredConstraintFlags & ~CONSTRAINT_FLAG_CHARGING)
+                    | (prefersCharging ? CONSTRAINT_FLAG_CHARGING : 0);
+            return this;
+        }
+
+        /**
+         * Specify that this job would prefer to be run when the device is not in active use.
+         * This defaults to {@code false}.
+         *
+         * <p>The system may attempt to delay this job until the device is not in active use,
+         * but may choose to run it even if the device is not idle. JobScheduler will not stop
+         * this job if this constraint is no longer satisfied after the job has started running.
+         * If this job must only run when the device is not in active use,
+         * use {@link #setRequiresDeviceIdle(boolean)} instead.
+         *
+         * <p>
+         * Because it doesn't make sense for a constraint to be both preferred and required,
+         * calling both this and {@link #setRequiresDeviceIdle(boolean)} with {@code true}
+         * will result in an {@link java.lang.IllegalArgumentException} when
+         * {@link android.app.job.JobInfo.Builder#build()} is called.
+         *
+         * <p class="note">Despite the similar naming, this job constraint is <em>not</em>
+         * related to the system's "device idle" or "doze" states.  This constraint only
+         * determines whether a job is allowed to run while the device is directly in use.
+         *
+         * @param prefersDeviceIdle Pass {@code true} to prefer that the device not be in active
+         *                          use when running this job.
+         * @return This object for method chaining
+         * @see JobInfo#isRequireDeviceIdle()
+         * @hide
+         */
+        @NonNull
+        public Builder setPrefersDeviceIdle(boolean prefersDeviceIdle) {
+            mPreferredConstraintFlags = (mPreferredConstraintFlags & ~CONSTRAINT_FLAG_DEVICE_IDLE)
+                    | (prefersDeviceIdle ? CONSTRAINT_FLAG_DEVICE_IDLE : 0);
+            return this;
+        }
+
+        /**
+         * Specify that to run this job, the device must be charging (or be a
+         * non-battery-powered device connected to permanent power, such as Android TV
+         * devices). This defaults to {@code false}. Setting this to {@code false} <b>DOES NOT</b>
+         * mean the job will only run when the device is not charging.
+         *
          * <p class="note">For purposes of running jobs, a battery-powered device
          * "charging" is not quite the same as simply being connected to power.  If the
          * device is so busy that the battery is draining despite a power connection, jobs
@@ -1530,7 +1663,9 @@
          * Specify that to run this job, the device's battery level must not be low.
          * This defaults to false.  If true, the job will only run when the battery level
          * is not low, which is generally the point where the user is given a "low battery"
-         * warning.
+         * warning. Setting this to {@code false} <b>DOES NOT</b> mean the job will only run
+         * when the battery is low.
+         *
          * @param batteryNotLow Whether or not the device's battery level must not be low.
          * @see JobInfo#isRequireBatteryNotLow()
          */
@@ -1543,7 +1678,8 @@
         /**
          * When set {@code true}, ensure that this job will not run if the device is in active use.
          * The default state is {@code false}: that is, the for the job to be runnable even when
-         * someone is interacting with the device.
+         * someone is interacting with the device. Setting this to {@code false} <b>DOES NOT</b>
+         * mean the job will only run when the device is not idle.
          *
          * <p>This state is a loose definition provided by the system. In general, it means that
          * the device is not currently being used interactively, and has not been in use for some
@@ -2156,6 +2292,29 @@
             }
         }
 
+        if ((constraintFlags & mPreferredConstraintFlags) != 0) {
+            // Something is marked as both preferred and required. Try to give a clear exception
+            // reason.
+            if ((constraintFlags & CONSTRAINT_FLAG_BATTERY_NOT_LOW) != 0
+                    && (mPreferredConstraintFlags & CONSTRAINT_FLAG_BATTERY_NOT_LOW) != 0) {
+                throw new IllegalArgumentException(
+                        "battery-not-low constraint cannot be both preferred and required");
+            }
+            if ((constraintFlags & CONSTRAINT_FLAG_CHARGING) != 0
+                    && (mPreferredConstraintFlags & CONSTRAINT_FLAG_CHARGING) != 0) {
+                throw new IllegalArgumentException(
+                        "charging constraint cannot be both preferred and required");
+            }
+            if ((constraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0
+                    && (mPreferredConstraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0) {
+                throw new IllegalArgumentException(
+                        "device idle constraint cannot be both preferred and required");
+            }
+            // Couldn't figure out what the overlap was. Just use a generic message.
+            throw new IllegalArgumentException(
+                    "constraints cannot be both preferred and required");
+        }
+
         if (isUserInitiated) {
             if (hasEarlyConstraint) {
                 throw new IllegalArgumentException("A user-initiated job cannot have a time delay");
@@ -2173,7 +2332,8 @@
             if (mPriority != PRIORITY_MAX) {
                 throw new IllegalArgumentException("A user-initiated job must be max priority.");
             }
-            if ((constraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0) {
+            if ((constraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0
+                    || (mPreferredConstraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0) {
                 throw new IllegalArgumentException(
                         "A user-initiated job cannot have a device-idle constraint");
             }
diff --git a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
index 9b64edf..f50a902 100644
--- a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
@@ -225,6 +225,8 @@
 
     void setActiveAdminApps(Set<String> adminPkgs, int userId);
 
+    void setAdminProtectedPackages(Set<String> packageNames, int userId);
+
     /**
      * @return {@code true} if the given package is an active device admin app.
      */
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..08810b5 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_USE_DATA_ESTIMATES_FOR_LIMITS:
                             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_USE_DATA_ESTIMATES_FOR_LIMITS =
+                "runtime_use_data_estimates_for_limits";
 
         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,17 @@
         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 =
-                Math.max(10 * MINUTE_IN_MILLIS, DEFAULT_RUNTIME_MIN_GUARANTEE_MS);
-        public static final long DEFAULT_RUNTIME_USER_INITIATED_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 long DEFAULT_RUNTIME_MIN_UI_GUARANTEE_MS =
+                Math.max(6 * HOUR_IN_MILLIS, DEFAULT_RUNTIME_MIN_GUARANTEE_MS);
+        public static final long DEFAULT_RUNTIME_UI_LIMIT_MS =
+                Math.max(12 * HOUR_IN_MILLIS, DEFAULT_RUNTIME_FREE_QUOTA_MAX_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 boolean DEFAULT_RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS = false;
         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 +736,35 @@
         /**
          * 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.
+         * will run for. This is only considered when using data estimates to calculate
+         * execution limits.
          */
-        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;
+        /**
+         * Whether to use data estimates to determine execution limits for execution limits.
+         */
+        public boolean RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS =
+                DEFAULT_RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS;
 
         /**
          * Whether to persist jobs in split files (by UID). If false, all persisted jobs will be
@@ -766,6 +773,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 +839,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 +878,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_USE_DATA_ESTIMATES_FOR_LIMITS);
 
             // Make sure min runtime for regular jobs is at least 10 minutes.
             RUNTIME_MIN_GUARANTEE_MS = Math.max(10 * MINUTE_IN_MILLIS,
@@ -880,37 +896,30 @@
                     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));
-            // 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,
-                            properties.getLong(
-                                    KEY_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS,
-                                    DEFAULT_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS)));
+                            KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS,
+                            DEFAULT_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS));
+
+            RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS = properties.getBoolean(
+                    KEY_RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS,
+                    DEFAULT_RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS);
         }
 
         private boolean updateTareSettingsLocked(@EconomyManager.EnabledMode int enabledMode) {
@@ -958,18 +967,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_USE_DATA_ESTIMATES_FOR_LIMITS,
+                    RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS).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 +1362,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 +1425,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 +1486,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 +1907,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) {
@@ -3252,23 +3304,26 @@
             if (job.shouldTreatAsUserInitiatedJob()
                     && checkRunUserInitiatedJobsPermission(
                             job.getSourceUid(), job.getSourcePackageName())) {
-                if (job.getJob().getRequiredNetwork() != null) { // UI+DT
-                    final long estimatedTransferTimeMs =
-                            mConnectivityController.getEstimatedTransferTimeMs(job);
-                    if (estimatedTransferTimeMs == ConnectivityController.UNKNOWN_TIME) {
-                        return mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS;
+                if (job.getJob().getRequiredNetwork() != null) {
+                    // User-initiated data transfers.
+                    if (mConstants.RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS) {
+                        final long estimatedTransferTimeMs =
+                                mConnectivityController.getEstimatedTransferTimeMs(job);
+                        if (estimatedTransferTimeMs == ConnectivityController.UNKNOWN_TIME) {
+                            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_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR);
+                        return Math.min(mConstants.RUNTIME_UI_LIMIT_MS,
+                                Math.max(factoredTransferTimeMs,
+                                        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,
-                            Math.max(factoredTransferTimeMs,
-                                    mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS
-                            ));
+                    return Math.max(mConstants.RUNTIME_MIN_UI_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
@@ -3283,14 +3338,10 @@
     /** Returns the maximum amount of time this job could run for. */
     public long getMaxJobExecutionTimeMs(JobStatus job) {
         synchronized (mLock) {
-            final boolean allowLongerJob = job.shouldTreatAsUserInitiatedJob()
+            if (job.shouldTreatAsUserInitiatedJob()
                     && checkRunUserInitiatedJobsPermission(
-                            job.getSourceUid(), job.getSourcePackageName());
-            if (job.getJob().getRequiredNetwork() != null && allowLongerJob) { // UI+DT
-                return mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS;
-            }
-            if (allowLongerJob) { // UI with LRJ permission
-                return mConstants.RUNTIME_USER_INITIATED_LIMIT_MS;
+                            job.getSourceUid(), job.getSourcePackageName())) {
+                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/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
index 0dcb0b245..a96a4ef 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -883,6 +883,15 @@
             if (job.isRequireStorageNotLow()) {
                 out.attribute(null, "storage-not-low", Boolean.toString(true));
             }
+            if (job.isPreferBatteryNotLow()) {
+                out.attributeBoolean(null, "prefer-battery-not-low", true);
+            }
+            if (job.isPreferCharging()) {
+                out.attributeBoolean(null, "prefer-charging", true);
+            }
+            if (job.isPreferDeviceIdle()) {
+                out.attributeBoolean(null, "prefer-idle", true);
+            }
             out.endTag(null, XML_TAG_PARAMS_CONSTRAINTS);
         }
 
@@ -1538,6 +1547,13 @@
             if (val != null) {
                 jobBuilder.setRequiresStorageNotLow(true);
             }
+
+            jobBuilder.setPrefersBatteryNotLow(
+                    parser.getAttributeBoolean(null, "prefer-battery-not-low", false));
+            jobBuilder.setPrefersCharging(
+                    parser.getAttributeBoolean(null, "prefer-charging", false));
+            jobBuilder.setPrefersDeviceIdle(
+                    parser.getAttributeBoolean(null, "prefer-idle", false));
         }
 
         /**
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
index e55bda7..3859d89 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
@@ -1147,10 +1147,9 @@
 
         final boolean changed = jobStatus.setConnectivityConstraintSatisfied(nowElapsed, satisfied);
 
+        jobStatus.setHasAccessToUnmetered(satisfied && capabilities != null
+                && capabilities.hasCapability(NET_CAPABILITY_NOT_METERED));
         if (jobStatus.getPreferUnmetered()) {
-            jobStatus.setHasAccessToUnmetered(satisfied && capabilities != null
-                    && capabilities.hasCapability(NET_CAPABILITY_NOT_METERED));
-
             jobStatus.setFlexibilityConstraintSatisfied(nowElapsed,
                     mFlexibilityController.isFlexibilitySatisfiedLocked(jobStatus));
         }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
index 620c48d..234a93c 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
@@ -239,14 +239,14 @@
         return !mFlexibilityEnabled
                 || mService.getUidBias(js.getSourceUid()) == JobInfo.BIAS_TOP_APP
                 || mService.isCurrentlyRunningLocked(js)
-                || getNumSatisfiedRequiredConstraintsLocked(js)
+                || getNumSatisfiedFlexibleConstraintsLocked(js)
                 >= js.getNumRequiredFlexibleConstraints();
     }
 
     @VisibleForTesting
     @GuardedBy("mLock")
-    int getNumSatisfiedRequiredConstraintsLocked(JobStatus js) {
-        return Integer.bitCount(mSatisfiedFlexibleConstraints)
+    int getNumSatisfiedFlexibleConstraintsLocked(JobStatus js) {
+        return Integer.bitCount(mSatisfiedFlexibleConstraints & js.getPreferredConstraintFlags())
                 + (js.getHasAccessToUnmetered() ? 1 : 0);
     }
 
@@ -651,7 +651,7 @@
         static final String KEY_RESCHEDULED_JOB_DEADLINE_MS =
                 FC_CONFIG_PREFIX + "rescheduled_job_deadline_ms";
 
-        private static final boolean DEFAULT_FLEXIBILITY_ENABLED = false;
+        private static final boolean DEFAULT_FLEXIBILITY_ENABLED = true;
         @VisibleForTesting
         static final long DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS = 15 * MINUTE_IN_MILLIS;
         @VisibleForTesting
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..17dde90 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
@@ -16,7 +16,6 @@
 
 package com.android.server.job.controllers;
 
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
 import static android.text.format.DateUtils.HOUR_IN_MILLIS;
 
 import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
@@ -25,8 +24,6 @@
 import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
 import static com.android.server.job.JobSchedulerService.WORKING_INDEX;
 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
-import static com.android.server.job.controllers.FlexibilityController.NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS;
-import static com.android.server.job.controllers.FlexibilityController.SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS;
 
 import android.annotation.ElapsedRealtimeLong;
 import android.annotation.NonNull;
@@ -115,12 +112,11 @@
     static final int CONSTRAINT_WITHIN_QUOTA = 1 << 24;      // Implicit constraint
     static final int CONSTRAINT_PREFETCH = 1 << 23;
     static final int CONSTRAINT_BACKGROUND_NOT_RESTRICTED = 1 << 22; // Implicit constraint
-    static final int CONSTRAINT_FLEXIBLE = 1 << 21; // Implicit constraint
+    static final int CONSTRAINT_FLEXIBLE = 1 << 21;
 
     private static final int IMPLICIT_CONSTRAINTS = 0
             | CONSTRAINT_BACKGROUND_NOT_RESTRICTED
             | CONSTRAINT_DEVICE_NOT_DOZING
-            | CONSTRAINT_FLEXIBLE
             | CONSTRAINT_TARE_WEALTH
             | CONSTRAINT_WITHIN_QUOTA;
 
@@ -298,6 +294,7 @@
 
     // Constraints.
     final int requiredConstraints;
+    private final int mPreferredConstraints;
     private final int mRequiredConstraintsOfInterest;
     int satisfiedConstraints = 0;
     private int mSatisfiedConstraintsOfInterest = 0;
@@ -618,24 +615,26 @@
         }
         mHasExemptedMediaUrisOnly = exemptedMediaUrisOnly;
 
-        mPreferUnmetered = job.getRequiredNetwork() != null
-                && !job.getRequiredNetwork().hasCapability(NET_CAPABILITY_NOT_METERED);
+        mPreferredConstraints = job.getPreferredConstraintFlags();
 
-        final boolean lacksSomeFlexibleConstraints =
-                ((~requiredConstraints) & SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS) != 0
-                        || mPreferUnmetered;
+        // Exposing a preferredNetworkRequest API requires that we make sure that the preferred
+        // NetworkRequest is a subset of the required NetworkRequest. We currently don't have the
+        // code to ensure that, so disable this part for now.
+        // TODO(236261941): look into enabling flexible network constraint requests
+        mPreferUnmetered = false;
+                // && job.getRequiredNetwork() != null
+                // && !job.getRequiredNetwork().hasCapability(NET_CAPABILITY_NOT_METERED);
+
         final boolean satisfiesMinWindowException =
                 (latestRunTimeElapsedMillis - earliestRunTimeElapsedMillis)
                 >= MIN_WINDOW_FOR_FLEXIBILITY_MS;
 
         // The first time a job is rescheduled it will not be subject to flexible constraints.
         // Otherwise, every consecutive reschedule increases a jobs' flexibility deadline.
-        if (!isRequestedExpeditedJob() && !job.isUserInitiated()
+        if (mPreferredConstraints != 0 && !isRequestedExpeditedJob() && !job.isUserInitiated()
                 && satisfiesMinWindowException
-                && (numFailures + numSystemStops) != 1
-                && lacksSomeFlexibleConstraints) {
-            mNumRequiredFlexibleConstraints =
-                    NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS + (mPreferUnmetered ? 1 : 0);
+                && (numFailures + numSystemStops) != 1) {
+            mNumRequiredFlexibleConstraints = Integer.bitCount(mPreferredConstraints);
             requiredConstraints |= CONSTRAINT_FLEXIBLE;
         } else {
             mNumRequiredFlexibleConstraints = 0;
@@ -814,6 +813,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();
     }
@@ -1135,6 +1141,10 @@
         mInternalFlags |= flags;
     }
 
+    int getPreferredConstraintFlags() {
+        return mPreferredConstraints;
+    }
+
     public int getSatisfiedConstraintFlags() {
         return satisfiedConstraints;
     }
@@ -2494,6 +2504,9 @@
         pw.print("Required constraints:");
         dumpConstraints(pw, requiredConstraints);
         pw.println();
+        pw.print("Preferred constraints:");
+        dumpConstraints(pw, mPreferredConstraints);
+        pw.println();
         pw.print("Dynamic constraints:");
         dumpConstraints(pw, mDynamicConstraints);
         pw.println();
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/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index c3118ff..ab0a8ad 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -268,6 +268,10 @@
     @GuardedBy("mActiveAdminApps")
     private final SparseArray<Set<String>> mActiveAdminApps = new SparseArray<>();
 
+    /** List of admin protected packages. Can contain {@link android.os.UserHandle#USER_ALL}. */
+    @GuardedBy("mAdminProtectedPackages")
+    private final SparseArray<Set<String>> mAdminProtectedPackages = new SparseArray<>();
+
     /**
      * Set of system apps that are headless (don't have any "front door" activities, enabled or
      * disabled). Presence in this map indicates that the app is a headless system app.
@@ -1380,6 +1384,9 @@
             synchronized (mActiveAdminApps) {
                 mActiveAdminApps.remove(userId);
             }
+            synchronized (mAdminProtectedPackages) {
+                mAdminProtectedPackages.remove(userId);
+            }
         }
     }
 
@@ -1469,6 +1476,10 @@
                 return STANDBY_BUCKET_EXEMPTED;
             }
 
+            if (isAdminProtectedPackages(packageName, userId)) {
+                return STANDBY_BUCKET_EXEMPTED;
+            }
+
             if (isActiveNetworkScorer(packageName)) {
                 return STANDBY_BUCKET_EXEMPTED;
             }
@@ -1948,6 +1959,17 @@
         }
     }
 
+    private boolean isAdminProtectedPackages(String packageName, int userId) {
+        synchronized (mAdminProtectedPackages) {
+            if (mAdminProtectedPackages.contains(UserHandle.USER_ALL)
+                    && mAdminProtectedPackages.get(UserHandle.USER_ALL).contains(packageName)) {
+                return true;
+            }
+            return mAdminProtectedPackages.contains(userId)
+                    && mAdminProtectedPackages.get(userId).contains(packageName);
+        }
+    }
+
     @Override
     public void addActiveDeviceAdmin(String adminPkg, int userId) {
         synchronized (mActiveAdminApps) {
@@ -1972,6 +1994,17 @@
     }
 
     @Override
+    public void setAdminProtectedPackages(Set<String> packageNames, int userId) {
+        synchronized (mAdminProtectedPackages) {
+            if (packageNames == null || packageNames.isEmpty()) {
+                mAdminProtectedPackages.remove(userId);
+            } else {
+                mAdminProtectedPackages.put(userId, packageNames);
+            }
+        }
+    }
+
+    @Override
     public void onAdminDataAvailable() {
         mAdminDataAvailableLatch.countDown();
     }
@@ -1993,6 +2026,13 @@
         }
     }
 
+    @VisibleForTesting
+    Set<String> getAdminProtectedPackagesForTest(int userId) {
+        synchronized (mAdminProtectedPackages) {
+            return mAdminProtectedPackages.get(userId);
+        }
+    }
+
     /**
      * Returns {@code true} if the supplied package is the device provisioning app. Otherwise,
      * returns {@code false}.
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 dd4f213..b292956 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";
@@ -5280,18 +5281,15 @@
 
   public class BroadcastOptions {
     method public void clearDeferralPolicy();
-    method public void clearDeliveryGroupMatchingFilter();
     method public void clearDeliveryGroupMatchingKey();
     method public void clearDeliveryGroupPolicy();
     method @NonNull public static android.app.BroadcastOptions fromBundle(@NonNull android.os.Bundle);
     method public int getDeferralPolicy();
-    method @Nullable public android.content.IntentFilter getDeliveryGroupMatchingFilter();
     method @Nullable public String getDeliveryGroupMatchingKey();
     method public int getDeliveryGroupPolicy();
     method public boolean isShareIdentityEnabled();
     method @NonNull public static android.app.BroadcastOptions makeBasic();
     method @NonNull public android.app.BroadcastOptions setDeferralPolicy(int);
-    method @NonNull public android.app.BroadcastOptions setDeliveryGroupMatchingFilter(@NonNull android.content.IntentFilter);
     method @NonNull public android.app.BroadcastOptions setDeliveryGroupMatchingKey(@NonNull String, @NonNull String);
     method @NonNull public android.app.BroadcastOptions setDeliveryGroupPolicy(int);
     method @NonNull public android.app.BroadcastOptions setShareIdentityEnabled(boolean);
@@ -6622,6 +6620,7 @@
     ctor public Notification.MediaStyle();
     ctor @Deprecated public Notification.MediaStyle(android.app.Notification.Builder);
     method public android.app.Notification.MediaStyle setMediaSession(android.media.session.MediaSession.Token);
+    method @NonNull @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public android.app.Notification.MediaStyle setRemotePlaybackInfo(@NonNull CharSequence, @DrawableRes int, @Nullable android.app.PendingIntent);
     method public android.app.Notification.MediaStyle setShowActionsInCompactView(int...);
   }
 
@@ -7574,23 +7573,23 @@
     method public android.content.Intent getCropAndSetWallpaperIntent(android.net.Uri);
     method public int getDesiredMinimumHeight();
     method public int getDesiredMinimumWidth();
-    method @Nullable @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public android.graphics.drawable.Drawable getDrawable();
-    method @Nullable @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public android.graphics.drawable.Drawable getDrawable(int);
-    method @Nullable @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public android.graphics.drawable.Drawable getFastDrawable();
-    method @Nullable @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public android.graphics.drawable.Drawable getFastDrawable(int);
+    method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, "android.permission.READ_WALLPAPER_INTERNAL"}) public android.graphics.drawable.Drawable getDrawable();
+    method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, "android.permission.READ_WALLPAPER_INTERNAL"}) public android.graphics.drawable.Drawable getDrawable(int);
+    method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, "android.permission.READ_WALLPAPER_INTERNAL"}) public android.graphics.drawable.Drawable getFastDrawable();
+    method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, "android.permission.READ_WALLPAPER_INTERNAL"}) public android.graphics.drawable.Drawable getFastDrawable(int);
     method public static android.app.WallpaperManager getInstance(android.content.Context);
     method @Nullable public android.app.WallpaperColors getWallpaperColors(int);
-    method @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public android.os.ParcelFileDescriptor getWallpaperFile(int);
+    method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, "android.permission.READ_WALLPAPER_INTERNAL"}) public android.os.ParcelFileDescriptor getWallpaperFile(int);
     method public int getWallpaperId(int);
     method public android.app.WallpaperInfo getWallpaperInfo();
     method @Nullable public android.app.WallpaperInfo getWallpaperInfo(int);
     method public boolean hasResourceWallpaper(@RawRes int);
     method public boolean isSetWallpaperAllowed();
     method public boolean isWallpaperSupported();
-    method @Nullable public android.graphics.drawable.Drawable peekDrawable();
-    method @Nullable public android.graphics.drawable.Drawable peekDrawable(int);
-    method @Nullable @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public android.graphics.drawable.Drawable peekFastDrawable();
-    method @Nullable @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public android.graphics.drawable.Drawable peekFastDrawable(int);
+    method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, "android.permission.READ_WALLPAPER_INTERNAL"}) public android.graphics.drawable.Drawable peekDrawable();
+    method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, "android.permission.READ_WALLPAPER_INTERNAL"}) public android.graphics.drawable.Drawable peekDrawable(int);
+    method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, "android.permission.READ_WALLPAPER_INTERNAL"}) public android.graphics.drawable.Drawable peekFastDrawable();
+    method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, "android.permission.READ_WALLPAPER_INTERNAL"}) public android.graphics.drawable.Drawable peekFastDrawable(int);
     method public void removeOnColorsChangedListener(@NonNull android.app.WallpaperManager.OnColorsChangedListener);
     method public void sendWallpaperCommand(android.os.IBinder, String, int, int, int, android.os.Bundle);
     method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) public void setBitmap(android.graphics.Bitmap) throws java.io.IOException;
@@ -7893,6 +7892,7 @@
     method @Deprecated public boolean isCallerApplicationRestrictionsManagingPackage();
     method public boolean isCommonCriteriaModeEnabled(@Nullable android.content.ComponentName);
     method public boolean isComplianceAcknowledgementRequired();
+    method public boolean isDeviceFinanced();
     method public boolean isDeviceIdAttestationSupported();
     method public boolean isDeviceOwnerApp(String);
     method public boolean isEphemeralUser(@NonNull android.content.ComponentName);
@@ -8048,6 +8048,7 @@
     field public static final String ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED = "android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED";
     field public static final String ACTION_CHECK_POLICY_COMPLIANCE = "android.app.action.CHECK_POLICY_COMPLIANCE";
     field public static final String ACTION_DEVICE_ADMIN_SERVICE = "android.app.action.DEVICE_ADMIN_SERVICE";
+    field public static final String ACTION_DEVICE_FINANCING_STATE_CHANGED = "android.app.admin.action.DEVICE_FINANCING_STATE_CHANGED";
     field public static final String ACTION_DEVICE_OWNER_CHANGED = "android.app.action.DEVICE_OWNER_CHANGED";
     field public static final String ACTION_DEVICE_POLICY_RESOURCE_UPDATED = "android.app.action.DEVICE_POLICY_RESOURCE_UPDATED";
     field public static final String ACTION_GET_PROVISIONING_MODE = "android.app.action.GET_PROVISIONING_MODE";
@@ -11184,7 +11185,6 @@
     method public final String getDataScheme(int);
     method public final android.os.PatternMatcher getDataSchemeSpecificPart(int);
     method public final String getDataType(int);
-    method @NonNull public final android.os.PersistableBundle getExtras();
     method public final int getPriority();
     method public final boolean hasAction(String);
     method public final boolean hasCategory(String);
@@ -11203,7 +11203,6 @@
     method public void readFromXml(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
     method public final java.util.Iterator<android.os.PatternMatcher> schemeSpecificPartsIterator();
     method public final java.util.Iterator<java.lang.String> schemesIterator();
-    method public final void setExtras(@NonNull android.os.PersistableBundle);
     method public final void setPriority(int);
     method public final java.util.Iterator<java.lang.String> typesIterator();
     method public final void writeToParcel(android.os.Parcel, int);
@@ -11403,7 +11402,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,9 +27344,13 @@
     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 long TV_MESSAGE_GROUP_ID_NONE = -1L; // 0xffffffffffffffffL
+    field public static final String TV_MESSAGE_KEY_GROUP_ID = "android.media.tv.TvInputManager.group_id";
     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 String TV_MESSAGE_SUBTYPE_CC_608E = "CTA 608-E";
+    field public static final String TV_MESSAGE_SUBTYPE_WATERMARKING_A335 = "ATSC A/335";
     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
@@ -27477,7 +27480,7 @@
     method public boolean onTrackballEvent(android.view.MotionEvent);
     method public abstract boolean onTune(android.net.Uri);
     method public boolean onTune(android.net.Uri, android.os.Bundle);
-    method public void onTvMessage(@NonNull String, @NonNull android.os.Bundle);
+    method public void onTvMessage(int, @NonNull android.os.Bundle);
     method public void onUnblockContent(android.media.tv.TvContentRating);
     method public void setOverlayViewEnabled(boolean);
   }
@@ -33866,7 +33869,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);
@@ -40593,7 +40596,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 {
@@ -40642,7 +40645,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 {
@@ -51285,10 +51288,12 @@
     method public long getDownTime();
     method public int getEdgeFlags();
     method public long getEventTime();
+    method public long getEventTimeNanos();
     method public int getFlags();
     method public float getHistoricalAxisValue(int, int);
     method public float getHistoricalAxisValue(int, int, int);
     method public long getHistoricalEventTime(int);
+    method public long getHistoricalEventTimeNanos(int);
     method public float getHistoricalOrientation(int);
     method public float getHistoricalOrientation(int, int);
     method public void getHistoricalPointerCoords(int, int, android.view.MotionEvent.PointerCoords);
@@ -53906,6 +53911,14 @@
     method public void removeViewImmediate(android.view.View);
     field public static final String PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE = "android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE";
     field public static final String PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED = "android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED";
+    field public static final String PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION = "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION";
+    field public static final String PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH = "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH";
+    field public static final String PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE = "android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE";
+    field public static final String PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE = "android.window.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE";
+    field public static final String PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE = "android.window.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE";
+    field public static final String PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS = "android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS";
+    field public static final String PROPERTY_COMPAT_ENABLE_FAKE_FOCUS = "android.window.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS";
+    field public static final String PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION = "android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION";
   }
 
   public static class WindowManager.BadTokenException extends java.lang.RuntimeException {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index d573e33..5a8ee94 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";
@@ -969,10 +968,6 @@
     field public static final int SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY = 11; // 0xb
   }
 
-  public static class Notification.MediaStyle extends android.app.Notification.Style {
-    method @NonNull @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public android.app.Notification.MediaStyle setRemotePlaybackInfo(@NonNull CharSequence, @DrawableRes int, @Nullable android.app.PendingIntent);
-  }
-
   public static final class Notification.TvExtender implements android.app.Notification.Extender {
     ctor public Notification.TvExtender();
     ctor public Notification.TvExtender(android.app.Notification);
@@ -1262,6 +1257,7 @@
     method @Nullable public CharSequence getDeviceOwnerOrganizationName();
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.os.UserHandle getDeviceOwnerUser();
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public android.app.admin.DevicePolicyState getDevicePolicyState();
+    method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public String getFinancedDeviceKioskRoleHolder();
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_ADMIN_POLICY}) public java.util.List<java.lang.String> getPermittedAccessibilityServices(int);
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_ADMIN_POLICY}) public java.util.List<java.lang.String> getPermittedInputMethodsForCurrentUser();
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public java.util.List<android.os.UserHandle> getPolicyManagedProfiles(@NonNull android.os.UserHandle);
@@ -9985,6 +9981,7 @@
   public final class HotspotNetwork implements android.os.Parcelable {
     method public int describeContents();
     method public long getDeviceId();
+    method @NonNull public android.os.Bundle getExtras();
     method public int getHostNetworkType();
     method @Nullable public String getHotspotBssid();
     method @NonNull public java.util.Set<java.lang.Integer> getHotspotSecurityTypes();
@@ -10004,6 +10001,7 @@
     method @NonNull public android.net.wifi.sharedconnectivity.app.HotspotNetwork.Builder addHotspotSecurityType(int);
     method @NonNull public android.net.wifi.sharedconnectivity.app.HotspotNetwork build();
     method @NonNull public android.net.wifi.sharedconnectivity.app.HotspotNetwork.Builder setDeviceId(long);
+    method @NonNull public android.net.wifi.sharedconnectivity.app.HotspotNetwork.Builder setExtras(@NonNull android.os.Bundle);
     method @NonNull public android.net.wifi.sharedconnectivity.app.HotspotNetwork.Builder setHostNetworkType(int);
     method @NonNull public android.net.wifi.sharedconnectivity.app.HotspotNetwork.Builder setHotspotBssid(@NonNull String);
     method @NonNull public android.net.wifi.sharedconnectivity.app.HotspotNetwork.Builder setHotspotSsid(@NonNull String);
@@ -10040,6 +10038,7 @@
 
   public final class KnownNetwork implements android.os.Parcelable {
     method public int describeContents();
+    method @NonNull public android.os.Bundle getExtras();
     method @Nullable public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo getNetworkProviderInfo();
     method public int getNetworkSource();
     method @NonNull public java.util.Set<java.lang.Integer> getSecurityTypes();
@@ -10055,6 +10054,7 @@
     ctor public KnownNetwork.Builder();
     method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork.Builder addSecurityType(int);
     method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork build();
+    method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork.Builder setExtras(@NonNull android.os.Bundle);
     method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork.Builder setNetworkProviderInfo(@Nullable android.net.wifi.sharedconnectivity.app.NetworkProviderInfo);
     method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork.Builder setNetworkSource(int);
     method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork.Builder setSsid(@NonNull String);
@@ -10086,6 +10086,7 @@
     method @IntRange(from=0, to=3) public int getConnectionStrength();
     method @NonNull public String getDeviceName();
     method public int getDeviceType();
+    method @NonNull public android.os.Bundle getExtras();
     method @NonNull public String getModelName();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.sharedconnectivity.app.NetworkProviderInfo> CREATOR;
@@ -10104,6 +10105,7 @@
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setConnectionStrength(@IntRange(from=0, to=3) int);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setDeviceName(@NonNull String);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setDeviceType(int);
+    method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setExtras(@NonNull android.os.Bundle);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setModelName(@NonNull String);
   }
 
@@ -10135,16 +10137,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 +12998,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
@@ -13104,12 +13108,14 @@
     method public int describeContents();
     method public int getAudioChannel();
     method @NonNull public java.util.List<android.service.voice.HotwordAudioStream> getAudioStreams();
+    method public int getBackgroundAudioPower();
     method public int getConfidenceLevel();
     method @NonNull public android.service.voice.DetectedPhrase getDetectedPhrase();
     method @NonNull public android.os.PersistableBundle getExtras();
     method public int getHotwordDurationMillis();
     method public int getHotwordOffsetMillis();
     method @Deprecated public int getHotwordPhraseId();
+    method public static int getMaxBackgroundAudioPower();
     method public static int getMaxBundleSize();
     method @Deprecated public static int getMaxHotwordPhraseId();
     method public static int getMaxScore();
@@ -13120,6 +13126,7 @@
     method public boolean isHotwordDetectionPersonalized();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field public static final int AUDIO_CHANNEL_UNSET = -1; // 0xffffffff
+    field public static final int BACKGROUND_AUDIO_POWER_UNSET = -1; // 0xffffffff
     field public static final int CONFIDENCE_LEVEL_HIGH = 5; // 0x5
     field public static final int CONFIDENCE_LEVEL_LOW = 1; // 0x1
     field public static final int CONFIDENCE_LEVEL_LOW_MEDIUM = 2; // 0x2
@@ -13139,6 +13146,7 @@
     method @NonNull public android.service.voice.HotwordDetectedResult build();
     method @NonNull public android.service.voice.HotwordDetectedResult.Builder setAudioChannel(int);
     method @NonNull public android.service.voice.HotwordDetectedResult.Builder setAudioStreams(@NonNull java.util.List<android.service.voice.HotwordAudioStream>);
+    method @NonNull public android.service.voice.HotwordDetectedResult.Builder setBackgroundAudioPower(int);
     method @NonNull public android.service.voice.HotwordDetectedResult.Builder setConfidenceLevel(int);
     method @NonNull public android.service.voice.HotwordDetectedResult.Builder setDetectedPhrase(@NonNull android.service.voice.DetectedPhrase);
     method @NonNull public android.service.voice.HotwordDetectedResult.Builder setExtras(@NonNull android.os.PersistableBundle);
@@ -13186,10 +13194,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 +13211,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 +13278,9 @@
 
   public class VisualQueryDetector {
     method public void destroy();
-    method @RequiresPermission(allOf={android.Manifest.permission.CAMERA, android.Manifest.permission.RECORD_AUDIO}) public boolean startRecognition() throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
-    method @RequiresPermission(allOf={android.Manifest.permission.CAMERA, android.Manifest.permission.RECORD_AUDIO}) public boolean stopRecognition() throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
-    method public void updateState(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory) throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
+    method @RequiresPermission(allOf={android.Manifest.permission.CAMERA, android.Manifest.permission.RECORD_AUDIO}) public boolean startRecognition();
+    method @RequiresPermission(allOf={android.Manifest.permission.CAMERA, android.Manifest.permission.RECORD_AUDIO}) public boolean stopRecognition();
+    method public void updateState(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory);
   }
 
   public static interface VisualQueryDetector.Callback {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 445b957..5a84b97 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -476,7 +476,7 @@
     method @Nullable public android.graphics.Rect peekBitmapDimensions(int);
     method public void setWallpaperZoomOut(@NonNull android.os.IBinder, float);
     method public boolean shouldEnableWideColorGamut();
-    method @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public boolean wallpaperSupportsWcg(int);
+    method public boolean wallpaperSupportsWcg(int);
   }
 
   public class WindowConfiguration implements java.lang.Comparable<android.app.WindowConfiguration> android.os.Parcelable {
@@ -516,6 +516,10 @@
 
 package android.app.admin {
 
+  public final class AccountTypePolicyKey extends android.app.admin.PolicyKey {
+    ctor public AccountTypePolicyKey(@NonNull String, @NonNull String);
+  }
+
   public final class DeviceAdminAuthority extends android.app.admin.Authority {
     field @NonNull public static final android.app.admin.DeviceAdminAuthority DEVICE_ADMIN_AUTHORITY;
   }
@@ -612,6 +616,10 @@
     field @NonNull public static final android.app.admin.FlagUnion FLAG_UNION;
   }
 
+  public final class IntentFilterPolicyKey extends android.app.admin.PolicyKey {
+    ctor public IntentFilterPolicyKey(@NonNull String, @NonNull android.content.IntentFilter);
+  }
+
   public final class MostRecent<V> extends android.app.admin.ResolutionMechanism<V> {
     ctor public MostRecent();
     method public int describeContents();
@@ -627,6 +635,14 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.MostRestrictive<?>> CREATOR;
   }
 
+  public final class PackagePermissionPolicyKey extends android.app.admin.PolicyKey {
+    ctor public PackagePermissionPolicyKey(@NonNull String, @NonNull String, @NonNull String);
+  }
+
+  public final class PackagePolicyKey extends android.app.admin.PolicyKey {
+    ctor public PackagePolicyKey(@NonNull String, @NonNull String);
+  }
+
   public final class PolicyState<V> implements android.os.Parcelable {
     method @NonNull public android.app.admin.ResolutionMechanism<V> getResolutionMechanism();
   }
@@ -676,6 +692,10 @@
     method public int getOperation();
   }
 
+  public final class UserRestrictionPolicyKey extends android.app.admin.PolicyKey {
+    ctor public UserRestrictionPolicyKey(@NonNull String, @NonNull String);
+  }
+
 }
 
 package android.app.assist {
@@ -1391,8 +1411,12 @@
 package android.hardware.camera2 {
 
   public final class CameraManager {
+    method @NonNull public android.hardware.camera2.CameraCharacteristics getCameraCharacteristics(@NonNull String, boolean) throws android.hardware.camera2.CameraAccessException;
     method public String[] getCameraIdListNoLazy() throws android.hardware.camera2.CameraAccessException;
+    method @RequiresPermission(android.Manifest.permission.CAMERA) public void openCamera(@NonNull String, boolean, @Nullable android.os.Handler, @NonNull android.hardware.camera2.CameraDevice.StateCallback) throws android.hardware.camera2.CameraAccessException;
     method @RequiresPermission(allOf={android.Manifest.permission.SYSTEM_CAMERA, android.Manifest.permission.CAMERA}) public void openCamera(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraDevice.StateCallback) throws android.hardware.camera2.CameraAccessException;
+    method public static boolean shouldOverrideToPortrait(@Nullable android.content.pm.PackageManager, @Nullable String);
+    field public static final String LANDSCAPE_TO_PORTRAIT_PROP = "camera.enable_landscape_to_portrait";
     field public static final long OVERRIDE_CAMERA_LANDSCAPE_TO_PORTRAIT = 250678880L; // 0xef10e60L
   }
 
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java
index a81ef18..d0ce701 100644
--- a/core/java/android/animation/Animator.java
+++ b/core/java/android/animation/Animator.java
@@ -69,7 +69,7 @@
      * backing field for backgroundPauseDelay property. This could be simply a hardcoded
      * value in AnimationHandler, but it is useful to be able to change the value in tests.
      */
-    private static long sBackgroundPauseDelay = 10000;
+    private static long sBackgroundPauseDelay = 1000;
 
     /**
      * Sets the duration for delaying pausing animators when apps go into the background.
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 90427cb..b96f8c9 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -994,6 +994,16 @@
      */
     public abstract void logFgsApiEnd(int apiType, int uid, int pid);
 
+     /**
+     * Temporarily allow foreground service started by an uid to have while-in-use permission
+     * for durationMs.
+     *
+     * @param uid The UID of the app that starts the foreground service.
+     * @param durationMs elapsedRealTime duration in milliseconds.
+     * @hide
+     */
+    public abstract void tempAllowWhileInUsePermissionInFgs(int uid, long durationMs);
+
     /**
      * The list of the events about the {@link android.media.projection.IMediaProjection} itself.
      *
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 2751b54..682fec8 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -5840,26 +5840,6 @@
 
         r.activity.mChangingConfigurations = true;
 
-        // If we are preserving the main window across relaunches we would also like to preserve
-        // the children. However the client side view system does not support preserving
-        // the child views so we notify the window manager to expect these windows to
-        // be replaced and defer requests to destroy or hide them. This way we can achieve
-        // visual continuity. It's important that we do this here prior to pause and destroy
-        // as that is when we may hide or remove the child views.
-        //
-        // There is another scenario, if we have decided locally to relaunch the app from a
-        // call to recreate, then none of the windows will be prepared for replacement or
-        // preserved by the server, so we want to notify it that we are preparing to replace
-        // everything
-        try {
-            if (r.mPreserveWindow) {
-                WindowManagerGlobal.getWindowSession().prepareToReplaceWindows(
-                        r.token, true /* childrenOnly */);
-            }
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-
         handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,
                 pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity");
     }
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 6301ad7..999075d 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -2561,7 +2561,7 @@
     public InstallSourceInfo getInstallSourceInfo(String packageName) throws NameNotFoundException {
         final InstallSourceInfo installSourceInfo;
         try {
-            installSourceInfo = mPM.getInstallSourceInfo(packageName);
+            installSourceInfo = mPM.getInstallSourceInfo(packageName, getUserId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index a5095b2..d23d3cd 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -889,6 +889,8 @@
      * <p> If neither matching key using {@link #setDeliveryGroupMatchingKey(String, String)} nor
      * matching filter using this API is specified, then by default
      * {@link Intent#filterEquals(Intent)} will be used to identify the delivery group.
+     *
+     * @hide
      */
     @NonNull
     public BroadcastOptions setDeliveryGroupMatchingFilter(@NonNull IntentFilter matchingFilter) {
@@ -902,6 +904,7 @@
      *
      * @return the {@link IntentFilter} object that was previously set using
      *         {@link #setDeliveryGroupMatchingFilter(IntentFilter)}.
+     * @hide
      */
     @Nullable
     public IntentFilter getDeliveryGroupMatchingFilter() {
@@ -911,6 +914,8 @@
     /**
      * Clears the {@link IntentFilter} object that was previously set using
      * {@link #setDeliveryGroupMatchingFilter(IntentFilter)}.
+     *
+     * @hide
      */
     public void clearDeliveryGroupMatchingFilter() {
         mDeliveryGroupMatchingFilter = null;
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..502ef0d 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -4634,9 +4634,9 @@
          * Set whether this is an "ongoing" notification.
          *
          * Ongoing notifications cannot be dismissed by the user on locked devices, or by
-         * notification listeners, and some notifications  (device management, media) cannot be
-         * dismissed on unlocked devices, so your application or service must take
-         * care of canceling them.
+         * notification listeners, and some notifications (call, device management, media) cannot
+         * be dismissed on unlocked devices, so your application or service must take care of
+         * canceling them.
          *
          * They are typically used to indicate a background task that the user is actively engaged
          * with (e.g., playing music) or is pending in some way and therefore occupying the device
@@ -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
@@ -9163,10 +9163,7 @@
          *                   {@code null}, in which case the output switcher will be disabled.
          *                   This intent should open an Activity or it will be ignored.
          * @return MediaStyle
-         *
-         * @hide
          */
-        @SystemApi
         @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL)
         @NonNull
         public MediaStyle setRemotePlaybackInfo(@NonNull CharSequence deviceName,
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/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index ff17824..540342b 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -16,6 +16,11 @@
 
 package android.app;
 
+import static android.Manifest.permission.MANAGE_EXTERNAL_STORAGE;
+import static android.Manifest.permission.READ_WALLPAPER_INTERNAL;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
+
 import android.annotation.FloatRange;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -28,6 +33,9 @@
 import android.annotation.SystemService;
 import android.annotation.TestApi;
 import android.annotation.UiContext;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -84,6 +92,7 @@
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -108,8 +117,26 @@
  */
 @SystemService(Context.WALLPAPER_SERVICE)
 public class WallpaperManager {
+
     private static String TAG = "WallpaperManager";
     private static final boolean DEBUG = false;
+
+    /**
+     * Trying to read the wallpaper file or bitmap in T will return
+     * the default wallpaper bitmap/file instead of throwing a SecurityException.
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+    static final long RETURN_DEFAULT_ON_SECURITY_EXCEPTION = 239784307L;
+
+    /**
+     * In U and later, attempting to read the wallpaper file or bitmap will throw an exception,
+     * (except with the READ_WALLPAPER_INTERNAL permission).
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    static final long THROW_ON_SECURITY_EXCEPTION = 237508058L;
+
     private float mWallpaperXStep = -1;
     private float mWallpaperYStep = -1;
     private static final @NonNull RectF LOCAL_COLOR_BOUNDS =
@@ -585,7 +612,8 @@
                 }
             }
             synchronized (this) {
-                if (mCachedWallpaper != null && mCachedWallpaper.isValid(userId, which)) {
+                if (mCachedWallpaper != null && mCachedWallpaper.isValid(userId, which) && context
+                        .checkSelfPermission(READ_WALLPAPER_INTERNAL) == PERMISSION_GRANTED) {
                     return mCachedWallpaper.mCachedWallpaper;
                 }
                 mCachedWallpaper = null;
@@ -596,6 +624,19 @@
                 } catch (OutOfMemoryError e) {
                     Log.w(TAG, "Out of memory loading the current wallpaper: " + e);
                 } catch (SecurityException e) {
+                    /*
+                     * Apps with target SDK <= S can still access the wallpaper through
+                     * READ_EXTERNAL_STORAGE. In T however, app that previously had access to the
+                     * wallpaper via READ_EXTERNAL_STORAGE will get a SecurityException here.
+                     * Thus, in T specifically, return the default wallpaper instead of crashing.
+                     */
+                    if (CompatChanges.isChangeEnabled(RETURN_DEFAULT_ON_SECURITY_EXCEPTION)
+                            && !CompatChanges.isChangeEnabled(THROW_ON_SECURITY_EXCEPTION)) {
+                        Log.w(TAG, "No permission to access wallpaper, returning default"
+                                + " wallpaper to avoid crashing legacy app.");
+                        return getDefaultWallpaper(context, FLAG_SYSTEM);
+                    }
+
                     if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.O_MR1) {
                         Log.w(TAG, "No permission to access wallpaper, suppressing"
                                 + " exception to avoid crashing legacy app.");
@@ -808,6 +849,18 @@
     }
 
     /**
+     * <strong> Important note: </strong>
+     * <ul>
+     *     <li>Up to version S, this method requires the
+     *     {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission.</li>
+     *     <li>Starting in T, directly accessing the wallpaper is not possible anymore,
+     *     instead the default system wallpaper is returned
+     *     (some versions of T may throw a {@code SecurityException}).</li>
+     *     <li>From version U, this method should not be used
+     *     and will always throw a @code SecurityException}.</li>
+     * </ul>
+     * <br>
+     *
      * Retrieve the current system wallpaper; if
      * no wallpaper is set, the system built-in static wallpaper is returned.
      * This is returned as an
@@ -821,14 +874,28 @@
      * @return Returns a Drawable object that will draw the system wallpaper,
      *     or {@code null} if no system wallpaper exists or if the calling application
      *     is not able to access the wallpaper.
+     *
+     * @throws SecurityException as described in the note
      */
-    @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
     @Nullable
+    @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL})
     public Drawable getDrawable() {
         return getDrawable(FLAG_SYSTEM);
     }
 
     /**
+     * <strong> Important note: </strong>
+     * <ul>
+     *     <li>Up to version S, this method requires the
+     *     {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission.</li>
+     *     <li>Starting in T, directly accessing the wallpaper is not possible anymore,
+     *     instead the default system wallpaper is returned
+     *     (some versions of T may throw a {@code SecurityException}).</li>
+     *     <li>From version U, this method should not be used
+     *     and will always throw a @code SecurityException}.</li>
+     * </ul>
+     * <br>
+     *
      * Retrieve the requested wallpaper; if
      * no wallpaper is set, the requested built-in static wallpaper is returned.
      * This is returned as an
@@ -844,9 +911,11 @@
      * @return Returns a Drawable object that will draw the requested wallpaper,
      *     or {@code null} if the requested wallpaper does not exist or if the calling application
      *     is not able to access the wallpaper.
+     *
+     * @throws SecurityException as described in the note
      */
-    @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
     @Nullable
+    @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL})
     public Drawable getDrawable(@SetWallpaperFlags int which) {
         final ColorManagementProxy cmProxy = getColorManagementProxy();
         Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, which, cmProxy);
@@ -1069,6 +1138,18 @@
     }
 
     /**
+     * <strong> Important note: </strong>
+     * <ul>
+     *     <li>Up to version S, this method requires the
+     *     {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission.</li>
+     *     <li>Starting in T, directly accessing the wallpaper is not possible anymore,
+     *     instead the default system wallpaper is returned
+     *     (some versions of T may throw a {@code SecurityException}).</li>
+     *     <li>From version U, this method should not be used
+     *     and will always throw a @code SecurityException}.</li>
+     * </ul>
+     * <br>
+     *
      * Retrieve the current system wallpaper; if there is no wallpaper set,
      * a null pointer is returned. This is returned as an
      * abstract Drawable that you can install in a View to display whatever
@@ -1076,13 +1157,28 @@
      *
      * @return Returns a Drawable object that will draw the wallpaper or a
      * null pointer if wallpaper is unset.
+     *
+     * @throws SecurityException as described in the note
      */
     @Nullable
+    @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL})
     public Drawable peekDrawable() {
         return peekDrawable(FLAG_SYSTEM);
     }
 
     /**
+     * <strong> Important note: </strong>
+     * <ul>
+     *     <li>Up to version S, this method requires the
+     *     {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission.</li>
+     *     <li>Starting in T, directly accessing the wallpaper is not possible anymore,
+     *     instead the default system wallpaper is returned
+     *     (some versions of T may throw a {@code SecurityException}).</li>
+     *     <li>From version U, this method should not be used
+     *     and will always throw a @code SecurityException}.</li>
+     * </ul>
+     * <br>
+     *
      * Retrieve the requested wallpaper; if there is no wallpaper set,
      * a null pointer is returned. This is returned as an
      * abstract Drawable that you can install in a View to display whatever
@@ -1092,11 +1188,14 @@
      *     IllegalArgumentException if an invalid wallpaper is requested.
      * @return Returns a Drawable object that will draw the wallpaper or a null pointer if
      * wallpaper is unset.
+     *
+     * @throws SecurityException as described in the note
      */
     @Nullable
+    @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL})
     public Drawable peekDrawable(@SetWallpaperFlags int which) {
         final ColorManagementProxy cmProxy = getColorManagementProxy();
-        Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false, which, cmProxy);
+        Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, which, cmProxy);
         if (bm != null) {
             Drawable dr = new BitmapDrawable(mContext.getResources(), bm);
             dr.setDither(false);
@@ -1106,6 +1205,18 @@
     }
 
     /**
+     * <strong> Important note: </strong>
+     * <ul>
+     *     <li>Up to version S, this method requires the
+     *     {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission.</li>
+     *     <li>Starting in T, directly accessing the wallpaper is not possible anymore,
+     *     instead the default wallpaper is returned
+     *     (some versions of T may throw a {@code SecurityException}).</li>
+     *     <li>From version U, this method should not be used
+     *     and will always throw a @code SecurityException}.</li>
+     * </ul>
+     * <br>
+     *
      * Like {@link #getDrawable()}, but the returned Drawable has a number
      * of limitations to reduce its overhead as much as possible. It will
      * never scale the wallpaper (only centering it if the requested bounds
@@ -1117,14 +1228,28 @@
      * the same density as the screen (not in density compatibility mode).
      *
      * @return Returns a Drawable object that will draw the wallpaper.
+     *
+     * @throws SecurityException as described in the note
      */
-    @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
     @Nullable
+    @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL})
     public Drawable getFastDrawable() {
         return getFastDrawable(FLAG_SYSTEM);
     }
 
     /**
+     * <strong> Important note: </strong>
+     * <ul>
+     *     <li>Up to version S, this method requires the
+     *     {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission.</li>
+     *     <li>Starting in T, directly accessing the wallpaper is not possible anymore,
+     *     instead the default system wallpaper is returned
+     *     (some versions of T may throw a {@code SecurityException}).</li>
+     *     <li>From version U, this method should not be used
+     *     and will always throw a @code SecurityException}.</li>
+     * </ul>
+     * <br>
+     *
      * Like {@link #getDrawable(int)}, but the returned Drawable has a number
      * of limitations to reduce its overhead as much as possible. It will
      * never scale the wallpaper (only centering it if the requested bounds
@@ -1138,9 +1263,11 @@
      * @param which The {@code FLAG_*} identifier of a valid wallpaper type.  Throws
      *     IllegalArgumentException if an invalid wallpaper is requested.
      * @return Returns a Drawable object that will draw the wallpaper.
+     *
+     * @throws SecurityException as described in the note
      */
-    @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
     @Nullable
+    @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL})
     public Drawable getFastDrawable(@SetWallpaperFlags int which) {
         final ColorManagementProxy cmProxy = getColorManagementProxy();
         Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, which, cmProxy);
@@ -1151,19 +1278,45 @@
     }
 
     /**
+     * <strong> Important note: </strong>
+     * <ul>
+     *     <li>Up to version S, this method requires the
+     *     {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission.</li>
+     *     <li>Starting in T, directly accessing the wallpaper is not possible anymore,
+     *     instead the default system wallpaper is returned
+     *     (some versions of T may throw a {@code SecurityException}).</li>
+     *     <li>From version U, this method should not be used
+     *     and will always throw a @code SecurityException}.</li>
+     * </ul>
+     * <br>
+     *
      * Like {@link #getFastDrawable()}, but if there is no wallpaper set,
      * a null pointer is returned.
      *
      * @return Returns an optimized Drawable object that will draw the
      * wallpaper or a null pointer if these is none.
+     *
+     * @throws SecurityException as described in the note
      */
-    @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
     @Nullable
+    @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL})
     public Drawable peekFastDrawable() {
         return peekFastDrawable(FLAG_SYSTEM);
     }
 
     /**
+     * <strong> Important note: </strong>
+     * <ul>
+     *     <li>Up to version S, this method requires the
+     *     {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission.</li>
+     *     <li>Starting in T, directly accessing the wallpaper is not possible anymore,
+     *     instead the default system wallpaper is returned
+     *     (some versions of T may throw a {@code SecurityException}).</li>
+     *     <li>From version U, this method should not be used
+     *     and will always throw a @code SecurityException}.</li>
+     * </ul>
+     * <br>
+     *
      * Like {@link #getFastDrawable()}, but if there is no wallpaper set,
      * a null pointer is returned.
      *
@@ -1171,12 +1324,14 @@
      *     IllegalArgumentException if an invalid wallpaper is requested.
      * @return Returns an optimized Drawable object that will draw the
      * wallpaper or a null pointer if these is none.
+     *
+     * @throws SecurityException as described in the note
      */
-    @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
     @Nullable
+    @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL})
     public Drawable peekFastDrawable(@SetWallpaperFlags int which) {
         final ColorManagementProxy cmProxy = getColorManagementProxy();
-        Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false, which, cmProxy);
+        Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, which, cmProxy);
         if (bm != null) {
             return new FastBitmapDrawable(bm);
         }
@@ -1194,7 +1349,6 @@
      * @hide
      */
     @TestApi
-    @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
     public boolean wallpaperSupportsWcg(int which) {
         if (!shouldEnableWideColorGamut()) {
             return false;
@@ -1295,6 +1449,18 @@
     }
 
     /**
+     * <strong> Important note: </strong>
+     * <ul>
+     *     <li>Up to version S, this method requires the
+     *     {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission.</li>
+     *     <li>Starting in T, directly accessing the wallpaper is not possible anymore,
+     *     instead the default system wallpaper is returned
+     *     (some versions of T may throw a {@code SecurityException}).</li>
+     *     <li>From version U, this method should not be used
+     *     and will always throw a @code SecurityException}.</li>
+     * </ul>
+     * <br>
+     *
      * Get an open, readable file descriptor to the given wallpaper image file.
      * The caller is responsible for closing the file descriptor when done ingesting the file.
      *
@@ -1305,14 +1471,17 @@
      * @param which The wallpaper whose image file is to be retrieved.  Must be a single
      *     defined kind of wallpaper, either {@link #FLAG_SYSTEM} or
      *     {@link #FLAG_LOCK}.
-     * @return An open, readable file desriptor to the requested wallpaper image file;
+     * @return An open, readable file descriptor to the requested wallpaper image file;
      *     or {@code null} if no such wallpaper is configured or if the calling app does
      *     not have permission to read the current wallpaper.
      *
      * @see #FLAG_LOCK
      * @see #FLAG_SYSTEM
+     *
+     * @throws SecurityException as described in the note
      */
-    @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
+    @Nullable
+    @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL})
     public ParcelFileDescriptor getWallpaperFile(@SetWallpaperFlags int which) {
         return getWallpaperFile(which, mContext.getUserId());
     }
@@ -1475,13 +1644,18 @@
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             } catch (SecurityException e) {
+                if (CompatChanges.isChangeEnabled(RETURN_DEFAULT_ON_SECURITY_EXCEPTION)
+                        && !CompatChanges.isChangeEnabled(THROW_ON_SECURITY_EXCEPTION)) {
+                    Log.w(TAG, "No permission to access wallpaper, returning default"
+                            + " wallpaper file to avoid crashing legacy app.");
+                    return getDefaultSystemWallpaperFile();
+                }
                 if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.O_MR1) {
                     Log.w(TAG, "No permission to access wallpaper, suppressing"
                             + " exception to avoid crashing legacy app.");
                     return null;
-                } else {
-                    throw e;
                 }
+                throw e;
             }
         }
     }
@@ -2586,6 +2760,24 @@
         return null;
     }
 
+    /**
+     * util used in T to return a default system wallpaper file
+     * when third party apps attempt to read the wallpaper with {@link #getWallpaperFile}
+     */
+    private static ParcelFileDescriptor getDefaultSystemWallpaperFile() {
+        for (String path: getDefaultSystemWallpaperPaths()) {
+            File file = new File(path);
+            if (file.exists()) {
+                try {
+                    return ParcelFileDescriptor.open(file, MODE_READ_ONLY);
+                } catch (FileNotFoundException e) {
+                    // continue; default wallpaper file not found on this path
+                }
+            }
+        }
+        return null;
+    }
+
     private static InputStream getWallpaperInputStream(String path) {
         if (!TextUtils.isEmpty(path)) {
             final File file = new File(path);
@@ -2600,6 +2792,14 @@
         return null;
     }
 
+    /**
+     * @return a list of paths to the system default wallpapers, in order of priority:
+     * if the file exists for the first path of this list, the first path should be used.
+     */
+    private static List<String> getDefaultSystemWallpaperPaths() {
+        return List.of(SystemProperties.get(PROP_WALLPAPER), getCmfWallpaperPath());
+    }
+
     private static String getCmfWallpaperPath() {
         return Environment.getProductDirectory() + WALLPAPER_CMF_PATH + "default_wallpaper_"
                 + VALUE_CMF_COLOR;
diff --git a/core/java/android/app/admin/AccountTypePolicyKey.java b/core/java/android/app/admin/AccountTypePolicyKey.java
index 6417cd4..9e376a7 100644
--- a/core/java/android/app/admin/AccountTypePolicyKey.java
+++ b/core/java/android/app/admin/AccountTypePolicyKey.java
@@ -23,6 +23,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.os.Bundle;
 import android.os.Parcel;
 
@@ -49,6 +50,7 @@
     /**
      * @hide
      */
+    @TestApi
     public AccountTypePolicyKey(@NonNull String key, @NonNull String accountType) {
         super(key);
         mAccountType = Objects.requireNonNull((accountType));
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 0241417..924a7c6 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -57,6 +57,7 @@
 
 import android.Manifest.permission;
 import android.accounts.Account;
+import android.annotation.BroadcastBehavior;
 import android.annotation.CallbackExecutor;
 import android.annotation.ColorInt;
 import android.annotation.IntDef;
@@ -3998,6 +3999,27 @@
     public static final String EXTRA_RESOURCE_IDS =
             "android.app.extra.RESOURCE_IDS";
 
+    /**
+     * Broadcast Action: Broadcast sent to indicate that the device financing state has changed.
+     *
+     * <p>This occurs when, for example, a financing kiosk app has been added or removed.
+     *
+     * <p>To query the current device financing state see {@link #isDeviceFinanced}.
+     *
+     * <p>This will be delivered to the following apps if they include a receiver for this action
+     * in their manifest:
+     * <ul>
+     *     <li>Device owner admins.
+     *     <li>Organization-owned profile owner admins
+     *     <li>The supervision app
+     *     <li>The device management role holder
+     * </ul>
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+    @BroadcastBehavior(explicitOnly = true, includeBackground = true)
+    public static final String ACTION_DEVICE_FINANCING_STATE_CHANGED =
+            "android.app.admin.action.DEVICE_FINANCING_STATE_CHANGED";
+
     /** Allow the user to choose whether to enable MTE on the device. */
     public static final int MTE_NOT_CONTROLLED_BY_POLICY = 0;
 
@@ -15818,9 +15840,8 @@
      * Called by a device owner or a profile owner or holder of the permission
      * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_APPS_CONTROL} to disable user
      * control over apps. User will not be able to clear app data or force-stop packages. When
-     * called by a device owner, applies to all users on the device. Starting from Android 13,
-     * packages with user control disabled are exempted from being put in the "restricted" App
-     * Standby Bucket.
+     * called by a device owner, applies to all users on the device. Packages with user control
+     * disabled are exempted from App Standby Buckets.
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the
      *               caller is not a device admin.
@@ -16880,4 +16901,55 @@
         }
         return false;
     }
+
+    /**
+     * Returns {@code true} if this device is marked as a financed device.
+     *
+     * <p>A financed device can be entered into lock task mode (see {@link #setLockTaskPackages})
+     * by the holder of the role {@link android.app.role.RoleManager#ROLE_FINANCED_DEVICE_KIOSK}.
+     * If this occurs, Device Owners and Profile Owners that have set lock task packages or
+     * features, or that attempt to set lock task packages or features, will receive a callback
+     * indicating that it could not be set. See {@link PolicyUpdateReceiver#onPolicyChanged} and
+     * {@link PolicyUpdateReceiver#onPolicySetResult}.
+     *
+     * <p>To be informed of changes to this status you can subscribe to the broadcast
+     * {@link ACTION_DEVICE_FINANCING_STATE_CHANGED}.
+     *
+     * @throws SecurityException if the caller is not a device owner, profile owner of an
+     * organization-owned managed profile, profile owner on the primary user or holder of one of the
+     * following roles: {@link android.app.role.RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT},
+     * android.app.role.RoleManager.ROLE_SYSTEM_SUPERVISION.
+     */
+    public boolean isDeviceFinanced() {
+        if (mService != null) {
+            try {
+                return mService.isDeviceFinanced(mContext.getPackageName());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns the package name of the application holding the role:
+     * {@link android.app.role.RoleManager#ROLE_FINANCED_DEVICE_KIOSK}.
+     *
+     * @return the package name of the application holding the role or {@code null} if the role is
+     * not held by any applications.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)
+    @Nullable
+    public String getFinancedDeviceKioskRoleHolder() {
+        if (mService != null) {
+            try {
+                return mService.getFinancedDeviceKioskRoleHolder(mContext.getPackageName());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return null;
+    }
 }
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 51aff9e..e202ac2 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -605,4 +605,7 @@
     void setOverrideKeepProfilesRunning(boolean enabled);
 
     boolean triggerDevicePolicyEngineMigration(boolean forceMigration);
+
+    boolean isDeviceFinanced(String callerPackageName);
+    String getFinancedDeviceKioskRoleHolder(String callerPackageName);
 }
diff --git a/core/java/android/app/admin/IntentFilterPolicyKey.java b/core/java/android/app/admin/IntentFilterPolicyKey.java
index b0af4cd..30aad96 100644
--- a/core/java/android/app/admin/IntentFilterPolicyKey.java
+++ b/core/java/android/app/admin/IntentFilterPolicyKey.java
@@ -23,6 +23,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.content.IntentFilter;
 import android.os.Bundle;
 import android.os.Parcel;
@@ -49,6 +50,7 @@
     /**
      * @hide
      */
+    @TestApi
     public IntentFilterPolicyKey(@NonNull String identifier, @NonNull IntentFilter filter) {
         super(identifier);
         mFilter = Objects.requireNonNull(filter);
diff --git a/core/java/android/app/admin/PackagePermissionPolicyKey.java b/core/java/android/app/admin/PackagePermissionPolicyKey.java
index 08c4224..7fd514c 100644
--- a/core/java/android/app/admin/PackagePermissionPolicyKey.java
+++ b/core/java/android/app/admin/PackagePermissionPolicyKey.java
@@ -24,6 +24,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -53,6 +54,7 @@
     /**
      * @hide
      */
+    @TestApi
     public PackagePermissionPolicyKey(@NonNull String identifier, @NonNull String packageName,
             @NonNull String permissionName) {
         super(identifier);
diff --git a/core/java/android/app/admin/PackagePolicyKey.java b/core/java/android/app/admin/PackagePolicyKey.java
index b2a8d5d..2ab00bc 100644
--- a/core/java/android/app/admin/PackagePolicyKey.java
+++ b/core/java/android/app/admin/PackagePolicyKey.java
@@ -23,6 +23,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -50,6 +51,7 @@
     /**
      * @hide
      */
+    @TestApi
     public PackagePolicyKey(@NonNull String key, @NonNull String packageName) {
         super(key);
         mPackageName = Objects.requireNonNull((packageName));
diff --git a/core/java/android/app/admin/UserRestrictionPolicyKey.java b/core/java/android/app/admin/UserRestrictionPolicyKey.java
index 880b58b..aeb2380 100644
--- a/core/java/android/app/admin/UserRestrictionPolicyKey.java
+++ b/core/java/android/app/admin/UserRestrictionPolicyKey.java
@@ -20,6 +20,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.os.Bundle;
 import android.os.Parcel;
 
@@ -40,6 +41,7 @@
     /**
      * @hide
      */
+    @TestApi
     public UserRestrictionPolicyKey(@NonNull String identifier, @NonNull String restriction) {
         super(identifier);
         mRestriction = Objects.requireNonNull(restriction);
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index fe10b7f..27f6a26 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -31,6 +31,7 @@
 import android.content.pm.LauncherApps;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.Resources;
+import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.PointF;
 import android.graphics.Rect;
@@ -311,20 +312,27 @@
             super.onLayout(changed, left, top, right, bottom);
         } catch (final RuntimeException e) {
             Log.e(TAG, "Remote provider threw runtime exception, using error view instead.", e);
-            removeViewInLayout(mView);
-            View child = getErrorView();
-            prepareView(child);
-            addViewInLayout(child, 0, child.getLayoutParams());
-            measureChild(child, MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
-                    MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
-            child.layout(0, 0, child.getMeasuredWidth() + mPaddingLeft + mPaddingRight,
-                    child.getMeasuredHeight() + mPaddingTop + mPaddingBottom);
-            mView = child;
-            mViewMode = VIEW_MODE_ERROR;
+            handleViewError();
         }
     }
 
     /**
+     * Remove bad view and replace with error message view
+     */
+    private void handleViewError() {
+        removeViewInLayout(mView);
+        View child = getErrorView();
+        prepareView(child);
+        addViewInLayout(child, 0, child.getLayoutParams());
+        measureChild(child, MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
+                MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
+        child.layout(0, 0, child.getMeasuredWidth() + mPaddingLeft + mPaddingRight,
+                child.getMeasuredHeight() + mPaddingTop + mPaddingBottom);
+        mView = child;
+        mViewMode = VIEW_MODE_ERROR;
+    }
+
+    /**
      * Provide guidance about the size of this widget to the AppWidgetManager. The widths and
      * heights should correspond to the full area the AppWidgetHostView is given. Padding added by
      * the framework will be accounted for automatically. This information gets embedded into the
@@ -953,4 +961,15 @@
             reapplyLastRemoteViews();
         }
     }
+
+    @Override
+    protected void dispatchDraw(@NonNull Canvas canvas) {
+        try {
+            super.dispatchDraw(canvas);
+        } catch (Exception e) {
+            // Catch draw exceptions that may be caused by RemoteViews
+            Log.e(TAG, "Drawing view failed: " + e);
+            post(this::handleViewError);
+        }
+    }
 }
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 85daf15..df8da24 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -5914,15 +5914,15 @@
     /**
      * A Parcelable[] of {@link ChooserAction} objects to provide the Android Sharesheet with
      * app-specific actions to be presented to the user when invoking {@link #ACTION_CHOOSER}.
+     * You can provide as many as five custom actions.
      */
     public static final String EXTRA_CHOOSER_CUSTOM_ACTIONS =
             "android.intent.extra.CHOOSER_CUSTOM_ACTIONS";
 
     /**
      * 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/IntentFilter.java b/core/java/android/content/IntentFilter.java
index 5928a50..6ff4271 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -2204,6 +2204,7 @@
      * <p> Subsequent calls to this method  will override any previously set extras.
      *
      * @param extras The intent extras to match against.
+     * @hide
      */
     public final void setExtras(@NonNull PersistableBundle extras) {
         mExtras = extras;
@@ -2214,6 +2215,7 @@
      *
      * @return the extras that were previously set using {@link #setExtras(PersistableBundle)} or
      *         an empty {@link PersistableBundle} object if no extras were set.
+     * @hide
      */
     public final @NonNull PersistableBundle getExtras() {
         return mExtras == null ? new PersistableBundle() : mExtras;
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/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..dfb9cf6 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -2550,41 +2550,15 @@
      * <ul>
      *   <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_UNSPECIFIED UNSPECIFIED}</li>
      *   <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_SRGB SRGB}</li>
-     *   <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_LINEAR_SRGB LINEAR_SRGB}</li>
-     *   <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_EXTENDED_SRGB EXTENDED_SRGB}</li>
-     *   <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_LINEAR_EXTENDED_SRGB LINEAR_EXTENDED_SRGB}</li>
-     *   <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_BT709 BT709}</li>
-     *   <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_BT2020 BT2020}</li>
-     *   <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_DCI_P3 DCI_P3}</li>
      *   <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_DISPLAY_P3 DISPLAY_P3}</li>
-     *   <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_NTSC_1953 NTSC_1953}</li>
-     *   <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_SMPTE_C SMPTE_C}</li>
-     *   <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_ADOBE_RGB ADOBE_RGB}</li>
-     *   <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_PRO_PHOTO_RGB PRO_PHOTO_RGB}</li>
-     *   <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_ACES ACES}</li>
-     *   <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_ACESCG ACESCG}</li>
-     *   <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_CIE_XYZ CIE_XYZ}</li>
-     *   <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_CIE_LAB CIE_LAB}</li>
+     *   <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_BT2020_HLG BT2020_HLG}</li>
      * </ul>
      *
      * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
      * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_UNSPECIFIED
      * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_SRGB
-     * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_LINEAR_SRGB
-     * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_EXTENDED_SRGB
-     * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_LINEAR_EXTENDED_SRGB
-     * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_BT709
-     * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_BT2020
-     * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_DCI_P3
      * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_DISPLAY_P3
-     * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_NTSC_1953
-     * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_SMPTE_C
-     * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_ADOBE_RGB
-     * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_PRO_PHOTO_RGB
-     * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_ACES
-     * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_ACESCG
-     * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_CIE_XYZ
-     * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_CIE_LAB
+     * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_BT2020_HLG
      * @hide
      */
     public static final Key<long[]> REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP =
@@ -4123,7 +4097,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 +4123,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 +4148,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 +4169,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..696873f 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -121,10 +121,28 @@
      * System property for allowing the above
      * @hide
      */
+    @TestApi
     public static final String LANDSCAPE_TO_PORTRAIT_PROP =
             "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) {
@@ -605,6 +623,16 @@
     @NonNull
     public CameraCharacteristics getCameraCharacteristics(@NonNull String cameraId)
             throws CameraAccessException {
+        return getCameraCharacteristics(cameraId, shouldOverrideToPortrait(mContext));
+    }
+
+    /**
+     * @hide
+     */
+    @TestApi
+    @NonNull
+    public CameraCharacteristics getCameraCharacteristics(@NonNull String cameraId,
+            boolean overrideToPortrait) throws CameraAccessException {
         CameraCharacteristics characteristics = null;
         if (CameraManagerGlobal.sCameraServiceDisabled) {
             throw new IllegalArgumentException("No cameras available on device");
@@ -618,7 +646,6 @@
             try {
                 Size displaySize = getDisplaySize();
 
-                boolean overrideToPortrait = shouldOverrideToPortrait(mContext);
                 CameraMetadataNative info = cameraService.getCameraCharacteristics(cameraId,
                         mContext.getApplicationInfo().targetSdkVersion, overrideToPortrait);
                 try {
@@ -710,7 +737,7 @@
      */
     private CameraDevice openCameraDeviceUserAsync(String cameraId,
             CameraDevice.StateCallback callback, Executor executor, final int uid,
-            final int oomScoreOffset) throws CameraAccessException {
+            final int oomScoreOffset, boolean overrideToPortrait) throws CameraAccessException {
         CameraCharacteristics characteristics = getCameraCharacteristics(cameraId);
         CameraDevice device = null;
         Map<String, CameraCharacteristics> physicalIdsToChars =
@@ -738,7 +765,6 @@
                         "Camera service is currently unavailable");
                 }
 
-                boolean overrideToPortrait = shouldOverrideToPortrait(mContext);
                 cameraUser = cameraService.connectDevice(callbacks, cameraId,
                     mContext.getOpPackageName(), mContext.getAttributionTag(), uid,
                     oomScoreOffset, mContext.getApplicationInfo().targetSdkVersion,
@@ -874,6 +900,43 @@
     }
 
     /**
+     * Open a connection to a camera with the given ID. Also specify overrideToPortrait for testing.
+     *
+     * @param cameraId
+     *             The unique identifier of the camera device to open
+     * @param handler
+     *             The handler on which the callback should be invoked, or
+     *             {@code null} to use the current thread's {@link android.os.Looper looper}.
+     * @param callback
+     *             The callback which is invoked once the camera is opened
+     * @param overrideToPortrait
+     *             Whether to apply the landscape to portrait override, using rotate and crop.
+     *
+     * @throws CameraAccessException if the camera is disabled by device policy,
+     * has been disconnected, or is being used by a higher-priority camera API client.
+     *
+     * @throws IllegalArgumentException if cameraId, the callback or the executor was null,
+     * or the cameraId does not match any currently or previously available
+     * camera device.
+     *
+     * @throws SecurityException if the application does not have permission to
+     * access the camera
+     *
+     * @see #getCameraIdList
+     * @see android.app.admin.DevicePolicyManager#setCameraDisabled
+     *
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.CAMERA)
+    public void openCamera(@NonNull String cameraId, boolean overrideToPortrait,
+            @Nullable Handler handler,
+            @NonNull final CameraDevice.StateCallback callback) throws CameraAccessException {
+        openCameraForUid(cameraId, callback, CameraDeviceImpl.checkAndWrapHandler(handler),
+                         USE_CALLING_UID, /*oomScoreOffset*/0, overrideToPortrait);
+    }
+
+    /**
      * Open a connection to a camera with the given ID.
      *
      * <p>The behavior of this method matches that of
@@ -977,7 +1040,8 @@
             throw new IllegalArgumentException(
                     "oomScoreOffset < 0, cannot increase priority of camera client");
         }
-        openCameraForUid(cameraId, callback, executor, USE_CALLING_UID, oomScoreOffset);
+        openCameraForUid(cameraId, callback, executor, USE_CALLING_UID, oomScoreOffset,
+                shouldOverrideToPortrait(mContext));
     }
 
     /**
@@ -999,7 +1063,8 @@
      */
     public void openCameraForUid(@NonNull String cameraId,
             @NonNull final CameraDevice.StateCallback callback, @NonNull Executor executor,
-            int clientUid, int oomScoreOffset) throws CameraAccessException {
+            int clientUid, int oomScoreOffset, boolean overrideToPortrait)
+            throws CameraAccessException {
 
         if (cameraId == null) {
             throw new IllegalArgumentException("cameraId was null");
@@ -1010,7 +1075,8 @@
             throw new IllegalArgumentException("No cameras available on device");
         }
 
-        openCameraDeviceUserAsync(cameraId, callback, executor, clientUid, oomScoreOffset);
+        openCameraDeviceUserAsync(cameraId, callback, executor, clientUid, oomScoreOffset,
+                overrideToPortrait);
     }
 
     /**
@@ -1031,7 +1097,8 @@
     public void openCameraForUid(@NonNull String cameraId,
             @NonNull final CameraDevice.StateCallback callback, @NonNull Executor executor,
             int clientUid) throws CameraAccessException {
-            openCameraForUid(cameraId, callback, executor, clientUid, /*oomScoreOffset*/0);
+        openCameraForUid(cameraId, callback, executor, clientUid, /*oomScoreOffset*/0,
+                shouldOverrideToPortrait(mContext));
     }
 
     /**
@@ -1174,17 +1241,32 @@
      * @hide
      */
     public static boolean shouldOverrideToPortrait(@Nullable Context context) {
+        PackageManager packageManager = null;
+        String packageName = null;
+
+        if (context != null) {
+            packageManager = context.getPackageManager();
+            packageName = context.getOpPackageName();
+        }
+
+        return shouldOverrideToPortrait(packageManager, packageName);
+    }
+
+    /**
+     * @hide
+     */
+    @TestApi
+    public static boolean shouldOverrideToPortrait(@Nullable PackageManager packageManager,
+                                                   @Nullable String packageName) {
         if (!CameraManagerGlobal.sLandscapeToPortrait) {
             return false;
         }
 
-        if (context != null) {
-            PackageManager packageManager = context.getPackageManager();
-
+        if (packageManager != null && packageName != null) {
             try {
                 return packageManager.getProperty(
                         PackageManager.PROPERTY_COMPAT_OVERRIDE_LANDSCAPE_TO_PORTRAIT,
-                        context.getOpPackageName()).getBoolean();
+                        packageName).getBoolean();
             } catch (PackageManager.NameNotFoundException e) {
                 // No such property
             }
@@ -1194,6 +1276,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 +1360,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 +1375,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 +1424,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 +1439,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 +2429,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 +2563,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/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/InputDeviceLightsManager.java b/core/java/android/hardware/input/InputDeviceLightsManager.java
index 802e6dd..f4ee9a2 100644
--- a/core/java/android/hardware/input/InputDeviceLightsManager.java
+++ b/core/java/android/hardware/input/InputDeviceLightsManager.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.app.ActivityThread;
+import android.content.Context;
 import android.hardware.lights.Light;
 import android.hardware.lights.LightState;
 import android.hardware.lights.LightsManager;
@@ -30,22 +31,22 @@
 import java.util.List;
 
 /**
- * LightsManager manages an input device's lights {@link android.hardware.input.Light}.
+ * LightsManager manages an input device's lights {@link android.hardware.lights.Light}
  */
 class InputDeviceLightsManager extends LightsManager {
     private static final String TAG = "InputDeviceLightsManager";
     private static final boolean DEBUG = false;
 
-    private final InputManager mInputManager;
+    private final InputManagerGlobal mGlobal;
 
     // The input device ID.
     private final int mDeviceId;
     // Package name
     private final String mPackageName;
 
-    InputDeviceLightsManager(InputManager inputManager, int deviceId) {
-        super(ActivityThread.currentActivityThread().getSystemContext());
-        mInputManager = inputManager;
+    InputDeviceLightsManager(Context context, int deviceId) {
+        super(context);
+        mGlobal = InputManagerGlobal.getInstance();
         mDeviceId = deviceId;
         mPackageName = ActivityThread.currentPackageName();
     }
@@ -57,7 +58,7 @@
      */
     @Override
     public @NonNull List<Light> getLights() {
-        return mInputManager.getLights(mDeviceId);
+        return mGlobal.getLights(mDeviceId);
     }
 
     /**
@@ -68,7 +69,7 @@
     @Override
     public @NonNull LightState getLightState(@NonNull Light light) {
         Preconditions.checkNotNull(light);
-        return mInputManager.getLightState(mDeviceId, light);
+        return mGlobal.getLightState(mDeviceId, light);
     }
 
     /**
@@ -77,7 +78,7 @@
     @Override
     public @NonNull LightsSession openSession() {
         final LightsSession session = new InputDeviceLightsSession();
-        mInputManager.openLightSession(mDeviceId, mPackageName, session.getToken());
+        mGlobal.openLightSession(mDeviceId, mPackageName, session.getToken());
         return session;
     }
 
@@ -113,7 +114,7 @@
             Preconditions.checkNotNull(request);
             Preconditions.checkArgument(!mClosed);
 
-            mInputManager.requestLights(mDeviceId, request, getToken());
+            mGlobal.requestLights(mDeviceId, request, getToken());
         }
 
         /**
@@ -122,7 +123,7 @@
         @Override
         public void close() {
             if (!mClosed) {
-                mInputManager.closeLightSession(mDeviceId, getToken());
+                mGlobal.closeLightSession(mDeviceId, getToken());
                 mClosed = true;
                 mCloseGuard.close();
             }
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/InputDeviceVibrator.java b/core/java/android/hardware/input/InputDeviceVibrator.java
index ce6b523..9c18260 100644
--- a/core/java/android/hardware/input/InputDeviceVibrator.java
+++ b/core/java/android/hardware/input/InputDeviceVibrator.java
@@ -45,14 +45,14 @@
     private final int mDeviceId;
     private final VibratorInfo mVibratorInfo;
     private final Binder mToken;
-    private final InputManager mInputManager;
+    private final InputManagerGlobal mGlobal;
 
     @GuardedBy("mDelegates")
     private final ArrayMap<OnVibratorStateChangedListener,
             OnVibratorStateChangedListenerDelegate> mDelegates = new ArrayMap<>();
 
-    InputDeviceVibrator(InputManager inputManager, int deviceId, int vibratorId) {
-        mInputManager = inputManager;
+    InputDeviceVibrator(int deviceId, int vibratorId) {
+        mGlobal = InputManagerGlobal.getInstance();
         mDeviceId = deviceId;
         mVibratorInfo = new VibratorInfo.Builder(vibratorId)
                 .setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL)
@@ -93,7 +93,7 @@
 
     @Override
     public boolean isVibrating() {
-        return mInputManager.isVibrating(mDeviceId);
+        return mGlobal.isVibrating(mDeviceId);
     }
 
     /**
@@ -132,7 +132,7 @@
 
             final OnVibratorStateChangedListenerDelegate delegate =
                     new OnVibratorStateChangedListenerDelegate(listener, executor);
-            if (!mInputManager.registerVibratorStateListener(mDeviceId, delegate)) {
+            if (!mGlobal.registerVibratorStateListener(mDeviceId, delegate)) {
                 Log.w(TAG, "Failed to register vibrate state listener");
                 return;
             }
@@ -156,7 +156,7 @@
             if (mDelegates.containsKey(listener)) {
                 final OnVibratorStateChangedListenerDelegate delegate = mDelegates.get(listener);
 
-                if (!mInputManager.unregisterVibratorStateListener(mDeviceId, delegate)) {
+                if (!mGlobal.unregisterVibratorStateListener(mDeviceId, delegate)) {
                     Log.w(TAG, "Failed to unregister vibrate state listener");
                     return;
                 }
@@ -176,12 +176,12 @@
     @Override
     public void vibrate(int uid, String opPkg, @NonNull VibrationEffect effect, String reason,
             @NonNull VibrationAttributes attributes) {
-        mInputManager.vibrate(mDeviceId, effect, mToken);
+        mGlobal.vibrate(mDeviceId, effect, mToken);
     }
 
     @Override
     public void cancel() {
-        mInputManager.cancelVibrate(mDeviceId, mToken);
+        mGlobal.cancelVibrate(mDeviceId, mToken);
     }
 
     @Override
diff --git a/core/java/android/hardware/input/InputDeviceVibratorManager.java b/core/java/android/hardware/input/InputDeviceVibratorManager.java
index d77f9c3..64b56677 100644
--- a/core/java/android/hardware/input/InputDeviceVibratorManager.java
+++ b/core/java/android/hardware/input/InputDeviceVibratorManager.java
@@ -40,7 +40,7 @@
     private static final boolean DEBUG = false;
 
     private final Binder mToken;
-    private final InputManager mInputManager;
+    private final InputManagerGlobal mGlobal;
 
     // The input device Id.
     private final int mDeviceId;
@@ -48,8 +48,8 @@
     @GuardedBy("mVibrators")
     private final SparseArray<Vibrator> mVibrators = new SparseArray<>();
 
-    public InputDeviceVibratorManager(InputManager inputManager, int deviceId) {
-        mInputManager = inputManager;
+    public InputDeviceVibratorManager(int deviceId) {
+        mGlobal = InputManagerGlobal.getInstance();
         mDeviceId = deviceId;
         mToken = new Binder();
 
@@ -61,10 +61,10 @@
             mVibrators.clear();
             InputDevice inputDevice = InputDevice.getDevice(mDeviceId);
             final int[] vibratorIds =
-                    mInputManager.getVibratorIds(mDeviceId);
+                    mGlobal.getVibratorIds(mDeviceId);
             for (int i = 0; i < vibratorIds.length; i++) {
                 mVibrators.put(vibratorIds[i],
-                        new InputDeviceVibrator(mInputManager, mDeviceId, vibratorIds[i]));
+                        new InputDeviceVibrator(mDeviceId, vibratorIds[i]));
             }
         }
     }
@@ -127,12 +127,12 @@
     @Override
     public void vibrate(int uid, String opPkg, @NonNull CombinedVibration effect,
             String reason, @Nullable VibrationAttributes attributes) {
-        mInputManager.vibrate(mDeviceId, effect, mToken);
+        mGlobal.vibrate(mDeviceId, effect, mToken);
     }
 
     @Override
     public void cancel() {
-        mInputManager.cancelVibrate(mDeviceId, mToken);
+        mGlobal.cancelVibrate(mDeviceId, mToken);
     }
 
     @Override
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 9662be3..5dc3825 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -33,25 +33,18 @@
 import android.content.Context;
 import android.hardware.BatteryState;
 import android.hardware.SensorManager;
-import android.hardware.lights.Light;
-import android.hardware.lights.LightState;
 import android.hardware.lights.LightsManager;
-import android.hardware.lights.LightsRequest;
 import android.os.Binder;
 import android.os.Build;
-import android.os.CombinedVibration;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.IVibratorStateListener;
 import android.os.InputEventInjectionSync;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
-import android.os.VibrationEffect;
 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;
@@ -64,7 +57,6 @@
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodSubtype;
 
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.lang.annotation.Retention;
@@ -105,21 +97,6 @@
     @Nullable
     private Boolean mIsStylusPointerIconEnabled = null;
 
-    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>
@@ -1240,11 +1217,7 @@
      * @hide
      */
     public InputSensorInfo[] getSensorList(int deviceId) {
-        try {
-            return mIm.getSensorList(deviceId);
-        } catch (RemoteException ex) {
-            throw ex.rethrowFromSystemServer();
-        }
+        return mGlobal.getSensorList(deviceId);
     }
 
     /**
@@ -1254,12 +1227,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);
     }
 
     /**
@@ -1268,11 +1237,7 @@
      * @hide
      */
     public void disableSensor(int deviceId, int sensorType) {
-        try {
-            mIm.disableSensor(deviceId, sensorType);
-        } catch (RemoteException ex) {
-            throw ex.rethrowFromSystemServer();
-        }
+        mGlobal.disableSensor(deviceId, sensorType);
     }
 
     /**
@@ -1281,11 +1246,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);
     }
 
     /**
@@ -1294,11 +1255,7 @@
      * @hide
      */
     public boolean registerSensorListener(IInputSensorEventListener listener) {
-        try {
-            return mIm.registerSensorListener(listener);
-        } catch (RemoteException ex) {
-            throw ex.rethrowFromSystemServer();
-        }
+        return mGlobal.registerSensorListener(listener);
     }
 
     /**
@@ -1307,11 +1264,7 @@
      * @hide
      */
     public void unregisterSensorListener(IInputSensorEventListener listener) {
-        try {
-            mIm.unregisterSensorListener(listener);
-        } catch (RemoteException ex) {
-            throw ex.rethrowFromSystemServer();
-        }
+        mGlobal.unregisterSensorListener(listener);
     }
 
     /**
@@ -1421,7 +1374,7 @@
      * @hide
      */
     public Vibrator getInputDeviceVibrator(int deviceId, int vibratorId) {
-        return new InputDeviceVibrator(this, deviceId, vibratorId);
+        return new InputDeviceVibrator(deviceId, vibratorId);
     }
 
     /**
@@ -1432,85 +1385,7 @@
      */
     @NonNull
     public VibratorManager getInputDeviceVibratorManager(int deviceId) {
-        return new InputDeviceVibratorManager(InputManager.this, deviceId);
-    }
-
-    /*
-     * Get the list of device vibrators
-     * @return The list of vibrators IDs
-     */
-    int[] getVibratorIds(int deviceId) {
-        try {
-            return mIm.getVibratorIds(deviceId);
-        } catch (RemoteException ex) {
-            throw ex.rethrowFromSystemServer();
-        }
-    }
-
-    /*
-     * Perform vibration effect
-     */
-    void vibrate(int deviceId, VibrationEffect effect, IBinder token) {
-        try {
-            mIm.vibrate(deviceId, effect, token);
-        } catch (RemoteException ex) {
-            throw ex.rethrowFromSystemServer();
-        }
-    }
-
-    /*
-     * Perform combined vibration effect
-     */
-    void vibrate(int deviceId, CombinedVibration effect, IBinder token) {
-        try {
-            mIm.vibrateCombined(deviceId, effect, token);
-        } catch (RemoteException ex) {
-            throw ex.rethrowFromSystemServer();
-        }
-    }
-
-    /*
-     * Cancel an ongoing vibration
-     */
-    void cancelVibrate(int deviceId, IBinder token) {
-        try {
-            mIm.cancelVibrate(deviceId, token);
-        } catch (RemoteException ex) {
-            throw ex.rethrowFromSystemServer();
-        }
-    }
-
-    /*
-     * Check if input device is vibrating
-     */
-    boolean isVibrating(int deviceId)  {
-        try {
-            return mIm.isVibrating(deviceId);
-        } catch (RemoteException ex) {
-            throw ex.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Register input device vibrator state listener
-     */
-    boolean registerVibratorStateListener(int deviceId, IVibratorStateListener listener) {
-        try {
-            return mIm.registerVibratorStateListener(deviceId, listener);
-        } catch (RemoteException ex) {
-            throw ex.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Unregister input device vibrator state listener
-     */
-    boolean unregisterVibratorStateListener(int deviceId, IVibratorStateListener listener) {
-        try {
-            return mIm.unregisterVibratorStateListener(deviceId, listener);
-        } catch (RemoteException ex) {
-            throw ex.rethrowFromSystemServer();
-        }
+        return new InputDeviceVibratorManager(deviceId);
     }
 
     /**
@@ -1520,10 +1395,7 @@
      */
     @NonNull
     public SensorManager getInputDeviceSensorManager(int deviceId) {
-        if (mInputDeviceSensorManager == null) {
-            mInputDeviceSensorManager = new InputDeviceSensorManager(this);
-        }
-        return mInputDeviceSensorManager.getSensorManager(deviceId);
+        return mGlobal.getInputDeviceSensorManager(deviceId);
     }
 
     /**
@@ -1533,15 +1405,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);
     }
 
     /**
@@ -1551,77 +1415,7 @@
      */
     @NonNull
     public LightsManager getInputDeviceLightsManager(int deviceId) {
-        return new InputDeviceLightsManager(InputManager.this, deviceId);
-    }
-
-    /**
-     * Gets a list of light objects associated with an input device.
-     * @return The list of lights, never null.
-     */
-    @NonNull List<Light> getLights(int deviceId) {
-        try {
-            return mIm.getLights(deviceId);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Returns the state of an input device light.
-     * @return the light state
-     */
-    @NonNull LightState getLightState(int deviceId, @NonNull Light light) {
-        try {
-            return mIm.getLightState(deviceId, light.getId());
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Request to modify the states of multiple lights.
-     *
-     * @param request the settings for lights that should change
-     */
-    void requestLights(int deviceId, @NonNull LightsRequest request, IBinder token) {
-        try {
-            List<Integer> lightIdList = request.getLights();
-            int[] lightIds = new int[lightIdList.size()];
-            for (int i = 0; i < lightIds.length; i++) {
-                lightIds[i] = lightIdList.get(i);
-            }
-            List<LightState> lightStateList = request.getLightStates();
-            mIm.setLightStates(deviceId, lightIds,
-                    lightStateList.toArray(new LightState[0]),
-                    token);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Open light session for input device manager
-     *
-     * @param token The token for the light session
-     */
-    void openLightSession(int deviceId, String opPkg, @NonNull IBinder token) {
-        try {
-            mIm.openLightSession(deviceId, opPkg, token);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Close light session
-     *
-     */
-    void closeLightSession(int deviceId, @NonNull IBinder token) {
-        try {
-            mIm.closeLightSession(deviceId, token);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        return new InputDeviceLightsManager(getContext(), deviceId);
     }
 
     /**
@@ -1677,49 +1471,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);
     }
 
     /**
@@ -1729,44 +1481,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);
     }
 
     /**
@@ -1792,30 +1507,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);
     }
 
     /**
@@ -1828,23 +1520,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);
     }
 
     /**
@@ -1929,133 +1605,4 @@
         void onKeyboardBacklightChanged(
                 int deviceId, @NonNull KeyboardBacklightState state, boolean isTriggeredByKeyPress);
     }
-
-    // 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 28699e0..08d81bd 100644
--- a/core/java/android/hardware/input/InputManagerGlobal.java
+++ b/core/java/android/hardware/input/InputManagerGlobal.java
@@ -16,17 +16,29 @@
 
 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.hardware.lights.Light;
+import android.hardware.lights.LightState;
+import android.hardware.lights.LightsRequest;
+import android.os.CombinedVibration;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.IVibratorStateListener;
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.VibrationEffect;
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.Display;
@@ -36,11 +48,13 @@
 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
  */
@@ -61,6 +75,22 @@
     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;
 
     private final IInputManager mIm;
@@ -503,4 +533,528 @@
         }
         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();
+        }
+    }
+
+    /**
+     * Gets a list of light objects associated with an input device.
+     * @return The list of lights, never null.
+     */
+    @NonNull List<Light> getLights(int deviceId) {
+        try {
+            return mIm.getLights(deviceId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns the state of an input device light.
+     * @return the light state
+     */
+    @NonNull LightState getLightState(int deviceId, @NonNull Light light) {
+        try {
+            return mIm.getLightState(deviceId, light.getId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Request to modify the states of multiple lights.
+     *
+     * @param request the settings for lights that should change
+     */
+    void requestLights(int deviceId, @NonNull LightsRequest request, IBinder token) {
+        try {
+            List<Integer> lightIdList = request.getLights();
+            int[] lightIds = new int[lightIdList.size()];
+            for (int i = 0; i < lightIds.length; i++) {
+                lightIds[i] = lightIdList.get(i);
+            }
+            List<LightState> lightStateList = request.getLightStates();
+            mIm.setLightStates(deviceId, lightIds,
+                    lightStateList.toArray(new LightState[0]),
+                    token);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Open light session for input device manager
+     *
+     * @param token The token for the light session
+     */
+    void openLightSession(int deviceId, String opPkg, @NonNull IBinder token) {
+        try {
+            mIm.openLightSession(deviceId, opPkg, token);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Close light session
+     *
+     */
+    void closeLightSession(int deviceId, @NonNull IBinder token) {
+        try {
+            mIm.closeLightSession(deviceId, token);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /*
+     * Get the list of device vibrators
+     * @return The list of vibrators IDs
+     */
+    int[] getVibratorIds(int deviceId) {
+        try {
+            return mIm.getVibratorIds(deviceId);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /*
+     * Perform vibration effect
+     */
+    void vibrate(int deviceId, VibrationEffect effect, IBinder token) {
+        try {
+            mIm.vibrate(deviceId, effect, token);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /*
+     * Perform combined vibration effect
+     */
+    void vibrate(int deviceId, CombinedVibration effect, IBinder token) {
+        try {
+            mIm.vibrateCombined(deviceId, effect, token);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /*
+     * Cancel an ongoing vibration
+     */
+    void cancelVibrate(int deviceId, IBinder token) {
+        try {
+            mIm.cancelVibrate(deviceId, token);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /*
+     * Check if input device is vibrating
+     */
+    boolean isVibrating(int deviceId)  {
+        try {
+            return mIm.isVibrating(deviceId);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Register input device vibrator state listener
+     */
+    boolean registerVibratorStateListener(int deviceId, IVibratorStateListener listener) {
+        try {
+            return mIm.registerVibratorStateListener(deviceId, listener);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Unregister input device vibrator state listener
+     */
+    boolean unregisterVibratorStateListener(int deviceId, IVibratorStateListener listener) {
+        try {
+            return mIm.unregisterVibratorStateListener(deviceId, listener);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/hardware/input/VirtualKeyboardConfig.java b/core/java/android/hardware/input/VirtualKeyboardConfig.java
index d788df4..6d03065 100644
--- a/core/java/android/hardware/input/VirtualKeyboardConfig.java
+++ b/core/java/android/hardware/input/VirtualKeyboardConfig.java
@@ -110,10 +110,7 @@
 
         /**
          * Sets the preferred input language of the virtual keyboard using an IETF
-         * <a href="https://tools.ietf.org/html/bcp47">BCP-47</a>
-         * conformant tag. See {@code keyboardLocale} attribute in
-         * frameworks/base/packages/InputDevices/res/xml/keyboard_layouts.xml for a list of
-         * supported language tags.
+         * <a href="https://tools.ietf.org/html/bcp47">BCP-47</a>  conformant tag.
          *
          * The passed in {@code languageTag} will be canonized using {@link
          * ULocale} and used by the system as a hint to configure the keyboard layout.
@@ -135,7 +132,7 @@
         public Builder setLanguageTag(@NonNull String languageTag) {
             Objects.requireNonNull(languageTag, "languageTag cannot be null");
             ULocale locale = ULocale.forLanguageTag(languageTag);
-            if (locale.getLanguage().isEmpty() || locale.getCountry().isEmpty()) {
+            if (locale.getLanguage().isEmpty()) {
                 throw new IllegalArgumentException("The language tag is not valid.");
             }
             mLanguageTag = ULocale.createCanonical(locale).toLanguageTag();
@@ -144,8 +141,8 @@
 
         /**
          * Sets the preferred layout type of the virtual keyboard. See {@code keyboardLayoutType}
-         * attribute in frameworks/base/packages/InputDevices/res/xml/keyboard_layouts.xml for a
-         * list of supported layout types.
+         * attribute in frameworks/base/core/res/res/values/attrs.xml for a list of supported
+         * layout types.
          *
          * Note that the preferred layout is not guaranteed. If the specified layout type is
          * well-formed but not supported, the keyboard will be using English US QWERTY layout.
diff --git a/core/java/android/hardware/radio/IRadioService.aidl b/core/java/android/hardware/radio/IRadioService.aidl
index c7131a7..9349cf7 100644
--- a/core/java/android/hardware/radio/IRadioService.aidl
+++ b/core/java/android/hardware/radio/IRadioService.aidl
@@ -31,7 +31,7 @@
     List<RadioManager.ModuleProperties> listModules();
 
     ITuner openTuner(int moduleId, in RadioManager.BandConfig bandConfig, boolean withAudio,
-            in ITunerCallback callback, int targetSdkVersion);
+            in ITunerCallback callback);
 
     ICloseHandle addAnnouncementListener(in int[] enabledTypes,
             in IAnnouncementListener listener);
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
index f072e3b..8c6083c 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -1796,7 +1796,7 @@
         ITuner tuner;
         TunerCallbackAdapter halCallback = new TunerCallbackAdapter(callback, handler);
         try {
-            tuner = mService.openTuner(moduleId, config, withAudio, halCallback, mTargetSdkVersion);
+            tuner = mService.openTuner(moduleId, config, withAudio, halCallback);
         } catch (RemoteException | IllegalArgumentException | IllegalStateException ex) {
             Log.e(TAG, "Failed to open tuner", ex);
             return null;
@@ -1873,7 +1873,6 @@
 
     @NonNull private final Context mContext;
     @NonNull private final IRadioService mService;
-    private final int mTargetSdkVersion;
 
     /**
      * @hide
@@ -1890,6 +1889,5 @@
     public RadioManager(Context context, IRadioService service) {
         mContext = context;
         mService = service;
-        mTargetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
     }
 }
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..123f480 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.
@@ -10014,6 +10022,21 @@
                 "emergency_gesture_sound_enabled";
 
         /**
+         * Whether the emergency gesture UI is currently showing.
+         *
+         * @hide
+         */
+        public static final String EMERGENCY_GESTURE_UI_SHOWING = "emergency_gesture_ui_showing";
+
+        /**
+         * The last time the emergency gesture UI was started.
+         *
+         * @hide
+         */
+        public static final String EMERGENCY_GESTURE_UI_LAST_STARTED_MILLIS =
+                "emergency_gesture_ui_last_started_millis";
+
+        /**
          * Whether the camera launch gesture to double tap the power button when the screen is off
          * should be disabled.
          *
@@ -11425,21 +11448,46 @@
         public @interface DeviceStateRotationLockSetting {
         }
 
+        /** @hide */
+        public static final int DEVICE_STATE_ROTATION_KEY_UNKNOWN = -1;
+        /** @hide */
+        public static final int DEVICE_STATE_ROTATION_KEY_FOLDED = 0;
+        /** @hide */
+        public static final int DEVICE_STATE_ROTATION_KEY_HALF_FOLDED = 1;
+        /** @hide */
+        public static final int DEVICE_STATE_ROTATION_KEY_UNFOLDED = 2;
+
+        /**
+         * The different postures that can be used as keys with
+         * {@link #DEVICE_STATE_ROTATION_LOCK}.
+         * @hide
+         */
+        @IntDef(prefix = {"DEVICE_STATE_ROTATION_KEY_"}, value = {
+                DEVICE_STATE_ROTATION_KEY_UNKNOWN,
+                DEVICE_STATE_ROTATION_KEY_FOLDED,
+                DEVICE_STATE_ROTATION_KEY_HALF_FOLDED,
+                DEVICE_STATE_ROTATION_KEY_UNFOLDED,
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface DeviceStateRotationLockKey {
+        }
+
         /**
          * Rotation lock setting keyed on device state.
          *
-         * This holds a serialized map using int keys that represent Device States and value of
+         * This holds a serialized map using int keys that represent postures in
+         * {@link DeviceStateRotationLockKey} and value of
          * {@link DeviceStateRotationLockSetting} representing the rotation lock setting for that
-         * device state.
+         * posture.
          *
          * Serialized as key0:value0:key1:value1:...:keyN:valueN.
          *
          * Example: "0:1:1:2:2:1"
          * This example represents a map of:
          * <ul>
-         *     <li>0 -> DEVICE_STATE_ROTATION_LOCK_LOCKED</li>
-         *     <li>1 -> DEVICE_STATE_ROTATION_LOCK_UNLOCKED</li>
-         *     <li>2 -> DEVICE_STATE_ROTATION_LOCK_IGNORED</li>
+         *     <li>DEVICE_STATE_ROTATION_KEY_FOLDED -> DEVICE_STATE_ROTATION_LOCK_LOCKED</li>
+         *     <li>DEVICE_STATE_ROTATION_KEY_HALF_FOLDED -> DEVICE_STATE_ROTATION_LOCK_UNLOCKED</li>
+         *     <li>DEVICE_STATE_ROTATION_KEY_UNFOLDED -> DEVICE_STATE_ROTATION_LOCK_IGNORED</li>
          * </ul>
          *
          * @hide
@@ -15022,6 +15070,16 @@
                 "emergency_gesture_tap_detection_min_time_ms";
 
         /**
+         * The maximum duration in milliseconds for which the emergency gesture UI can stay
+         * "sticky", where the notification pull-down shade and navigation gestures/buttons are
+         *  temporarily disabled. The feature is disabled completely if the value is set to zero.
+         *
+         * @hide
+         */
+        public static final String EMERGENCY_GESTURE_STICKY_UI_MAX_DURATION_MILLIS =
+                "emergency_gesture_sticky_ui_max_duration_millis";
+
+        /**
          * Whether to enable automatic system server heap dumps. This only works on userdebug or
          * eng builds, not on user builds. This is set by the user and overrides the config value.
          * 1 means enable, 0 means disable.
@@ -18574,7 +18632,7 @@
          * The modes that can be used when disabling syncs to the 'config' settings.
          * @hide
          */
-        @IntDef(prefix = "DISABLE_SYNC_MODE_",
+        @IntDef(prefix = "SYNC_DISABLED_MODE_",
                 value = { SYNC_DISABLED_MODE_NONE, SYNC_DISABLED_MODE_PERSISTENT,
                         SYNC_DISABLED_MODE_UNTIL_REBOOT })
         @Retention(RetentionPolicy.SOURCE)
@@ -18584,23 +18642,36 @@
         /**
          * Sync is not disabled.
          *
+         * @deprecated use the constant in DeviceConfig
+         *
          * @hide
          */
-        public static final int SYNC_DISABLED_MODE_NONE = 0;
+        @Deprecated
+        public static final int SYNC_DISABLED_MODE_NONE = DeviceConfig.SYNC_DISABLED_MODE_NONE;
 
         /**
          * Disabling of Config bulk update / syncing is persistent, i.e. it survives a device
          * reboot.
+         *
+         * @deprecated use the constant in DeviceConfig
+         *
          * @hide
          */
-        public static final int SYNC_DISABLED_MODE_PERSISTENT = 1;
+        @Deprecated
+        public static final int SYNC_DISABLED_MODE_PERSISTENT =
+                DeviceConfig.SYNC_DISABLED_MODE_PERSISTENT;
 
         /**
          * Disabling of Config bulk update / syncing is not persistent, i.e. it will not survive a
          * device reboot.
+         *
+         * @deprecated use the constant in DeviceConfig
+         *
          * @hide
          */
-        public static final int SYNC_DISABLED_MODE_UNTIL_REBOOT = 2;
+        @Deprecated
+        public static final int SYNC_DISABLED_MODE_UNTIL_REBOOT =
+                DeviceConfig.SYNC_DISABLED_MODE_UNTIL_REBOOT;
 
         /**
          * The content:// style URL for the config table.
diff --git a/core/java/android/security/net/config/SystemCertificateSource.java b/core/java/android/security/net/config/SystemCertificateSource.java
index 13f7e5d..3a254c1 100644
--- a/core/java/android/security/net/config/SystemCertificateSource.java
+++ b/core/java/android/security/net/config/SystemCertificateSource.java
@@ -39,9 +39,13 @@
     }
 
     private static File getDirectory() {
-        // TODO(miguelaranda): figure out correct code path.
+        if ((System.getProperty("system.certs.enabled") != null)
+                && (System.getProperty("system.certs.enabled")).equals("true")) {
+            return new File(System.getenv("ANDROID_ROOT") + "/etc/security/cacerts");
+        }
         File updatable_dir = new File("/apex/com.android.conscrypt/cacerts");
-        if (updatable_dir.exists() && !(updatable_dir.list().length == 0)) {
+        if (updatable_dir.exists()
+                && !(updatable_dir.list().length == 0)) {
             return updatable_dir;
         }
         return new File(System.getenv("ANDROID_ROOT") + "/etc/security/cacerts");
diff --git a/core/java/android/service/dreams/DreamOverlayConnectionHandler.java b/core/java/android/service/dreams/DreamOverlayConnectionHandler.java
new file mode 100644
index 0000000..cafe02a
--- /dev/null
+++ b/core/java/android/service/dreams/DreamOverlayConnectionHandler.java
@@ -0,0 +1,242 @@
+/*
+ * 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.service.dreams;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ObservableServiceConnection;
+import com.android.internal.util.PersistentServiceConnection;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Handles the service connection to {@link IDreamOverlay}
+ *
+ * @hide
+ */
+@VisibleForTesting
+public final class DreamOverlayConnectionHandler {
+    private static final String TAG = "DreamOverlayConnection";
+
+    private static final int MSG_ADD_CONSUMER = 1;
+    private static final int MSG_REMOVE_CONSUMER = 2;
+    private static final int MSG_OVERLAY_CLIENT_READY = 3;
+
+    private final Handler mHandler;
+    private final PersistentServiceConnection<IDreamOverlay> mConnection;
+    // Retrieved Client
+    private IDreamOverlayClient mClient;
+    // A list of pending requests to execute on the overlay.
+    private final List<Consumer<IDreamOverlayClient>> mConsumers = new ArrayList<>();
+    private final OverlayConnectionCallback mCallback;
+
+    DreamOverlayConnectionHandler(
+            Context context,
+            Looper looper,
+            Intent serviceIntent,
+            int minConnectionDurationMs,
+            int maxReconnectAttempts,
+            int baseReconnectDelayMs) {
+        this(context, looper, serviceIntent, minConnectionDurationMs, maxReconnectAttempts,
+                baseReconnectDelayMs, new Injector());
+    }
+
+    @VisibleForTesting
+    public DreamOverlayConnectionHandler(
+            Context context,
+            Looper looper,
+            Intent serviceIntent,
+            int minConnectionDurationMs,
+            int maxReconnectAttempts,
+            int baseReconnectDelayMs,
+            Injector injector) {
+        mCallback = new OverlayConnectionCallback();
+        mHandler = new Handler(looper, new OverlayHandlerCallback());
+        mConnection = injector.buildConnection(
+                context,
+                mHandler,
+                serviceIntent,
+                minConnectionDurationMs,
+                maxReconnectAttempts,
+                baseReconnectDelayMs
+        );
+    }
+
+    /**
+     * Bind to the overlay service. If binding fails, we automatically call unbind to clean
+     * up resources.
+     *
+     * @return true if binding was successful, false otherwise.
+     */
+    public boolean bind() {
+        mConnection.addCallback(mCallback);
+        final boolean success = mConnection.bind();
+        if (!success) {
+            unbind();
+        }
+        return success;
+    }
+
+    /**
+     * Unbind from the overlay service, clearing any pending callbacks.
+     */
+    public void unbind() {
+        mConnection.removeCallback(mCallback);
+        // Remove any pending messages.
+        mHandler.removeCallbacksAndMessages(null);
+        mClient = null;
+        mConsumers.clear();
+        mConnection.unbind();
+    }
+
+    /**
+     * Adds a consumer to run once the overlay service has connected. If the overlay service
+     * disconnects (eg binding dies) and then reconnects, this consumer will be re-run unless
+     * removed.
+     *
+     * @param consumer The consumer to run. This consumer is always executed asynchronously.
+     */
+    public void addConsumer(Consumer<IDreamOverlayClient> consumer) {
+        final Message msg = mHandler.obtainMessage(MSG_ADD_CONSUMER, consumer);
+        mHandler.sendMessage(msg);
+    }
+
+    /**
+     * Removes the consumer, preventing this consumer from being called again.
+     *
+     * @param consumer The consumer to remove.
+     */
+    public void removeConsumer(Consumer<IDreamOverlayClient> consumer) {
+        final Message msg = mHandler.obtainMessage(MSG_REMOVE_CONSUMER, consumer);
+        mHandler.sendMessage(msg);
+        // Clear any pending messages to add this consumer
+        mHandler.removeMessages(MSG_ADD_CONSUMER, consumer);
+    }
+
+    private final class OverlayHandlerCallback implements Handler.Callback {
+        @Override
+        public boolean handleMessage(@NonNull Message msg) {
+            switch (msg.what) {
+                case MSG_OVERLAY_CLIENT_READY:
+                    onOverlayClientReady((IDreamOverlayClient) msg.obj);
+                    break;
+                case MSG_ADD_CONSUMER:
+                    onAddConsumer((Consumer<IDreamOverlayClient>) msg.obj);
+                    break;
+                case MSG_REMOVE_CONSUMER:
+                    onRemoveConsumer((Consumer<IDreamOverlayClient>) msg.obj);
+                    break;
+            }
+            return true;
+        }
+    }
+
+    private void onOverlayClientReady(IDreamOverlayClient client) {
+        mClient = client;
+        for (Consumer<IDreamOverlayClient> consumer : mConsumers) {
+            consumer.accept(mClient);
+        }
+    }
+
+    private void onAddConsumer(Consumer<IDreamOverlayClient> consumer) {
+        if (mClient != null) {
+            consumer.accept(mClient);
+        }
+        mConsumers.add(consumer);
+    }
+
+    private void onRemoveConsumer(Consumer<IDreamOverlayClient> consumer) {
+        mConsumers.remove(consumer);
+    }
+
+    private final class OverlayConnectionCallback implements
+            ObservableServiceConnection.Callback<IDreamOverlay> {
+
+        private final IDreamOverlayClientCallback mClientCallback =
+                new IDreamOverlayClientCallback.Stub() {
+                    @Override
+                    public void onDreamOverlayClient(IDreamOverlayClient client) {
+                        final Message msg =
+                                mHandler.obtainMessage(MSG_OVERLAY_CLIENT_READY, client);
+                        mHandler.sendMessage(msg);
+                    }
+                };
+
+        @Override
+        public void onConnected(
+                ObservableServiceConnection<IDreamOverlay> connection,
+                IDreamOverlay service) {
+            try {
+                service.getClient(mClientCallback);
+            } catch (RemoteException e) {
+                Log.e(TAG, "could not get DreamOverlayClient", e);
+            }
+        }
+
+        @Override
+        public void onDisconnected(ObservableServiceConnection<IDreamOverlay> connection,
+                int reason) {
+            mClient = null;
+            // Cancel any pending messages about the overlay being ready, since it is no
+            // longer ready.
+            mHandler.removeMessages(MSG_OVERLAY_CLIENT_READY);
+        }
+    }
+
+    /**
+     * Injector for testing
+     */
+    @VisibleForTesting
+    public static class Injector {
+        /**
+         * Returns milliseconds since boot, not counting time spent in deep sleep. Can be overridden
+         * in tests with a fake clock.
+         */
+        public PersistentServiceConnection<IDreamOverlay> buildConnection(
+                Context context,
+                Handler handler,
+                Intent serviceIntent,
+                int minConnectionDurationMs,
+                int maxReconnectAttempts,
+                int baseReconnectDelayMs) {
+            final Executor executor = handler::post;
+            final int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE;
+            return new PersistentServiceConnection<>(
+                    context,
+                    executor,
+                    handler,
+                    IDreamOverlay.Stub::asInterface,
+                    serviceIntent,
+                    flags,
+                    minConnectionDurationMs,
+                    maxReconnectAttempts,
+                    baseReconnectDelayMs
+            );
+        }
+    }
+}
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index ce8af83..3a32352 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -68,8 +68,6 @@
 
 import com.android.internal.R;
 import com.android.internal.util.DumpUtils;
-import com.android.internal.util.ObservableServiceConnection;
-import com.android.internal.util.PersistentServiceConnection;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -77,8 +75,6 @@
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
 /**
@@ -234,7 +230,6 @@
     private boolean mCanDoze;
     private boolean mDozing;
     private boolean mWindowless;
-    private boolean mOverlayFinishing;
     private int mDozeScreenState = Display.STATE_UNKNOWN;
     private int mDozeScreenBrightness = PowerManager.BRIGHTNESS_DEFAULT;
 
@@ -246,88 +241,7 @@
     private DreamServiceWrapper mDreamServiceWrapper;
     private Runnable mDispatchAfterOnAttachedToWindow;
 
-    private OverlayConnection mOverlayConnection;
-
-    private static class OverlayConnection extends PersistentServiceConnection<IDreamOverlay> {
-        // Retrieved Client
-        private IDreamOverlayClient mClient;
-
-        // A list of pending requests to execute on the overlay.
-        private final ArrayList<Consumer<IDreamOverlayClient>> mConsumers = new ArrayList<>();
-
-        private final IDreamOverlayClientCallback mClientCallback =
-                new IDreamOverlayClientCallback.Stub() {
-            @Override
-            public void onDreamOverlayClient(IDreamOverlayClient client) {
-                mClient = client;
-
-                for (Consumer<IDreamOverlayClient> consumer : mConsumers) {
-                    consumer.accept(mClient);
-                }
-            }
-        };
-
-        private final Callback<IDreamOverlay> mCallback = new Callback<IDreamOverlay>() {
-            @Override
-            public void onConnected(ObservableServiceConnection<IDreamOverlay> connection,
-                    IDreamOverlay service) {
-                try {
-                    service.getClient(mClientCallback);
-                } catch (RemoteException e) {
-                    Log.e(TAG, "could not get DreamOverlayClient", e);
-                }
-            }
-
-            @Override
-            public void onDisconnected(ObservableServiceConnection<IDreamOverlay> connection,
-                    int reason) {
-                mClient = null;
-            }
-        };
-
-        OverlayConnection(Context context,
-                Executor executor,
-                Handler handler,
-                ServiceTransformer<IDreamOverlay> transformer,
-                Intent serviceIntent,
-                int flags,
-                int minConnectionDurationMs,
-                int maxReconnectAttempts,
-                int baseReconnectDelayMs) {
-            super(context, executor, handler, transformer, serviceIntent, flags,
-                    minConnectionDurationMs,
-                    maxReconnectAttempts, baseReconnectDelayMs);
-        }
-
-        @Override
-        public boolean bind() {
-            addCallback(mCallback);
-            return super.bind();
-        }
-
-        @Override
-        public void unbind() {
-            removeCallback(mCallback);
-            super.unbind();
-        }
-
-        public void addConsumer(Consumer<IDreamOverlayClient> consumer) {
-            execute(() -> {
-                mConsumers.add(consumer);
-                if (mClient != null) {
-                    consumer.accept(mClient);
-                }
-            });
-        }
-
-        public void removeConsumer(Consumer<IDreamOverlayClient> consumer) {
-            execute(() -> mConsumers.remove(consumer));
-        }
-
-        public void clearConsumers() {
-            execute(() -> mConsumers.clear());
-        }
-    }
+    private DreamOverlayConnectionHandler mOverlayConnection;
 
     private final IDreamOverlayCallback mOverlayCallback = new IDreamOverlayCallback.Stub() {
         @Override
@@ -1030,18 +944,18 @@
             final Resources resources = getResources();
             final Intent overlayIntent = new Intent().setComponent(overlayComponent);
 
-            mOverlayConnection = new OverlayConnection(
+            mOverlayConnection = new DreamOverlayConnectionHandler(
                     /* context= */ this,
-                    getMainExecutor(),
-                    mHandler,
-                    IDreamOverlay.Stub::asInterface,
+                    Looper.getMainLooper(),
                     overlayIntent,
-                    /* flags= */ Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
                     resources.getInteger(R.integer.config_minDreamOverlayDurationMs),
                     resources.getInteger(R.integer.config_dreamOverlayMaxReconnectAttempts),
                     resources.getInteger(R.integer.config_dreamOverlayReconnectTimeoutMs));
 
-            mOverlayConnection.bind();
+            if (!mOverlayConnection.bind()) {
+                // Binding failed.
+                mOverlayConnection = null;
+            }
         }
 
         return mDreamServiceWrapper;
@@ -1069,9 +983,7 @@
         // If there is an active overlay connection, signal that the dream is ending before
         // continuing. Note that the overlay cannot rely on the unbound state, since another dream
         // might have bound to it in the meantime.
-        if (mOverlayConnection != null && !mOverlayFinishing) {
-            // Set mOverlayFinish to true to only allow this consumer to be added once.
-            mOverlayFinishing = true;
+        if (mOverlayConnection != null) {
             mOverlayConnection.addConsumer(overlay -> {
                 try {
                     overlay.endDream();
@@ -1082,7 +994,6 @@
                     Log.e(mTag, "could not inform overlay of dream end:" + e);
                 }
             });
-            mOverlayConnection.clearConsumers();
             return;
         }
 
@@ -1362,6 +1273,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/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index e55e2e5..1d49049 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -77,6 +77,7 @@
  * <pre>
  * &lt;service android:name=".NotificationListener"
  *          android:label="&#64;string/service_name"
+ *          android:exported="false"
  *          android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
  *     &lt;intent-filter>
  *         &lt;action android:name="android.service.notification.NotificationListenerService" />
@@ -1420,7 +1421,7 @@
         if (getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.P) {
             ArrayList<Person> people = notification.extras.getParcelableArrayList(
                     Notification.EXTRA_PEOPLE_LIST, android.app.Person.class);
-            if (people != null && people.isEmpty()) {
+            if (people != null && !people.isEmpty()) {
                 int size = people.size();
                 String[] peopleArray = new String[size];
                 for (int i = 0; i < size; i++) {
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/HotwordDetectedResult.java b/core/java/android/service/voice/HotwordDetectedResult.java
index dd3f99c..ab6f055 100644
--- a/core/java/android/service/voice/HotwordDetectedResult.java
+++ b/core/java/android/service/voice/HotwordDetectedResult.java
@@ -94,6 +94,9 @@
     /** Represents unset value for the triggered audio channel. */
     public static final int AUDIO_CHANNEL_UNSET = -1;
 
+    /** Represents unset value for the background audio signal power. */
+    public static final int BACKGROUND_AUDIO_POWER_UNSET = -1;
+
     /** Limits the max value for the hotword offset. */
     private static final int LIMIT_HOTWORD_OFFSET_MAX_VALUE = 60 * 60 * 1000; // 1 hour
 
@@ -296,6 +299,24 @@
             new DetectedPhrase.Builder().build();
 
     /**
+     * Power of the background audio signal in which the hotword phrase was detected.
+     *
+     * <p> Only values between 0 and {@link #getMaxBackgroundAudioPower} (inclusive)
+     * and the special value {@link #BACKGROUND_AUDIO_POWER_UNSET} are valid.
+     */
+    private final int mBackgroundAudioPower;
+    private static int defaultBackgroundAudioPower() {
+        return BACKGROUND_AUDIO_POWER_UNSET;
+    }
+
+    /**
+     * Returns the maximum value of {@link #getBackgroundAudioPower()}.
+     */
+    public static int getMaxBackgroundAudioPower() {
+        return 255;
+    }
+
+    /**
      * Returns how many bytes should be written into the Parcel
      *
      * @hide
@@ -346,6 +367,10 @@
         if (!persistableBundle.isEmpty()) {
             totalBits += getParcelableSize(persistableBundle) * Byte.SIZE;
         }
+        if (hotwordDetectedResult.getBackgroundAudioPower() != defaultBackgroundAudioPower()) {
+            totalBits += bitCount(HotwordDetectedResult.getMaxBackgroundAudioPower());
+        }
+
         return totalBits;
     }
 
@@ -362,6 +387,10 @@
         Preconditions.checkArgumentInRange(mScore, 0, getMaxScore(), "score");
         Preconditions.checkArgumentInRange(mPersonalizedScore, 0, getMaxScore(),
                 "personalizedScore");
+        if (mBackgroundAudioPower != BACKGROUND_AUDIO_POWER_UNSET) {
+            Preconditions.checkArgumentInRange(mBackgroundAudioPower,
+                    0, getMaxBackgroundAudioPower(), "backgroundAudioPower");
+        }
         Preconditions.checkArgumentInRange((long) mHotwordDurationMillis, 0,
                 AudioRecord.getMaxSharedAudioHistoryMillis(), "hotwordDurationMillis");
         if (mHotwordOffsetMillis != HOTWORD_OFFSET_UNSET) {
@@ -493,7 +522,8 @@
             .setPersonalizedScore(mPersonalizedScore)
             .setAudioStreams(mAudioStreams)
             .setExtras(mExtras)
-            .setDetectedPhrase(mDetectedPhrase);
+            .setDetectedPhrase(mDetectedPhrase)
+            .setBackgroundAudioPower(mBackgroundAudioPower);
     }
 
 
@@ -604,7 +634,8 @@
             int personalizedScore,
             @NonNull List<HotwordAudioStream> audioStreams,
             @NonNull PersistableBundle extras,
-            @NonNull DetectedPhrase detectedPhrase) {
+            @NonNull DetectedPhrase detectedPhrase,
+            int backgroundAudioPower) {
         this.mConfidenceLevel = confidenceLevel;
         com.android.internal.util.AnnotationValidations.validate(
                 HotwordConfidenceLevelValue.class, null, mConfidenceLevel);
@@ -624,6 +655,7 @@
         this.mDetectedPhrase = detectedPhrase;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mDetectedPhrase);
+        this.mBackgroundAudioPower = backgroundAudioPower;
 
         onConstructed();
     }
@@ -732,6 +764,17 @@
         return mDetectedPhrase;
     }
 
+    /**
+     * Power of the background audio signal in which the hotword phrase was detected.
+     *
+     * <p> Only values between 0 and {@link #getMaxBackgroundAudioPower} (inclusive)
+     * and the special value {@link #BACKGROUND_AUDIO_POWER_UNSET} are valid.
+     */
+    @DataClass.Generated.Member
+    public int getBackgroundAudioPower() {
+        return mBackgroundAudioPower;
+    }
+
     @Override
     @DataClass.Generated.Member
     public String toString() {
@@ -749,7 +792,8 @@
                 "personalizedScore = " + mPersonalizedScore + ", " +
                 "audioStreams = " + mAudioStreams + ", " +
                 "extras = " + mExtras + ", " +
-                "detectedPhrase = " + mDetectedPhrase +
+                "detectedPhrase = " + mDetectedPhrase + ", " +
+                "backgroundAudioPower = " + mBackgroundAudioPower +
         " }";
     }
 
@@ -776,7 +820,8 @@
                 && mPersonalizedScore == that.mPersonalizedScore
                 && Objects.equals(mAudioStreams, that.mAudioStreams)
                 && Objects.equals(mExtras, that.mExtras)
-                && Objects.equals(mDetectedPhrase, that.mDetectedPhrase);
+                && Objects.equals(mDetectedPhrase, that.mDetectedPhrase)
+                && mBackgroundAudioPower == that.mBackgroundAudioPower;
     }
 
     @Override
@@ -797,6 +842,7 @@
         _hash = 31 * _hash + Objects.hashCode(mAudioStreams);
         _hash = 31 * _hash + Objects.hashCode(mExtras);
         _hash = 31 * _hash + Objects.hashCode(mDetectedPhrase);
+        _hash = 31 * _hash + mBackgroundAudioPower;
         return _hash;
     }
 
@@ -820,6 +866,7 @@
         dest.writeParcelableList(mAudioStreams, flags);
         dest.writeTypedObject(mExtras, flags);
         dest.writeTypedObject(mDetectedPhrase, flags);
+        dest.writeInt(mBackgroundAudioPower);
     }
 
     @Override
@@ -846,6 +893,7 @@
         in.readParcelableList(audioStreams, HotwordAudioStream.class.getClassLoader());
         PersistableBundle extras = (PersistableBundle) in.readTypedObject(PersistableBundle.CREATOR);
         DetectedPhrase detectedPhrase = (DetectedPhrase) in.readTypedObject(DetectedPhrase.CREATOR);
+        int backgroundAudioPower = in.readInt();
 
         this.mConfidenceLevel = confidenceLevel;
         com.android.internal.util.AnnotationValidations.validate(
@@ -866,6 +914,7 @@
         this.mDetectedPhrase = detectedPhrase;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mDetectedPhrase);
+        this.mBackgroundAudioPower = backgroundAudioPower;
 
         onConstructed();
     }
@@ -902,6 +951,7 @@
         private @NonNull List<HotwordAudioStream> mAudioStreams;
         private @NonNull PersistableBundle mExtras;
         private @NonNull DetectedPhrase mDetectedPhrase;
+        private int mBackgroundAudioPower;
 
         private long mBuilderFieldsSet = 0L;
 
@@ -1052,10 +1102,24 @@
             return this;
         }
 
+        /**
+         * Power of the background audio signal in which the hotword phrase was detected.
+         *
+         * <p> Only values between 0 and {@link #getMaxBackgroundAudioPower} (inclusive)
+         * and the special value {@link #BACKGROUND_AUDIO_POWER_UNSET} are valid.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setBackgroundAudioPower(int value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x800;
+            mBackgroundAudioPower = value;
+            return this;
+        }
+
         /** Builds the instance. This builder should not be touched after calling this! */
         public @NonNull HotwordDetectedResult build() {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x800; // Mark builder used
+            mBuilderFieldsSet |= 0x1000; // Mark builder used
 
             if ((mBuilderFieldsSet & 0x1) == 0) {
                 mConfidenceLevel = defaultConfidenceLevel();
@@ -1090,6 +1154,9 @@
             if ((mBuilderFieldsSet & 0x400) == 0) {
                 mDetectedPhrase = new DetectedPhrase.Builder().build();
             }
+            if ((mBuilderFieldsSet & 0x800) == 0) {
+                mBackgroundAudioPower = defaultBackgroundAudioPower();
+            }
             HotwordDetectedResult o = new HotwordDetectedResult(
                     mConfidenceLevel,
                     mMediaSyncEvent,
@@ -1101,12 +1168,13 @@
                     mPersonalizedScore,
                     mAudioStreams,
                     mExtras,
-                    mDetectedPhrase);
+                    mDetectedPhrase,
+                    mBackgroundAudioPower);
             return o;
         }
 
         private void checkNotUsed() {
-            if ((mBuilderFieldsSet & 0x800) != 0) {
+            if ((mBuilderFieldsSet & 0x1000) != 0) {
                 throw new IllegalStateException(
                         "This Builder should not be reused. Use a new Builder instance instead");
             }
@@ -1114,10 +1182,10 @@
     }
 
     @DataClass.Generated(
-            time = 1676870324215L,
+            time = 1679010159293L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/service/voice/HotwordDetectedResult.java",
-            inputSignatures = "public static final  int CONFIDENCE_LEVEL_NONE\npublic static final  int CONFIDENCE_LEVEL_LOW\npublic static final  int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final  int CONFIDENCE_LEVEL_HIGH\npublic static final  int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final  int HOTWORD_OFFSET_UNSET\npublic static final  int AUDIO_CHANNEL_UNSET\nprivate static final  int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final  int LIMIT_AUDIO_CHANNEL_MAX_VALUE\nprivate static final  java.lang.String EXTRA_PROXIMITY\npublic static final  int PROXIMITY_UNKNOWN\npublic static final  int PROXIMITY_NEAR\npublic static final  int PROXIMITY_FAR\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate  int mHotwordOffsetMillis\nprivate  int mHotwordDurationMillis\nprivate  int mAudioChannel\nprivate  boolean mHotwordDetectionPersonalized\nprivate final  int mScore\nprivate final  int mPersonalizedScore\nprivate final @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> mAudioStreams\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static  int sMaxBundleSize\nprivate @android.annotation.NonNull android.service.voice.DetectedPhrase mDetectedPhrase\nprivate static  int defaultConfidenceLevel()\nprivate static  int defaultScore()\nprivate static  int defaultPersonalizedScore()\npublic static  int getMaxScore()\npublic @java.lang.Deprecated int getHotwordPhraseId()\npublic static @java.lang.Deprecated int getMaxHotwordPhraseId()\nprivate static  java.util.List<android.service.voice.HotwordAudioStream> defaultAudioStreams()\nprivate static  android.os.PersistableBundle defaultExtras()\npublic static  int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\npublic static  int getParcelableSize(android.os.Parcelable)\npublic static  int getUsageSize(android.service.voice.HotwordDetectedResult)\nstatic  int bitCount(long)\nprivate  void onConstructed()\npublic @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> getAudioStreams()\npublic  void setProximity(double)\npublic @android.service.voice.HotwordDetectedResult.ProximityValue int getProximity()\nprivate @android.service.voice.HotwordDetectedResult.ProximityValue int convertToProximityLevel(double)\npublic  android.service.voice.HotwordDetectedResult.Builder buildUpon()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\npublic @java.lang.Deprecated @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setHotwordPhraseId(int)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\npublic @java.lang.Deprecated @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setHotwordPhraseId(int)\nclass BaseBuilder extends java.lang.Object implements []")
+            inputSignatures = "public static final  int CONFIDENCE_LEVEL_NONE\npublic static final  int CONFIDENCE_LEVEL_LOW\npublic static final  int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final  int CONFIDENCE_LEVEL_HIGH\npublic static final  int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final  int HOTWORD_OFFSET_UNSET\npublic static final  int AUDIO_CHANNEL_UNSET\npublic static final  int BACKGROUND_AUDIO_POWER_UNSET\nprivate static final  int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final  int LIMIT_AUDIO_CHANNEL_MAX_VALUE\nprivate static final  java.lang.String EXTRA_PROXIMITY\npublic static final  int PROXIMITY_UNKNOWN\npublic static final  int PROXIMITY_NEAR\npublic static final  int PROXIMITY_FAR\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate  int mHotwordOffsetMillis\nprivate  int mHotwordDurationMillis\nprivate  int mAudioChannel\nprivate  boolean mHotwordDetectionPersonalized\nprivate final  int mScore\nprivate final  int mPersonalizedScore\nprivate final @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> mAudioStreams\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static  int sMaxBundleSize\nprivate @android.annotation.NonNull android.service.voice.DetectedPhrase mDetectedPhrase\nprivate final  int mBackgroundAudioPower\nprivate static  int defaultConfidenceLevel()\nprivate static  int defaultScore()\nprivate static  int defaultPersonalizedScore()\npublic static  int getMaxScore()\npublic @java.lang.Deprecated int getHotwordPhraseId()\npublic static @java.lang.Deprecated int getMaxHotwordPhraseId()\nprivate static  java.util.List<android.service.voice.HotwordAudioStream> defaultAudioStreams()\nprivate static  android.os.PersistableBundle defaultExtras()\npublic static  int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\nprivate static  int defaultBackgroundAudioPower()\npublic static  int getMaxBackgroundAudioPower()\npublic static  int getParcelableSize(android.os.Parcelable)\npublic static  int getUsageSize(android.service.voice.HotwordDetectedResult)\nstatic  int bitCount(long)\nprivate  void onConstructed()\npublic @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> getAudioStreams()\npublic  void setProximity(double)\npublic @android.service.voice.HotwordDetectedResult.ProximityValue int getProximity()\nprivate @android.service.voice.HotwordDetectedResult.ProximityValue int convertToProximityLevel(double)\npublic  android.service.voice.HotwordDetectedResult.Builder buildUpon()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\npublic @java.lang.Deprecated @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setHotwordPhraseId(int)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\npublic @java.lang.Deprecated @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setHotwordPhraseId(int)\nclass BaseBuilder extends java.lang.Object implements []")
     @Deprecated
     private void __metadata() {}
 
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/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 259012f..8d95c02 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -1458,7 +1458,7 @@
                     com.android.internal.R.dimen.config_wallpaperDimAmount);
             mWallpaperDimAmount = mDefaultDimAmount;
             mPreviousWallpaperDimAmount = mWallpaperDimAmount;
-            mDisplayState = mDisplay.getState();
+            mDisplayState = mDisplay.getCommittedState();
 
             if (DEBUG) Log.v(TAG, "onCreate(): " + this);
             Trace.beginSection("WPMS.Engine.onCreate");
@@ -1548,7 +1548,8 @@
                 return;
             }
             if (!mDestroyed) {
-                mDisplayState = mDisplay == null ? Display.STATE_UNKNOWN : mDisplay.getState();
+                mDisplayState = mDisplay == null ? Display.STATE_UNKNOWN :
+                        mDisplay.getCommittedState();
                 boolean visible = mVisible && mDisplayState != Display.STATE_OFF;
                 if (DEBUG) {
                     Log.v(
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/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/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 5810642..83de2a0 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -110,16 +110,6 @@
             int requestedWidth, int requestedHeight, int viewVisibility, int flags, int seq,
             int lastSyncSeqId);
 
-    /*
-     * Notify the window manager that an application is relaunching and
-     * windows should be prepared for replacement.
-     *
-     * @param appToken The application
-     * @param childrenOnly Whether to only prepare child windows for replacement
-     * (for example when main windows are being reused via preservation).
-     */
-    oneway void prepareToReplaceWindows(IBinder appToken, boolean childrenOnly);
-
     /**
      * Called by a client to report that it ran out of graphics memory.
      */
@@ -304,7 +294,7 @@
     * an input channel where the client can receive input.
     */
     void grantInputChannel(int displayId, in SurfaceControl surface, in IWindow window,
-            in IBinder hostInputToken, int flags, int privateFlags, int type,
+            in IBinder hostInputToken, int flags, int privateFlags, int inputFeatures, int type,
             in IBinder windowToken, in IBinder focusGrantToken, String inputHandleName,
             out InputChannel outInputChannel);
 
@@ -312,7 +302,8 @@
      * Update the flags on an input channel associated with a particular surface.
      */
     oneway void updateInputChannel(in IBinder channelToken, int displayId,
-            in SurfaceControl surface, int flags, int privateFlags, in Region region);
+            in SurfaceControl surface, int flags, int privateFlags, int inputFeatures,
+            in Region region);
 
     /**
      * Transfer window focus to an embedded window if the calling window has focus.
diff --git a/core/java/android/view/InputEvent.java b/core/java/android/view/InputEvent.java
index cb9e746..0b4adae 100644
--- a/core/java/android/view/InputEvent.java
+++ b/core/java/android/view/InputEvent.java
@@ -207,7 +207,7 @@
      *
      * @hide
      */
-    public abstract long getEventTimeNano();
+    public abstract long getEventTimeNanos();
 
     /**
      * Marks the input event as being canceled.
diff --git a/core/java/android/view/InputEventConsistencyVerifier.java b/core/java/android/view/InputEventConsistencyVerifier.java
index c0a3cec..5c38a15 100644
--- a/core/java/android/view/InputEventConsistencyVerifier.java
+++ b/core/java/android/view/InputEventConsistencyVerifier.java
@@ -721,7 +721,7 @@
 
     private static void appendEvent(StringBuilder message, int index,
             InputEvent event, boolean unhandled) {
-        message.append(index).append(": sent at ").append(event.getEventTimeNano());
+        message.append(index).append(": sent at ").append(event.getEventTimeNanos());
         message.append(", ");
         if (unhandled) {
             message.append("(unhandled) ");
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index ab81345..2af0254 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -2711,7 +2711,7 @@
      * @hide
      */
     @Override
-    public final long getEventTimeNano() {
+    public final long getEventTimeNanos() {
         return mEventTime;
     }
 
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index a71ab8a..3902989 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -2440,13 +2440,31 @@
      *
      * @hide
      */
-    @Override
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(publicAlternatives =
+            "Use {@link #getEventTimeNanos()} public API instead.",
+            maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
     public final long getEventTimeNano() {
         return nativeGetEventTimeNanos(mNativePtr, HISTORY_CURRENT);
     }
 
     /**
+     * Retrieve the time this event occurred,
+     * in the {@link android.os.SystemClock#uptimeMillis} time base but with
+     * nanosecond precision.
+     * <p>
+     * The value is in nanosecond precision but it may not have nanosecond accuracy.
+     * </p>
+     *
+     * @return Returns the time this event occurred,
+     * in the {@link android.os.SystemClock#uptimeMillis} time base but with
+     * nanosecond precision.
+     */
+    @Override
+    public long getEventTimeNanos() {
+        return nativeGetEventTimeNanos(mNativePtr, HISTORY_CURRENT);
+    }
+
+    /**
      * Equivalent to {@link #getX(int)} for pointer index 0 (regardless of the
      * pointer identifier).
      *
@@ -3104,10 +3122,8 @@
      *
      * @see #getHistorySize
      * @see #getEventTime
-     *
-     * @hide
      */
-    public final long getHistoricalEventTimeNano(int pos) {
+    public long getHistoricalEventTimeNanos(int pos) {
         return nativeGetEventTimeNanos(mNativePtr, pos);
     }
 
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 24dcb69..d1f9fbd 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -417,7 +417,8 @@
     private boolean mUseBLASTAdapter;
     private boolean mForceDisableBLAST;
 
-    private boolean mFastScrollSoundEffectsEnabled;
+    /** lazily-initialized in getAudioManager() */
+    private boolean mFastScrollSoundEffectsEnabled = false;
 
     /**
      * Signals that compatibility booleans have been initialized according to
@@ -1028,8 +1029,6 @@
 
         loadSystemProperties();
         mImeFocusController = new ImeFocusController(this);
-        AudioManager audioManager = mContext.getSystemService(AudioManager.class);
-        mFastScrollSoundEffectsEnabled = audioManager.areNavigationRepeatSoundEffectsEnabled();
 
         mScrollCaptureRequestTimeout = SCROLL_CAPTURE_REQUEST_TIMEOUT_MILLIS;
         mOnBackInvokedDispatcher = new WindowOnBackInvokedDispatcher(context);
@@ -8340,6 +8339,7 @@
         }
         if (mAudioManager == null) {
             mAudioManager = (AudioManager) mView.getContext().getSystemService(Context.AUDIO_SERVICE);
+            mFastScrollSoundEffectsEnabled = mAudioManager.areNavigationRepeatSoundEffectsEnabled();
         }
         return mAudioManager;
     }
@@ -9171,7 +9171,7 @@
      * Represents a pending input event that is waiting in a queue.
      *
      * Input events are processed in serial order by the timestamp specified by
-     * {@link InputEvent#getEventTimeNano()}.  In general, the input dispatcher delivers
+     * {@link InputEvent#getEventTimeNanos()}.  In general, the input dispatcher delivers
      * one input event to the application at a time and waits for the application
      * to finish handling it before delivering the next one.
      *
@@ -9361,7 +9361,7 @@
         if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
             Trace.traceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent src=0x"
                     + Integer.toHexString(q.mEvent.getSource()) + " eventTimeNano="
-                    + q.mEvent.getEventTimeNano() + " id=0x"
+                    + q.mEvent.getEventTimeNanos() + " id=0x"
                     + Integer.toHexString(q.mEvent.getId()));
         }
         try {
@@ -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/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 35ed88f..cda1f3a 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -863,10 +863,8 @@
      *     android:value="true|false"/&gt;
      * &lt;/application&gt;
      * </pre>
-     *
-     * @hide
      */
-    // TODO(b/263984287): Make this public API.
+    // TODO(b/263984287): Add CTS tests.
     String PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION =
             "android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION";
 
@@ -899,8 +897,6 @@
      *     android:value="false"/&gt;
      * &lt;/application&gt;
      * </pre>
-     *
-     * @hide
      */
     // TODO(b/263984287): Make this public API.
     String PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS =
@@ -937,10 +933,8 @@
      *     android:value="true|false"/&gt;
      * &lt;/application&gt;
      * </pre>
-     *
-     * @hide
      */
-    // TODO(b/263984287): Make this public API.
+    // TODO(b/263984287): Add CTS tests.
     String PROPERTY_COMPAT_ENABLE_FAKE_FOCUS = "android.window.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS";
 
     /**
@@ -976,10 +970,8 @@
      *     android:value="true|false"/&gt;
      * &lt;/application&gt;
      * </pre>
-     *
-     * @hide
      */
-    // TODO(b/263984287): Make this public API.
+    // TODO(b/263984287): Add CTS tests.
     String PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION =
             "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION";
 
@@ -1023,10 +1015,8 @@
      *     android:value="true|false"/&gt;
      * &lt;/application&gt;
      * </pre>
-     *
-     * @hide
      */
-    // TODO(b/263984287): Make this public API.
+    // TODO(b/263984287): Add CTS tests.
     String PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH =
             "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH";
 
@@ -1073,17 +1063,28 @@
      *     android:value="true|false"/&gt;
      * &lt;/application&gt;
      * </pre>
-     *
-     * @hide
      */
-    // TODO(b/263984287): Make this public API.
+    // TODO(b/263984287): Add CTS tests.
     String PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE =
             "android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE";
 
     /**
      * Application level {@link android.content.pm.PackageManager.Property PackageManager
      * .Property} for an app to inform the system that the app should be excluded from the
-     * compatibility override for orientation set by the device manufacturer.
+     * compatibility override for orientation set by the device manufacturer. When the orientation
+     * override is applied it can:
+     * <ul>
+     *   <li>Replace the specific orientation requested by the app with another selected by the
+             device manufacturer, e.g. replace undefined requested by the app with portrait.
+     *   <li>Always use an orientation selected by the device manufacturer.
+     *   <li>Do one of the above but only when camera connection is open.
+     * </ul>
+     *
+     * <p>This property is different from {@link PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION}
+     * (which is used to avoid orientation loops caused by the incorrect use of {@link
+     * android.app.Activity#setRequestedOrientation}) because this property overrides the app to an
+     * orientation selected by the device manufacturer rather than ignoring one of orientation
+     * requests coming from the app while respecting the previous one.
      *
      * <p>With this property set to {@code true} or unset, device manufacturers can override
      * orientation for the app using their discretion to improve display compatibility.
@@ -1099,10 +1100,8 @@
      *     android:value="true|false"/&gt;
      * &lt;/application&gt;
      * </pre>
-     *
-     * @hide
      */
-    // TODO(b/263984287): Make this public API.
+    // TODO(b/263984287): Add CTS tests.
     String PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE =
             "android.window.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE";
 
@@ -1144,10 +1143,8 @@
      *     android:value="true|false"/&gt;
      * &lt;/application&gt;
      * </pre>
-     *
-     * @hide
      */
-    // TODO(b/263984287): Make this public API.
+    // TODO(b/263984287): Add CTS tests.
     String PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE =
             "android.window.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE";
 
@@ -2815,7 +2812,7 @@
          *
          * @hide
          */
-        public static final int PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED = 0x00000002;
+        public static final int PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED = 1 << 1;
 
         /**
          * By default, wallpapers are sent new offsets when the wallpaper is scrolled. Wallpapers
@@ -2826,7 +2823,7 @@
          *
          * @hide
          */
-        public static final int PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS = 0x00000004;
+        public static final int PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS = 1 << 2;
 
         /**
          * When set {@link LayoutParams#TYPE_APPLICATION_OVERLAY} windows will stay visible, even if
@@ -2835,7 +2832,7 @@
          * @hide
          */
         @RequiresPermission(permission.SYSTEM_APPLICATION_OVERLAY)
-        public static final int PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY = 0x00000008;
+        public static final int PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY = 1 << 3;
 
         /** In a multiuser system if this flag is set and the owner is a system process then this
          * window will appear on all user screens. This overrides the default behavior of window
@@ -2845,7 +2842,7 @@
          * {@hide} */
         @SystemApi
         @RequiresPermission(permission.INTERNAL_SYSTEM_WINDOW)
-        public static final int SYSTEM_FLAG_SHOW_FOR_ALL_USERS = 0x00000010;
+        public static final int SYSTEM_FLAG_SHOW_FOR_ALL_USERS = 1 << 4;
 
         /**
          * Flag to allow this window to have unrestricted gesture exclusion.
@@ -2853,7 +2850,7 @@
          * @see View#setSystemGestureExclusionRects(List)
          * @hide
          */
-        public static final int PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION = 0x00000020;
+        public static final int PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION = 1 << 5;
 
         /**
          * Never animate position changes of the window.
@@ -2862,20 +2859,20 @@
          * {@hide}
          */
         @UnsupportedAppUsage
-        public static final int PRIVATE_FLAG_NO_MOVE_ANIMATION = 0x00000040;
+        public static final int PRIVATE_FLAG_NO_MOVE_ANIMATION = 1 << 6;
 
         /** Window flag: special flag to limit the size of the window to be
          * original size ([320x480] x density). Used to create window for applications
          * running under compatibility mode.
          *
          * {@hide} */
-        public static final int PRIVATE_FLAG_COMPATIBLE_WINDOW = 0x00000080;
+        public static final int PRIVATE_FLAG_COMPATIBLE_WINDOW = 1 << 7;
 
         /** Window flag: a special option intended for system dialogs.  When
          * this flag is set, the window will demand focus unconditionally when
          * it is created.
          * {@hide} */
-        public static final int PRIVATE_FLAG_SYSTEM_ERROR = 0x00000100;
+        public static final int PRIVATE_FLAG_SYSTEM_ERROR = 1 << 8;
 
         /**
          * Flag to indicate that the view hierarchy of the window can only be measured when
@@ -2884,14 +2881,14 @@
          * views. This reduces the chances to perform measure.
          * {@hide}
          */
-        public static final int PRIVATE_FLAG_OPTIMIZE_MEASURE = 0x00000200;
+        public static final int PRIVATE_FLAG_OPTIMIZE_MEASURE = 1 << 9;
 
         /**
          * Flag that prevents the wallpaper behind the current window from receiving touch events.
          *
          * {@hide}
          */
-        public static final int PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS = 0x00000800;
+        public static final int PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS = 1 << 10;
 
         /**
          * Flag to force the status bar window to be visible all the time. If the bar is hidden when
@@ -2900,7 +2897,7 @@
          *
          * {@hide}
          */
-        public static final int PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR = 0x00001000;
+        public static final int PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR = 1 << 11;
 
         /**
          * Flag to indicate that the window frame should be the requested frame adding the display
@@ -2910,7 +2907,7 @@
          *
          * {@hide}
          */
-        public static final int PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT = 0x00002000;
+        public static final int PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT = 1 << 12;
 
         /**
          * Flag that will make window ignore app visibility and instead depend purely on the decor
@@ -2918,39 +2915,28 @@
          * drawing after it launches an app.
          * @hide
          */
-        public static final int PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY = 0x00004000;
-
-        /**
-         * Flag to indicate that this window is not expected to be replaced across
-         * configuration change triggered activity relaunches. In general the WindowManager
-         * expects Windows to be replaced after relaunch, and thus it will preserve their surfaces
-         * until the replacement is ready to show in order to prevent visual glitch. However
-         * some windows, such as PopupWindows expect to be cleared across configuration change,
-         * and thus should hint to the WindowManager that it should not wait for a replacement.
-         * @hide
-         */
-        public static final int PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH = 0x00008000;
+        public static final int PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY = 1 << 13;
 
         /**
          * Flag to indicate that this child window should always be laid-out in the parent
          * frame regardless of the current windowing mode configuration.
          * @hide
          */
-        public static final int PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME = 0x00010000;
+        public static final int PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME = 1 << 14;
 
         /**
          * Flag to indicate that this window is always drawing the status bar background, no matter
          * what the other flags are.
          * @hide
          */
-        public static final int PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS = 0x00020000;
+        public static final int PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS = 1 << 15;
 
         /**
          * Flag to indicate that this window needs Sustained Performance Mode if
          * the device supports it.
          * @hide
          */
-        public static final int PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE = 0x00040000;
+        public static final int PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE = 1 << 16;
 
         /**
          * Flag to indicate that any window added by an application process that is of type
@@ -2961,7 +2947,7 @@
          */
         @SystemApi
         @RequiresPermission(permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS)
-        public static final int SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS = 0x00080000;
+        public static final int SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS = 1 << 19;
 
         /**
          * Indicates that this window is the rounded corners overlay present on some
@@ -2969,7 +2955,7 @@
          * screen magnification, and mirroring.
          * @hide
          */
-        public static final int PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY = 0x00100000;
+        public static final int PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY = 1 << 20;
 
         /**
          * Flag to indicate that this window will be excluded while computing the magnifiable region
@@ -2983,7 +2969,7 @@
          * </p><p>
          * @hide
          */
-        public static final int PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION = 0x00200000;
+        public static final int PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION = 1 << 21;
 
         /**
          * Flag to prevent the window from being magnified by the accessibility magnifier.
@@ -2991,7 +2977,7 @@
          * TODO(b/190623172): This is a temporary solution and need to find out another way instead.
          * @hide
          */
-        public static final int PRIVATE_FLAG_NOT_MAGNIFIABLE = 0x00400000;
+        public static final int PRIVATE_FLAG_NOT_MAGNIFIABLE = 1 << 22;
 
         /**
          * Flag to indicate that the status bar window is in a state such that it forces showing
@@ -3000,54 +2986,54 @@
          * It only takes effects if this is set by {@link LayoutParams#TYPE_STATUS_BAR}.
          * @hide
          */
-        public static final int PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION = 0x00800000;
+        public static final int PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION = 1 << 23;
 
         /**
          * Flag to indicate that the window is color space agnostic, and the color can be
          * interpreted to any color space.
          * @hide
          */
-        public static final int PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC = 0x01000000;
+        public static final int PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC = 1 << 24;
 
         /**
          * Flag to request creation of a BLAST (Buffer as LayerState) Layer.
          * If not specified the client will receive a BufferQueue layer.
          * @hide
          */
-        public static final int PRIVATE_FLAG_USE_BLAST = 0x02000000;
+        public static final int PRIVATE_FLAG_USE_BLAST = 1 << 25;
 
         /**
          * Flag to indicate that the window is controlling the appearance of system bars. So we
          * don't need to adjust it by reading its system UI flags for compatibility.
          * @hide
          */
-        public static final int PRIVATE_FLAG_APPEARANCE_CONTROLLED = 0x04000000;
+        public static final int PRIVATE_FLAG_APPEARANCE_CONTROLLED = 1 << 26;
 
         /**
          * Flag to indicate that the window is controlling the behavior of system bars. So we don't
          * need to adjust it by reading its window flags or system UI flags for compatibility.
          * @hide
          */
-        public static final int PRIVATE_FLAG_BEHAVIOR_CONTROLLED = 0x08000000;
+        public static final int PRIVATE_FLAG_BEHAVIOR_CONTROLLED = 1 << 27;
 
         /**
          * Flag to indicate that the window is controlling how it fits window insets on its own.
          * So we don't need to adjust its attributes for fitting window insets.
          * @hide
          */
-        public static final int PRIVATE_FLAG_FIT_INSETS_CONTROLLED = 0x10000000;
+        public static final int PRIVATE_FLAG_FIT_INSETS_CONTROLLED = 1 << 28;
 
         /**
          * Flag to indicate that the window is a trusted overlay.
          * @hide
          */
-        public static final int PRIVATE_FLAG_TRUSTED_OVERLAY = 0x20000000;
+        public static final int PRIVATE_FLAG_TRUSTED_OVERLAY = 1 << 29;
 
         /**
          * Flag to indicate that the parent frame of a window should be inset by IME.
          * @hide
          */
-        public static final int PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME = 0x40000000;
+        public static final int PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME = 1 << 30;
 
         /**
          * Flag to indicate that we want to intercept and handle global drag and drop for all users.
@@ -3062,7 +3048,7 @@
          * @hide
          */
         @RequiresPermission(permission.MANAGE_ACTIVITY_TASKS)
-        public static final int PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP = 0x80000000;
+        public static final int PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP = 1 << 31;
 
         /**
          * An internal annotation for flags that can be specified to {@link #softInputMode}.
@@ -3093,7 +3079,6 @@
                 PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR,
                 PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT,
                 PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY,
-                PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH,
                 PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME,
                 PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS,
                 PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE,
@@ -3169,10 +3154,6 @@
                         equals = PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY,
                         name = "FORCE_DECOR_VIEW_VISIBILITY"),
                 @ViewDebug.FlagToString(
-                        mask = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH,
-                        equals = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH,
-                        name = "WILL_NOT_REPLACE_ON_RELAUNCH"),
-                @ViewDebug.FlagToString(
                         mask = PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME,
                         equals = PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME,
                         name = "LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME"),
@@ -3635,9 +3616,20 @@
         /**
          * The preferred refresh rate for the window.
          * <p>
-         * This must be one of the supported refresh rates obtained for the display(s) the window
-         * is on. The selected refresh rate will be applied to the display's default mode.
+         * Before API 34, this must be one of the supported refresh rates obtained
+         * for the display(s) the window is on. The selected refresh rate will be
+         * applied to the display's default mode.
          * <p>
+         * Starting API 34, this value is not limited to the supported refresh rates
+         * obtained from the display(s) for the window: it can be any refresh rate
+         * the window intends to run at. Any refresh rate can be provided as the
+         * preferred window refresh rate. The OS will select the refresh rate that
+         * best matches the {@link #preferredRefreshRate}.
+         * <p>
+         * Setting this value is the equivalent of calling {@link Surface#setFrameRate} with (
+         *     preferred_frame_rate,
+         *     {@link Surface#FRAME_RATE_COMPATIBILITY_DEFAULT},
+         *     {@link Surface#CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS}).
          * This should be used in favor of {@link LayoutParams#preferredDisplayModeId} for
          * applications that want to specify the refresh rate, but do not want to specify a
          * preference for any other displayMode properties (e.g., resolution).
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index b157ea0..0560caf 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -139,7 +139,7 @@
                 try {
                     mRealWm.updateInputChannel(state.mInputChannelToken, state.mDisplayId,
                             state.mSurfaceControl, state.mParams.flags, state.mParams.privateFlags,
-                            state.mInputRegion);
+                            state.mParams.inputFeatures, state.mInputRegion);
                 } catch (RemoteException e) {
                     Log.e(TAG, "Failed to update surface input channel: ", e);
                 }
@@ -189,12 +189,13 @@
                     mRealWm.grantInputChannel(displayId,
                             new SurfaceControl(sc, "WindowlessWindowManager.addToDisplay"),
                             window, mHostInputToken,
-                            attrs.flags, attrs.privateFlags, attrs.type, attrs.token,
-                            mFocusGrantToken, attrs.getTitle().toString(), outInputChannel);
+                            attrs.flags, attrs.privateFlags, attrs.inputFeatures, attrs.type,
+                            attrs.token, mFocusGrantToken, attrs.getTitle().toString(),
+                            outInputChannel);
                 } else {
                     mRealWm.grantInputChannel(displayId, sc, window, mHostInputToken, attrs.flags,
-                            attrs.privateFlags, attrs.type, attrs.token, mFocusGrantToken,
-                            attrs.getTitle().toString(), outInputChannel);
+                            attrs.privateFlags, attrs.inputFeatures, attrs.type, attrs.token,
+                            mFocusGrantToken, attrs.getTitle().toString(), outInputChannel);
                 }
             } catch (RemoteException e) {
                 Log.e(TAG, "Failed to grant input to surface: ", e);
@@ -381,16 +382,19 @@
             outMergedConfiguration.setConfiguration(mConfiguration, mConfiguration);
         }
 
-        if ((attrChanges & WindowManager.LayoutParams.FLAGS_CHANGED) != 0
-                && state.mInputChannelToken != null) {
+        final int inputChangeMask = WindowManager.LayoutParams.FLAGS_CHANGED
+                | WindowManager.LayoutParams.INPUT_FEATURES_CHANGED;
+        if ((attrChanges & inputChangeMask) != 0 && state.mInputChannelToken != null) {
             try {
-                if(mRealWm instanceof IWindowSession.Stub) {
+                if (mRealWm instanceof IWindowSession.Stub) {
                     mRealWm.updateInputChannel(state.mInputChannelToken, state.mDisplayId,
                             new SurfaceControl(sc, "WindowlessWindowManager.relayout"),
-                            attrs.flags, attrs.privateFlags, state.mInputRegion);
+                            attrs.flags, attrs.privateFlags, attrs.inputFeatures,
+                            state.mInputRegion);
                 } else {
                     mRealWm.updateInputChannel(state.mInputChannelToken, state.mDisplayId, sc,
-                            attrs.flags, attrs.privateFlags, state.mInputRegion);
+                            attrs.flags, attrs.privateFlags, attrs.inputFeatures,
+                            state.mInputRegion);
                 }
             } catch (RemoteException e) {
                 Log.e(TAG, "Failed to update surface input channel: ", e);
@@ -415,10 +419,6 @@
     }
 
     @Override
-    public void prepareToReplaceWindows(android.os.IBinder appToken, boolean childrenOnly) {
-    }
-
-    @Override
     public boolean outOfMemory(android.view.IWindow window) {
         return false;
     }
@@ -564,14 +564,14 @@
 
     @Override
     public void grantInputChannel(int displayId, SurfaceControl surface, IWindow window,
-            IBinder hostInputToken, int flags, int privateFlags, int type,
+            IBinder hostInputToken, int flags, int privateFlags, int inputFeatures, int type,
             IBinder windowToken, IBinder focusGrantToken, String inputHandleName,
             InputChannel outInputChannel) {
     }
 
     @Override
     public void updateInputChannel(IBinder channelToken, int displayId, SurfaceControl surface,
-            int flags, int privateFlags, Region region) {
+            int flags, int privateFlags, int inputFeatures, Region region) {
     }
 
     @Override
diff --git a/core/java/android/widget/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/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index c1ec168..d54addb 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -19,7 +19,6 @@
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -1644,8 +1643,7 @@
             p.width = mLastWidth = mWidth;
         }
 
-        p.privateFlags = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH
-                | PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
+        p.privateFlags = PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
 
         // Used for debugging.
         p.setTitle("PopupWindow:" + Integer.toHexString(hashCode()));
diff --git a/core/java/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/widget/Toast.java b/core/java/android/widget/Toast.java
index ca57c84..fceee4e 100644
--- a/core/java/android/widget/Toast.java
+++ b/core/java/android/widget/Toast.java
@@ -189,6 +189,9 @@
 
     /**
      * Show the view for the specified duration.
+     *
+     * <p>Note that toasts being sent from the background are rate limited, so avoid sending such
+     * toasts in quick succession.
      */
     public void show() {
         if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) {
diff --git a/core/java/android/window/ITaskOrganizerController.aidl b/core/java/android/window/ITaskOrganizerController.aidl
index 0032b9c..e10f7c8 100644
--- a/core/java/android/window/ITaskOrganizerController.aidl
+++ b/core/java/android/window/ITaskOrganizerController.aidl
@@ -73,11 +73,17 @@
 
     /**
      * Controls whether ignore orientation request logic in {@link
-     * com.android.server.wm.DisplayArea} is disabled at runtime.
+     * com.android.server.wm.DisplayArea} is disabled at runtime and how to optionally map some
+     * requested orientations to others.
      *
      * @param isDisabled when {@code true}, the system always ignores the value of {@link
      *                   com.android.server.wm.DisplayArea#getIgnoreOrientationRequest} and app
      *                   requested orientation is respected.
+     * @param fromOrientations The orientations we want to map to the correspondent orientations
+     *                        in toOrientation.
+     * @param toOrientations The orientations we map to the ones in fromOrientations at the same
+     *                       index
      */
-     void setIsIgnoreOrientationRequestDisabled(boolean isDisabled);
+     void setOrientationRequestPolicy(boolean isIgnoreOrientationRequestDisabled,
+            in int[] fromOrientations, in int[] toOrientations);
 }
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/TaskConstants.java b/core/java/android/window/TaskConstants.java
index c403840..3a04198 100644
--- a/core/java/android/window/TaskConstants.java
+++ b/core/java/android/window/TaskConstants.java
@@ -80,14 +80,6 @@
     public static final int TASK_CHILD_LAYER_TASK_OVERLAY = 5 * TASK_CHILD_LAYER_REGION_SIZE;
 
     /**
-     * Legacy machanism to force an activity to the top of the task (i.e. for work profile
-     * comfirmation).
-     * @hide
-     */
-    public static final int TASK_CHILD_LAYER_TASK_OVERLAY_ACTIVITIES =
-            6 * TASK_CHILD_LAYER_REGION_SIZE;
-
-    /**
      * Z-orders of task child layers other than activities, task fragments and layers interleaved
      * with them, e.g. IME windows. [-10000, 10000) is reserved for these layers.
      * @hide
@@ -99,8 +91,7 @@
             TASK_CHILD_LAYER_LETTERBOX_EDUCATION,
             TASK_CHILD_LAYER_WINDOW_DECORATIONS,
             TASK_CHILD_LAYER_RECENTS_ANIMATION_PIP_OVERLAY,
-            TASK_CHILD_LAYER_TASK_OVERLAY,
-            TASK_CHILD_LAYER_TASK_OVERLAY_ACTIVITIES
+            TASK_CHILD_LAYER_TASK_OVERLAY
     })
     public @interface TaskChildLayer {}
 }
diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java
index d4728c1..2913faf 100644
--- a/core/java/android/window/TaskOrganizer.java
+++ b/core/java/android/window/TaskOrganizer.java
@@ -267,17 +267,24 @@
 
     /**
      * Controls whether ignore orientation request logic in {@link
-     * com.android.server.wm.DisplayArea} is disabled at runtime.
+     * com.android.server.wm.DisplayArea} is disabled at runtime and how to optionally map some
+     * requested orientation to others.
      *
-     * @param isDisabled when {@code true}, the system always ignores the value of {@link
-     *                   com.android.server.wm.DisplayArea#getIgnoreOrientationRequest} and app
-     *                   requested orientation is respected.
+     * @param isIgnoreOrientationRequestDisabled when {@code true}, the system always ignores the
+     *           value of  {@link com.android.server.wm.DisplayArea#getIgnoreOrientationRequest}
+     *           and app requested orientation is respected.
+     * @param fromOrientations The orientations we want to map to the correspondent orientations
+     *                        in toOrientation.
+     * @param toOrientations The orientations we map to the ones in fromOrientations at the same
+     *                       index
      * @hide
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
-    public void setIsIgnoreOrientationRequestDisabled(boolean isDisabled) {
+    public void setOrientationRequestPolicy(boolean isIgnoreOrientationRequestDisabled,
+            @Nullable int[] fromOrientations, @Nullable int[] toOrientations) {
         try {
-            mTaskOrganizerController.setIsIgnoreOrientationRequestDisabled(isDisabled);
+            mTaskOrganizerController.setOrientationRequestPolicy(isIgnoreOrientationRequestDisabled,
+                    fromOrientations, toOrientations);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
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/EventLogTags.logtags b/core/java/com/android/internal/jank/EventLogTags.logtags
index ad47b81..66ee131 100644
--- a/core/java/com/android/internal/jank/EventLogTags.logtags
+++ b/core/java/com/android/internal/jank/EventLogTags.logtags
@@ -3,7 +3,7 @@
 option java_package com.android.internal.jank;
 
 # Marks a request to start tracing a CUJ. Doesn't mean the request was executed.
-37001 jank_cuj_events_begin_request (CUJ Type|1|5),(Unix Time Ns|2|3),(Elapsed Time Ns|2|3),(Uptime Ns|2|3)
+37001 jank_cuj_events_begin_request (CUJ Type|1|5),(Unix Time Ns|2|3),(Elapsed Time Ns|2|3),(Uptime Ns|2|3),(Tag|3)
 # Marks a request to end tracing a CUJ. Doesn't mean the request was executed.
 37002 jank_cuj_events_end_request (CUJ Type|1|5),(Unix Time Ns|2|3),(Elapsed Time Ns|2|3),(Uptime Time Ns|2|3)
 # Marks a request to cancel tracing a CUJ. Doesn't mean the request was executed.
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 7ae63b1..6344568 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());
+                    }
+                });
     }
 
     /**
@@ -581,7 +590,7 @@
             final Configuration config = builder.build();
             postEventLogToWorkerThread((unixNanos, elapsedNanos, realtimeNanos) -> {
                 EventLogTags.writeJankCujEventsBeginRequest(
-                        config.mCujType, unixNanos, elapsedNanos, realtimeNanos);
+                        config.mCujType, unixNanos, elapsedNanos, realtimeNanos, config.mTag);
             });
             final TrackerResult result = new TrackerResult();
             final boolean success = config.getHandler().runWithScissors(
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/proto/android/server/windowmanagertransitiontrace.proto b/core/proto/android/server/windowmanagertransitiontrace.proto
index ab87384..9e53a91 100644
--- a/core/proto/android/server/windowmanagertransitiontrace.proto
+++ b/core/proto/android/server/windowmanagertransitiontrace.proto
@@ -36,8 +36,10 @@
     MAGIC_NUMBER_H = 0x45434152;  /* RACE (little-endian ASCII) */
   }
 
-  required fixed64 magic_number = 1;  /* Must be the first field, set to value in MagicNumber */
-  repeated Transition sent_transitions = 2;
+  // Must be the first field, set to value in MagicNumber
+  required fixed64 magic_number = 1;
+  // Transitions that don't have a finish time are considered aborted
+  repeated Transition finished_transitions = 2;
 
   // Additional debugging info only collected and dumped when explicitly requested to trace
   repeated TransitionState transition_states = 3;
@@ -50,7 +52,9 @@
   required uint64 finish_transaction_id = 3;
   required int64 create_time_ns = 4;
   required int64 send_time_ns = 5;
-  repeated Target targets = 6;
+  optional int64 finish_time_ns = 6; // consider aborted if not provided
+  required int32 type = 7;
+  repeated Target targets = 8;
 }
 
 message Target {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 611035e..55edc24 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -824,6 +824,9 @@
     <protected-broadcast android:name="android.companion.virtual.action.VIRTUAL_DEVICE_REMOVED" />
     <protected-broadcast android:name="com.android.internal.intent.action.FLASH_NOTIFICATION_START_PREVIEW" />
     <protected-broadcast android:name="com.android.internal.intent.action.FLASH_NOTIFICATION_STOP_PREVIEW" />
+    <protected-broadcast android:name="android.app.admin.action.DEVICE_FINANCING_STATE_CHANGED" />
+    <protected-broadcast android:name="android.app.admin.action.DEVICE_POLICY_SET_RESULT" />
+    <protected-broadcast android:name="android.app.admin.action.DEVICE_POLICY_CHANGED" />
 
     <!-- ====================================================================== -->
     <!--                          RUNTIME PERMISSIONS                           -->
@@ -4035,8 +4038,7 @@
         android:description="@string/permdesc_setWallpaperHints"
         android:protectionLevel="normal" />
 
-    <!-- Allow the app to read the system wallpaper image without
-        holding the READ_EXTERNAL_STORAGE permission.
+    <!-- Allow the app to read the system and lock wallpaper images.
         <p>Not for use by third-party applications.
         @hide
         @SystemApi
@@ -4492,7 +4494,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 +5305,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/config.xml b/core/res/res/values/config.xml
index 2ed1817..220a193 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -720,14 +720,13 @@
          display is powered on at the same time. -->
     <bool name="config_supportsConcurrentInternalDisplays">true</bool>
 
-    <!-- Map of DeviceState to rotation lock setting. Each entry must be in the format
-         "key:value", for example: "0:1".
-          The keys are device states, and the values are one of
-          Settings.Secure.DeviceStateRotationLockSetting.
-          Any device state that doesn't have a default set here will be treated as
-          DEVICE_STATE_ROTATION_LOCK_IGNORED meaning it will not have its own rotation lock setting.
-          If this map is missing, the feature is disabled and only one global rotation lock setting
-           will apply, regardless of device state. -->
+    <!-- Map of device posture to rotation lock setting. Each entry must be in the format
+         "key:value", or "key:value:fallback_key" for example: "0:1" or "2:0:1". The keys are one of
+         Settings.Secure.DeviceStateRotationLockKey, and the values are one of
+         Settings.Secure.DeviceStateRotationLockSetting.
+         The fallback is a key to a device posture that can be specified when the value is
+         Settings.Secure.DEVICE_STATE_ROTATION_LOCK_IGNORED.
+     -->
     <string-array name="config_perDeviceStateRotationLockDefaults" />
 
     <!-- Dock behavior -->
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 5bb86dc..80bf795 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -356,7 +356,7 @@
     <dimen name="notification_headerless_margin_twoline">20dp</dimen>
 
     <!-- The height of each of the 1 or 2 lines in the headerless notification template -->
-    <dimen name="notification_headerless_line_height">24dp</dimen>
+    <dimen name="notification_headerless_line_height">24sp</dimen>
 
     <!-- vertical margin for the headerless notification content -->
     <dimen name="notification_headerless_min_height">56dp</dimen>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d8e69d7..6afdae5 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -6267,6 +6267,12 @@
     <string name="concurrent_display_notification_thermal_title">Device is too warm</string>
     <!-- Content of concurrent display thermal notification. [CHAR LIMIT=NONE] -->
     <string name="concurrent_display_notification_thermal_content">Dual Screen is unavailable because your phone is getting too warm</string>
+    <!-- Title of concurrent display power saver notification. [CHAR LIMIT=NONE] -->
+    <string name="concurrent_display_notification_power_save_title">Dual Screen is unavailable</string>
+    <!-- Content of concurrent display power saver notification. [CHAR LIMIT=NONE] -->
+    <string name="concurrent_display_notification_power_save_content">Dual Screen is unavailable because Battery Saver is on. You can turn this off in Settings.</string>
+    <!-- Text of power saver notification settings button. [CHAR LIMIT=NONE] -->
+    <string name="device_state_notification_settings_button">Go to Settings</string>
     <!-- Text of device state notification turn off button. [CHAR LIMIT=NONE] -->
     <string name="device_state_notification_turn_off_button">Turn off</string>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c10612e..1cb56e0 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4931,12 +4931,17 @@
   <java-symbol type="array" name="device_state_notification_active_contents"/>
   <java-symbol type="array" name="device_state_notification_thermal_titles"/>
   <java-symbol type="array" name="device_state_notification_thermal_contents"/>
+  <java-symbol type="array" name="device_state_notification_power_save_titles"/>
+  <java-symbol type="array" name="device_state_notification_power_save_contents"/>
   <java-symbol type="string" name="concurrent_display_notification_name"/>
   <java-symbol type="string" name="concurrent_display_notification_active_title"/>
   <java-symbol type="string" name="concurrent_display_notification_active_content"/>
   <java-symbol type="string" name="concurrent_display_notification_thermal_title"/>
   <java-symbol type="string" name="concurrent_display_notification_thermal_content"/>
+  <java-symbol type="string" name="concurrent_display_notification_power_save_title"/>
+  <java-symbol type="string" name="concurrent_display_notification_power_save_content"/>
   <java-symbol type="string" name="device_state_notification_turn_off_button"/>
+  <java-symbol type="string" name="device_state_notification_settings_button"/>
   <java-symbol type="bool" name="config_independentLockscreenLiveWallpaper"/>
   <java-symbol type="integer" name="config_deviceStateConcurrentRearDisplay" />
   <java-symbol type="string" name="config_rearDisplayPhysicalAddress" />
diff --git a/core/tests/BroadcastRadioTests/AndroidManifest.xml b/core/tests/BroadcastRadioTests/AndroidManifest.xml
index 8f655ef..fef3d16 100644
--- a/core/tests/BroadcastRadioTests/AndroidManifest.xml
+++ b/core/tests/BroadcastRadioTests/AndroidManifest.xml
@@ -18,6 +18,8 @@
     package="com.android.frameworks.broadcastradiotests">
 
     <uses-permission android:name="android.permission.ACCESS_BROADCAST_RADIO" />
+    <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE" />
+    <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
 
     <application android:debuggable="true">
         <uses-library android:name="android.test.runner" />
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java
index 75f8c95..7c3d2f2 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java
@@ -580,7 +580,7 @@
         doAnswer(invocation -> {
             mTunerCallback = (ITunerCallback) invocation.getArguments()[3];
             return mTunerMock;
-        }).when(mRadioServiceMock).openTuner(anyInt(), any(), anyBoolean(), any(), anyInt());
+        }).when(mRadioServiceMock).openTuner(anyInt(), any(), anyBoolean(), any());
 
         mRadioTuner = radioManager.openTuner(/* moduleId= */ 0, band,
                 /* withAudio= */ true, mTunerCallbackMock, /* handler= */ null);
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java
index ce3e019..b9f4c3f 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java
@@ -30,7 +30,6 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
-import android.os.Build;
 import android.os.Parcel;
 import android.os.RemoteException;
 import android.util.ArrayMap;
@@ -50,8 +49,6 @@
 @RunWith(MockitoJUnitRunner.class)
 public final class RadioManagerTest {
 
-    private static final int TEST_TARGET_SDK_VERSION = Build.VERSION_CODES.CUR_DEVELOPMENT;
-
     private static final int REGION = RadioManager.REGION_ITU_2;
     private static final int FM_LOWER_LIMIT = 87500;
     private static final int FM_UPPER_LIMIT = 108000;
@@ -1043,14 +1040,13 @@
         mRadioManager.openTuner(moduleId, FM_BAND_CONFIG, withAudio, mCallbackMock,
                 /* handler= */ null);
 
-        verify(mRadioServiceMock).openTuner(eq(moduleId), eq(FM_BAND_CONFIG), eq(withAudio), any(),
-                anyInt());
+        verify(mRadioServiceMock).openTuner(eq(moduleId), eq(FM_BAND_CONFIG), eq(withAudio), any());
     }
 
     @Test
     public void openTuner_whenServiceDied_returnsNull() throws Exception {
         createRadioManager();
-        when(mRadioServiceMock.openTuner(anyInt(), any(), anyBoolean(), any(), anyInt()))
+        when(mRadioServiceMock.openTuner(anyInt(), any(), anyBoolean(), any()))
                 .thenThrow(new RemoteException());
 
         RadioTuner nullTuner = mRadioManager.openTuner(/* moduleId= */ 0, FM_BAND_CONFIG,
@@ -1166,7 +1162,6 @@
     }
 
     private void createRadioManager() throws RemoteException {
-        mApplicationInfo.targetSdkVersion = TEST_TARGET_SDK_VERSION;
         when(mContextMock.getApplicationInfo()).thenReturn(mApplicationInfo);
         when(mRadioServiceMock.listModules()).thenReturn(Arrays.asList(AMFM_PROPERTIES));
         when(mRadioServiceMock.addAnnouncementListener(any(), any())).thenReturn(mCloseHandleMock);
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java
index 8b257e8..c7b82b1 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java
@@ -86,7 +86,7 @@
         doAnswer(invocation -> {
             mTunerCallback = (ITunerCallback) invocation.getArguments()[3];
             return mTunerMock;
-        }).when(mRadioServiceMock).openTuner(anyInt(), any(), anyBoolean(), any(), anyInt());
+        }).when(mRadioServiceMock).openTuner(anyInt(), any(), anyBoolean(), any());
 
         doAnswer(invocation -> {
             ProgramSelector program = (ProgramSelector) invocation.getArguments()[0];
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java
index 16c1499..da51ba4 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java
@@ -24,7 +24,6 @@
 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.doNothing;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -35,7 +34,6 @@
 import android.hardware.radio.ITuner;
 import android.hardware.radio.ITunerCallback;
 import android.hardware.radio.RadioManager;
-import android.os.Build;
 import android.os.IBinder;
 import android.os.ServiceManager;
 
@@ -58,7 +56,6 @@
             "android.hardware.broadcastradio.IBroadcastRadio/amfm";
     private static final String DAB_SERVICE_NAME =
             "android.hardware.broadcastradio.IBroadcastRadio/dab";
-    private static final int TARGET_SDK_VERSION = Build.VERSION_CODES.CUR_DEVELOPMENT;
 
     private IRadioServiceAidlImpl mAidlImpl;
 
@@ -86,7 +83,7 @@
         doNothing().when(mServiceMock).enforcePolicyAccess();
 
         when(mHalMock.listModules()).thenReturn(List.of(mModuleMock));
-        when(mHalMock.openSession(anyInt(), any(), anyBoolean(), any(), eq(TARGET_SDK_VERSION)))
+        when(mHalMock.openSession(anyInt(), any(), anyBoolean(), any()))
                 .thenReturn(mTunerMock);
         when(mHalMock.addAnnouncementListener(any(), any())).thenReturn(mICloseHandle);
 
@@ -118,7 +115,7 @@
     @Test
     public void openTuner_forAidlImpl() throws Exception {
         ITuner tuner = mAidlImpl.openTuner(/* moduleId= */ 0, mBandConfigMock,
-                /* withAudio= */ true, mTunerCallbackMock, TARGET_SDK_VERSION);
+                /* withAudio= */ true, mTunerCallbackMock);
 
         assertWithMessage("Tuner opened in AIDL HAL")
                 .that(tuner).isEqualTo(mTunerMock);
@@ -128,7 +125,7 @@
     public void openTuner_withNullCallbackForAidlImpl_fails() throws Exception {
         IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
                 () -> mAidlImpl.openTuner(/* moduleId= */ 0, mBandConfigMock,
-                        /* withAudio= */ true, /* callback= */ null, TARGET_SDK_VERSION));
+                        /* withAudio= */ true, /* callback= */ null));
 
         assertWithMessage("Exception for opening tuner with null callback")
                 .that(thrown).hasMessageThat().contains("Callback must not be null");
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java
index 164c9af..20bc8d4 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java
@@ -33,7 +33,6 @@
 import android.hardware.radio.ITuner;
 import android.hardware.radio.ITunerCallback;
 import android.hardware.radio.RadioManager;
-import android.os.Build;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -51,7 +50,6 @@
 
     private static final int HAL1_MODULE_ID = 0;
     private static final int[] ENABLE_TYPES = new int[]{Announcement.TYPE_TRAFFIC};
-    private static final int TARGET_SDK_VERSION = Build.VERSION_CODES.CUR_DEVELOPMENT;
 
     private IRadioServiceHidlImpl mHidlImpl;
 
@@ -106,7 +104,7 @@
     @Test
     public void openTuner_withHal1ModuleId_forHidlImpl() throws Exception {
         ITuner tuner = mHidlImpl.openTuner(HAL1_MODULE_ID, mBandConfigMock,
-                /* withAudio= */ true, mTunerCallbackMock, TARGET_SDK_VERSION);
+                /* withAudio= */ true, mTunerCallbackMock);
 
         assertWithMessage("Tuner opened in HAL 1")
                 .that(tuner).isEqualTo(mHal1TunerMock);
@@ -115,7 +113,7 @@
     @Test
     public void openTuner_withHal2ModuleId_forHidlImpl() throws Exception {
         ITuner tuner = mHidlImpl.openTuner(HAL1_MODULE_ID + 1, mBandConfigMock,
-                /* withAudio= */ true, mTunerCallbackMock, TARGET_SDK_VERSION);
+                /* withAudio= */ true, mTunerCallbackMock);
 
         assertWithMessage("Tuner opened in HAL 2")
                 .that(tuner).isEqualTo(mHal2TunerMock);
@@ -125,7 +123,7 @@
     public void openTuner_withNullCallbackForHidlImpl_fails() throws Exception {
         NullPointerException thrown = assertThrows(NullPointerException.class,
                 () -> mHidlImpl.openTuner(/* moduleId= */ 0, mBandConfigMock,
-                        /* withAudio= */ true, /* callback= */ null, TARGET_SDK_VERSION));
+                        /* withAudio= */ true, /* callback= */ null));
 
         assertWithMessage("Exception for opening tuner with null callback")
                 .that(thrown).hasMessageThat().contains("Callback must not be null");
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/RadioServiceUserControllerTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/RadioServiceUserControllerTest.java
index 161ac2d..3e9e992 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/RadioServiceUserControllerTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/RadioServiceUserControllerTest.java
@@ -24,6 +24,7 @@
 import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
+import android.app.compat.CompatChanges;
 import android.os.Binder;
 import android.os.UserHandle;
 
@@ -46,8 +47,8 @@
 
     @Override
     protected void initializeSession(StaticMockitoSessionBuilder builder) {
-        builder.spyStatic(ActivityManager.class)
-                .spyStatic(Binder.class);
+        builder.spyStatic(ActivityManager.class).spyStatic(Binder.class)
+                .spyStatic(CompatChanges.class);
     }
 
     @Before
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java
index 98103f6..22f3bd4 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java
@@ -36,7 +36,6 @@
 import android.hardware.radio.ITunerCallback;
 import android.hardware.radio.RadioManager;
 import android.hardware.radio.RadioTuner;
-import android.os.Build;
 import android.os.IBinder;
 import android.os.IServiceCallback;
 import android.os.RemoteException;
@@ -55,8 +54,6 @@
 
 public final class BroadcastRadioServiceImplTest extends ExtendedRadioMockitoTestCase {
 
-    private static final int TARGET_SDK_VERSION = Build.VERSION_CODES.CUR_DEVELOPMENT;
-
     private static final int FM_RADIO_MODULE_ID = 0;
     private static final int DAB_RADIO_MODULE_ID = 1;
     private static final ArrayList<String> SERVICE_LIST =
@@ -140,8 +137,7 @@
         createBroadcastRadioService();
 
         ITuner session = mBroadcastRadioService.openSession(FM_RADIO_MODULE_ID,
-                /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock,
-                TARGET_SDK_VERSION);
+                /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock);
 
         assertWithMessage("Session opened in FM radio module")
                 .that(session).isEqualTo(mFmTunerSessionMock);
@@ -152,8 +148,7 @@
         createBroadcastRadioService();
 
         ITuner session = mBroadcastRadioService.openSession(DAB_RADIO_MODULE_ID + 1,
-                /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock,
-                TARGET_SDK_VERSION);
+                /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock);
 
         assertWithMessage("Session opened with id not found").that(session).isNull();
     }
@@ -165,8 +160,7 @@
 
         IllegalStateException thrown = assertThrows(IllegalStateException.class,
                 () -> mBroadcastRadioService.openSession(FM_RADIO_MODULE_ID,
-                        /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock,
-                        TARGET_SDK_VERSION));
+                        /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock));
 
         assertWithMessage("Exception for opening session by non-current user")
                 .that(thrown).hasMessageThat().contains("Cannot open session for non-current user");
@@ -178,8 +172,7 @@
 
         IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
                 () -> mBroadcastRadioService.openSession(FM_RADIO_MODULE_ID,
-                        /* legacyConfig= */ null, /* withAudio= */ false, mTunerCallbackMock,
-                        TARGET_SDK_VERSION));
+                        /* legacyConfig= */ null, /* withAudio= */ false, mTunerCallbackMock));
 
         assertWithMessage("Exception for opening session without audio")
                 .that(thrown).hasMessageThat().contains("not supported");
@@ -247,7 +240,6 @@
             return null;
         }).when(mFmBinderMock).linkToDeath(any(), anyInt());
 
-        when(mFmRadioModuleMock.openSession(eq(mTunerCallbackMock), eq(TARGET_SDK_VERSION)))
-                .thenReturn(mFmTunerSessionMock);
+        when(mFmRadioModuleMock.openSession(mTunerCallbackMock)).thenReturn(mFmTunerSessionMock);
     }
 }
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
index 5d0e076..ba05791 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
@@ -16,6 +16,9 @@
 
 package com.android.server.broadcastradio.aidl;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
+import android.app.compat.CompatChanges;
 import android.hardware.broadcastradio.AmFmBandRange;
 import android.hardware.broadcastradio.AmFmRegionConfig;
 import android.hardware.broadcastradio.DabTableEntry;
@@ -29,17 +32,23 @@
 import android.hardware.radio.ProgramList;
 import android.hardware.radio.ProgramSelector;
 import android.hardware.radio.RadioManager;
-import android.os.Build;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
+import com.android.server.broadcastradio.ExtendedRadioMockitoTestCase;
 
 import com.google.common.truth.Expect;
 
+import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 
 import java.util.Map;
 import java.util.Set;
 
-public final class ConversionUtilsTest {
+public final class ConversionUtilsTest extends ExtendedRadioMockitoTestCase {
+
+    private static final int U_APP_UID = 1001;
+    private static final int T_APP_UID = 1002;
 
     private static final int FM_LOWER_LIMIT = 87_500;
     private static final int FM_UPPER_LIMIT = 108_000;
@@ -118,16 +127,29 @@
     @Rule
     public final Expect expect = Expect.create();
 
+    @Override
+    protected void initializeSession(StaticMockitoSessionBuilder builder) {
+        builder.spyStatic(CompatChanges.class);
+    }
+
+    @Before
+    public void setUp() {
+        doReturn(true).when(() -> CompatChanges.isChangeEnabled(
+                ConversionUtils.RADIO_U_VERSION_REQUIRED, U_APP_UID));
+        doReturn(false).when(() -> CompatChanges.isChangeEnabled(
+                ConversionUtils.RADIO_U_VERSION_REQUIRED, T_APP_UID));
+    }
+
     @Test
     public void isAtLeastU_withTSdkVersion_returnsFalse() {
         expect.withMessage("Target SDK version of T")
-                .that(ConversionUtils.isAtLeastU(Build.VERSION_CODES.TIRAMISU)).isFalse();
+                .that(ConversionUtils.isAtLeastU(T_APP_UID)).isFalse();
     }
 
     @Test
     public void isAtLeastU_withCurrentSdkVersion_returnsTrue() {
         expect.withMessage("Target SDK version of U")
-                .that(ConversionUtils.isAtLeastU(Build.VERSION_CODES.CUR_DEVELOPMENT)).isTrue();
+                .that(ConversionUtils.isAtLeastU(U_APP_UID)).isTrue();
     }
 
     @Test
@@ -372,14 +394,14 @@
     public void programSelectorMeetsSdkVersionRequirement_withLowerVersionId_returnsFalse() {
         expect.withMessage("Selector %s without required SDK version", TEST_DAB_SELECTOR)
                 .that(ConversionUtils.programSelectorMeetsSdkVersionRequirement(TEST_DAB_SELECTOR,
-                        Build.VERSION_CODES.TIRAMISU)).isFalse();
+                        T_APP_UID)).isFalse();
     }
 
     @Test
     public void programSelectorMeetsSdkVersionRequirement_withRequiredVersionId_returnsTrue() {
         expect.withMessage("Selector %s with required SDK version", TEST_FM_SELECTOR)
                 .that(ConversionUtils.programSelectorMeetsSdkVersionRequirement(TEST_FM_SELECTOR,
-                        Build.VERSION_CODES.TIRAMISU)).isTrue();
+                        T_APP_UID)).isTrue();
     }
 
     @Test
@@ -389,7 +411,7 @@
 
         expect.withMessage("Program info %s without required SDK version", dabProgramInfo)
                 .that(ConversionUtils.programInfoMeetsSdkVersionRequirement(dabProgramInfo,
-                        Build.VERSION_CODES.TIRAMISU)).isFalse();
+                        T_APP_UID)).isFalse();
     }
 
     @Test
@@ -399,7 +421,7 @@
 
         expect.withMessage("Program info %s with required SDK version", fmProgramInfo)
                 .that(ConversionUtils.programInfoMeetsSdkVersionRequirement(fmProgramInfo,
-                        Build.VERSION_CODES.TIRAMISU)).isTrue();
+                        T_APP_UID)).isTrue();
     }
 
     @Test
@@ -413,7 +435,7 @@
                 Set.of(TEST_DAB_SID_EXT_ID, TEST_DAB_ENSEMBLE_ID, TEST_VENDOR_ID));
 
         ProgramList.Chunk convertedChunk = ConversionUtils.convertChunkToTargetSdkVersion(chunk,
-                Build.VERSION_CODES.TIRAMISU);
+                T_APP_UID);
 
         expect.withMessage(
                 "Purged state of the converted program list chunk with lower SDK version")
@@ -441,7 +463,7 @@
                 Set.of(TEST_DAB_SID_EXT_ID, TEST_DAB_ENSEMBLE_ID, TEST_VENDOR_ID));
 
         ProgramList.Chunk convertedChunk = ConversionUtils.convertChunkToTargetSdkVersion(chunk,
-                Build.VERSION_CODES.CUR_DEVELOPMENT);
+                U_APP_UID);
 
         expect.withMessage("Converted program list chunk with required SDK version")
                 .that(convertedChunk).isEqualTo(chunk);
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
index 464ecb2..78b5a4a 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
@@ -33,6 +33,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.compat.CompatChanges;
 import android.graphics.Bitmap;
 import android.hardware.broadcastradio.IBroadcastRadio;
 import android.hardware.broadcastradio.ITunerCallback;
@@ -46,7 +47,6 @@
 import android.hardware.radio.ProgramSelector;
 import android.hardware.radio.RadioManager;
 import android.hardware.radio.RadioTuner;
-import android.os.Build;
 import android.os.ParcelableException;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
@@ -73,7 +73,6 @@
  */
 public final class TunerSessionTest extends ExtendedRadioMockitoTestCase {
 
-    private static final int TARGET_SDK_VERSION = Build.VERSION_CODES.CUR_DEVELOPMENT;
     private static final VerificationWithTimeout CALLBACK_TIMEOUT =
             timeout(/* millis= */ 200);
     private static final int SIGNAL_QUALITY = 90;
@@ -125,11 +124,13 @@
 
     @Override
     protected void initializeSession(StaticMockitoSessionBuilder builder) {
-        builder.spyStatic(RadioServiceUserController.class);
+        builder.spyStatic(RadioServiceUserController.class).spyStatic(CompatChanges.class);
     }
 
     @Before
     public void setup() throws Exception {
+        doReturn(true).when(() -> CompatChanges.isChangeEnabled(
+                eq(ConversionUtils.RADIO_U_VERSION_REQUIRED), anyInt()));
         doReturn(true).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
 
         mRadioModule = new RadioModule(mBroadcastRadioMock,
@@ -341,7 +342,9 @@
 
     @Test
     public void tune_withLowerSdkVersion() throws Exception {
-        openAidlClients(/* numClients= */ 1, Build.VERSION_CODES.TIRAMISU);
+        doReturn(false).when(() -> CompatChanges.isChangeEnabled(
+                eq(ConversionUtils.RADIO_U_VERSION_REQUIRED), anyInt()));
+        openAidlClients(/* numClients= */ 1);
         ProgramSelector initialSel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
         RadioManager.ProgramInfo tuneInfo =
                 AidlTestUtils.makeProgramInfo(initialSel, SIGNAL_QUALITY);
@@ -1175,17 +1178,13 @@
                     .onParametersUpdated(parametersExpected);
         }
     }
-    private void openAidlClients(int numClients) throws Exception {
-        openAidlClients(numClients, TARGET_SDK_VERSION);
-    }
 
-    private void openAidlClients(int numClients, int targetSdkVersion) throws Exception {
+    private void openAidlClients(int numClients) throws Exception {
         mAidlTunerCallbackMocks = new android.hardware.radio.ITunerCallback[numClients];
         mTunerSessions = new TunerSession[numClients];
         for (int index = 0; index < numClients; index++) {
             mAidlTunerCallbackMocks[index] = mock(android.hardware.radio.ITunerCallback.class);
-            mTunerSessions[index] = mRadioModule.openSession(mAidlTunerCallbackMocks[index],
-                    targetSdkVersion);
+            mTunerSessions[index] = mRadioModule.openSession(mAidlTunerCallbackMocks[index]);
         }
     }
 
diff --git a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
index 9b1f0cd..9a202ae 100644
--- a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
+++ b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
@@ -112,7 +112,7 @@
         mCaptor.getValue().onAllAuthenticatorsRegistered(mProps);
         List<FaceSensorPropertiesInternal> actual = mFaceManager.getSensorPropertiesInternal();
 
-        assertThat(actual).isEqualTo(mProps);
+        assertThat(actual).containsExactlyElementsIn(mProps);
         verify(mService, never()).getSensorPropertiesInternal(any());
     }
 
diff --git a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java
index f31903a..5058065 100644
--- a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java
+++ b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java
@@ -113,7 +113,7 @@
         List<FingerprintSensorPropertiesInternal> actual =
                 mFingerprintManager.getSensorPropertiesInternal();
 
-        assertThat(actual).isEqualTo(mProps);
+        assertThat(actual).containsExactlyElementsIn(mProps);
         verify(mService, never()).getSensorPropertiesInternal(any());
     }
 
diff --git a/core/tests/coretests/src/android/provider/DeviceConfigTest.java b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
index aa1853f..1ea20f1 100644
--- a/core/tests/coretests/src/android/provider/DeviceConfigTest.java
+++ b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
@@ -803,51 +803,51 @@
 
         try {
             // Ensure the device starts in a known state.
-            DeviceConfig.setSyncDisabledMode(Settings.Config.SYNC_DISABLED_MODE_NONE);
+            DeviceConfig.setSyncDisabledMode(DeviceConfig.SYNC_DISABLED_MODE_NONE);
 
             // Assert starting state.
             assertThat(DeviceConfig.getSyncDisabledMode())
-                    .isEqualTo(Settings.Config.SYNC_DISABLED_MODE_NONE);
+                    .isEqualTo(DeviceConfig.SYNC_DISABLED_MODE_NONE);
             assertThat(DeviceConfig.setProperties(properties1)).isTrue();
             assertThat(DeviceConfig.getProperties(NAMESPACE, KEY).getString(KEY, DEFAULT_VALUE))
                     .isEqualTo(VALUE);
 
             // Test disabled (persistent). Persistence is not actually tested, that would require
             // a host test.
-            DeviceConfig.setSyncDisabledMode(Settings.Config.SYNC_DISABLED_MODE_PERSISTENT);
+            DeviceConfig.setSyncDisabledMode(DeviceConfig.SYNC_DISABLED_MODE_PERSISTENT);
             assertThat(DeviceConfig.getSyncDisabledMode())
-                    .isEqualTo(Settings.Config.SYNC_DISABLED_MODE_PERSISTENT);
+                    .isEqualTo(DeviceConfig.SYNC_DISABLED_MODE_PERSISTENT);
             assertThat(DeviceConfig.setProperties(properties2)).isFalse();
             assertThat(DeviceConfig.getProperties(NAMESPACE, KEY).getString(KEY, DEFAULT_VALUE))
                     .isEqualTo(VALUE);
 
             // Return to not disabled.
-            DeviceConfig.setSyncDisabledMode(Settings.Config.SYNC_DISABLED_MODE_NONE);
+            DeviceConfig.setSyncDisabledMode(DeviceConfig.SYNC_DISABLED_MODE_NONE);
             assertThat(DeviceConfig.getSyncDisabledMode())
-                    .isEqualTo(Settings.Config.SYNC_DISABLED_MODE_NONE);
+                    .isEqualTo(DeviceConfig.SYNC_DISABLED_MODE_NONE);
             assertThat(DeviceConfig.setProperties(properties2)).isTrue();
             assertThat(DeviceConfig.getProperties(NAMESPACE, KEY).getString(KEY, DEFAULT_VALUE))
                     .isEqualTo(VALUE2);
 
             // Test disabled (persistent). Absence of persistence is not actually tested, that would
             // require a host test.
-            DeviceConfig.setSyncDisabledMode(Settings.Config.SYNC_DISABLED_MODE_UNTIL_REBOOT);
+            DeviceConfig.setSyncDisabledMode(DeviceConfig.SYNC_DISABLED_MODE_UNTIL_REBOOT);
             assertThat(DeviceConfig.getSyncDisabledMode())
-                    .isEqualTo(Settings.Config.SYNC_DISABLED_MODE_UNTIL_REBOOT);
+                    .isEqualTo(DeviceConfig.SYNC_DISABLED_MODE_UNTIL_REBOOT);
             assertThat(DeviceConfig.setProperties(properties1)).isFalse();
             assertThat(DeviceConfig.getProperties(NAMESPACE, KEY).getString(KEY, DEFAULT_VALUE))
                     .isEqualTo(VALUE2);
 
             // Return to not disabled.
-            DeviceConfig.setSyncDisabledMode(Settings.Config.SYNC_DISABLED_MODE_NONE);
+            DeviceConfig.setSyncDisabledMode(DeviceConfig.SYNC_DISABLED_MODE_NONE);
             assertThat(DeviceConfig.getSyncDisabledMode())
-                    .isEqualTo(Settings.Config.SYNC_DISABLED_MODE_NONE);
+                    .isEqualTo(DeviceConfig.SYNC_DISABLED_MODE_NONE);
             assertThat(DeviceConfig.setProperties(properties1)).isTrue();
             assertThat(DeviceConfig.getProperties(NAMESPACE, KEY).getString(KEY, DEFAULT_VALUE))
                     .isEqualTo(VALUE);
         } finally {
             // Try to return to the default sync disabled state in case of failure.
-            DeviceConfig.setSyncDisabledMode(Settings.Config.SYNC_DISABLED_MODE_NONE);
+            DeviceConfig.setSyncDisabledMode(DeviceConfig.SYNC_DISABLED_MODE_NONE);
 
             // NAMESPACE will be cleared by cleanUp()
         }
diff --git a/core/tests/coretests/src/android/view/inputmethod/DeleteRangeGestureTest.java b/core/tests/coretests/src/android/view/inputmethod/DeleteRangeGestureTest.java
new file mode 100644
index 0000000..d7b911d
--- /dev/null
+++ b/core/tests/coretests/src/android/view/inputmethod/DeleteRangeGestureTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.graphics.RectF;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.ApiTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@ApiTest(apis = {"android.view.inputmethod.DeleteRangeGesture.Builder#setGranularity",
+    "android.view.inputmethod.DeleteRangeGesture.Builder#setDeletionStartArea",
+    "android.view.inputmethod.DeleteRangeGesture.Builder#setDeletionEndArea",
+    "android.view.inputmethod.DeleteRangeGesture.Builder#setFallbackText",
+    "android.view.inputmethod.DeleteRangeGesture.Builder#build"})
+public class DeleteRangeGestureTest {
+    private static final RectF DELETION_START_RECTANGLE = new RectF(1, 2, 3, 4);
+    private static final RectF DELETION_END_RECTANGLE = new RectF(0, 2, 3, 4);
+    private static final String FALLBACK_TEXT = "fallback_test";
+
+    @Test
+    public void testBuilder() {
+        DeleteRangeGesture.Builder builder = new DeleteRangeGesture.Builder();
+        DeleteRangeGesture gesture = builder.setGranularity(HandwritingGesture.GRANULARITY_WORD)
+                .setDeletionStartArea(DELETION_START_RECTANGLE)
+                .setDeletionEndArea(DELETION_END_RECTANGLE)
+                .setFallbackText(FALLBACK_TEXT).build();
+        assertNotNull(gesture);
+        assertEquals(HandwritingGesture.GRANULARITY_WORD, gesture.getGranularity());
+        assertEquals(DELETION_START_RECTANGLE, gesture.getDeletionStartArea());
+        assertEquals(DELETION_END_RECTANGLE, gesture.getDeletionEndArea());
+        assertEquals(FALLBACK_TEXT, gesture.getFallbackText());
+    }
+}
diff --git a/core/tests/coretests/src/android/view/inputmethod/InsertGestureTest.java b/core/tests/coretests/src/android/view/inputmethod/InsertGestureTest.java
new file mode 100644
index 0000000..47a724d
--- /dev/null
+++ b/core/tests/coretests/src/android/view/inputmethod/InsertGestureTest.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.graphics.PointF;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.ApiTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@ApiTest(apis = {"android.view.inputmethod.InsertGesture.Builder#setInsertionPoint",
+    "android.view.inputmethod.InsertGesture.Builder#setTextToInsert",
+    "android.view.inputmethod.InsertGesture.Builder#setFallbackText",
+    "android.view.inputmethod.InsertGesture.Builder#build"})
+public class InsertGestureTest {
+    private static final PointF INSERTION_POINT = new PointF(1, 2);
+    private static final String FALLBACK_TEXT = "fallback_text";
+    private static final String TEXT_TO_INSERT = "text";
+
+    @Test
+    public void testBuilder() {
+        InsertGesture.Builder builder = new InsertGesture.Builder();
+        InsertGesture gesture = builder.setInsertionPoint(INSERTION_POINT)
+                .setTextToInsert(TEXT_TO_INSERT)
+                .setFallbackText(FALLBACK_TEXT).build();
+        assertNotNull(gesture);
+        assertEquals(INSERTION_POINT, gesture.getInsertionPoint());
+        assertEquals(FALLBACK_TEXT, gesture.getFallbackText());
+        assertEquals(TEXT_TO_INSERT, gesture.getTextToInsert());
+    }
+}
diff --git a/core/tests/coretests/src/android/view/inputmethod/InsertModeGestureTest.java b/core/tests/coretests/src/android/view/inputmethod/InsertModeGestureTest.java
new file mode 100644
index 0000000..11ddba1
--- /dev/null
+++ b/core/tests/coretests/src/android/view/inputmethod/InsertModeGestureTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.graphics.PointF;
+import android.os.CancellationSignal;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.ApiTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@ApiTest(apis = {"android.view.inputmethod.InsertModeGesture.Builder#setInsertionPoint",
+    "android.view.inputmethod.InsertModeGesture.Builder#setCancellationSignal",
+    "android.view.inputmethod.InsertModeGesture.Builder#setFallbackText",
+    "android.view.inputmethod.InsertModeGesture.Builder#build"})
+public class InsertModeGestureTest {
+    private static final PointF INSERTION_POINT = new PointF(1, 2);
+    private static final String FALLBACK_TEXT = "fallback_text";
+    private static final CancellationSignal CANCELLATION_SIGNAL = new CancellationSignal();
+
+    @Test
+    public void testBuilder() {
+        InsertModeGesture.Builder builder = new InsertModeGesture.Builder();
+        InsertModeGesture gesture = builder.setInsertionPoint(INSERTION_POINT)
+                .setCancellationSignal(CANCELLATION_SIGNAL)
+                .setFallbackText(FALLBACK_TEXT).build();
+        assertNotNull(gesture);
+        assertEquals(INSERTION_POINT, gesture.getInsertionPoint());
+        assertEquals(FALLBACK_TEXT, gesture.getFallbackText());
+        assertEquals(CANCELLATION_SIGNAL, gesture.getCancellationSignal());
+    }
+}
diff --git a/core/tests/coretests/src/android/view/inputmethod/SelectGestureTest.java b/core/tests/coretests/src/android/view/inputmethod/SelectGestureTest.java
new file mode 100644
index 0000000..b2eb07c
--- /dev/null
+++ b/core/tests/coretests/src/android/view/inputmethod/SelectGestureTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.graphics.RectF;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.ApiTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@ApiTest(apis = {"android.view.inputmethod.SelectGesture.Builder#setGranularity",
+    "android.view.inputmethod.SelectGesture.Builder#setSelectionArea",
+    "android.view.inputmethod.SelectGesture.Builder#setFallbackText",
+    "android.view.inputmethod.SelectGesture.Builder#build"})
+public class SelectGestureTest {
+    private static final RectF SELECTION_RECTANGLE = new RectF(1, 2, 3, 4);
+    private static final String FALLBACK_TEXT = "fallback_text";
+
+    @Test
+    public void testBuilder() {
+        SelectGesture.Builder builder = new SelectGesture.Builder();
+        SelectGesture gesture = builder.setGranularity(HandwritingGesture.GRANULARITY_WORD)
+                .setSelectionArea(SELECTION_RECTANGLE)
+                .setFallbackText(FALLBACK_TEXT).build();
+        assertNotNull(gesture);
+        assertEquals(HandwritingGesture.GRANULARITY_WORD, gesture.getGranularity());
+        assertEquals(SELECTION_RECTANGLE, gesture.getSelectionArea());
+        assertEquals(FALLBACK_TEXT, gesture.getFallbackText());
+    }
+}
diff --git a/core/tests/coretests/src/android/view/inputmethod/SelectRangeGestureTest.java b/core/tests/coretests/src/android/view/inputmethod/SelectRangeGestureTest.java
new file mode 100644
index 0000000..df63a4a
--- /dev/null
+++ b/core/tests/coretests/src/android/view/inputmethod/SelectRangeGestureTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.graphics.RectF;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.ApiTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@ApiTest(apis = {"android.view.inputmethod.SelectRangeGesture.Builder#setGranularity",
+    "android.view.inputmethod.SelectRangeGesture.Builder#setSelectionStartArea",
+    "android.view.inputmethod.SelectRangeGesture.Builder#setSelectionEndArea",
+    "android.view.inputmethod.SelectRangeGesture.Builder#setFallbackText",
+    "android.view.inputmethod.SelectRangeGesture.Builder#build"})
+public class SelectRangeGestureTest {
+    private static final RectF SELECTION_START_RECTANGLE = new RectF(1, 2, 3, 4);
+    private static final RectF SELECTION_END_RECTANGLE = new RectF(0, 2, 3, 4);
+    private static final String FALLBACK_TEXT = "fallback_text";
+
+    @Test
+    public void testBuilder() {
+        SelectRangeGesture.Builder builder = new SelectRangeGesture.Builder();
+        SelectRangeGesture gesture = builder.setGranularity(HandwritingGesture.GRANULARITY_WORD)
+                .setSelectionStartArea(SELECTION_START_RECTANGLE)
+                .setSelectionEndArea(SELECTION_END_RECTANGLE)
+                .setFallbackText(FALLBACK_TEXT).build();
+        assertNotNull(gesture);
+        assertEquals(HandwritingGesture.GRANULARITY_WORD, gesture.getGranularity());
+        assertEquals(SELECTION_START_RECTANGLE, gesture.getSelectionStartArea());
+        assertEquals(SELECTION_END_RECTANGLE, gesture.getSelectionEndArea());
+        assertEquals(FALLBACK_TEXT, gesture.getFallbackText());
+    }
+}
diff --git a/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/com.android.systemui.xml b/data/etc/com.android.systemui.xml
index fe639ff..922dbb5 100644
--- a/data/etc/com.android.systemui.xml
+++ b/data/etc/com.android.systemui.xml
@@ -51,6 +51,7 @@
         <permission name="android.permission.READ_NETWORK_USAGE_HISTORY"/>
         <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
         <permission name="android.permission.READ_PRECISE_PHONE_STATE"/>
+        <permission name="android.permission.READ_WALLPAPER_INTERNAL"/>
         <permission name="android.permission.REAL_GET_TASKS"/>
         <permission name="android.permission.REQUEST_NETWORK_SCORES"/>
         <permission name="android.permission.RECEIVE_MEDIA_RESOURCE_USAGE"/>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 5d303cf..0faf62e 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -321,6 +321,7 @@
         <permission name="android.permission.REGISTER_CONNECTION_MANAGER"/>
         <permission name="android.permission.REGISTER_SIM_SUBSCRIPTION"/>
         <permission name="android.permission.RETRIEVE_WINDOW_CONTENT"/>
+        <permission name="android.permission.SCHEDULE_EXACT_ALARM"/>
         <permission name="android.permission.SET_ALWAYS_FINISH"/>
         <permission name="android.permission.SET_ANIMATION_SCALE"/>
         <permission name="android.permission.SET_DEBUG_APP"/>
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index ffc5ff2..5549f88 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -259,12 +259,6 @@
       "group": "WM_DEBUG_BOOT",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "-1878839956": {
-      "message": "Marking app token %s with replacing windows.",
-      "level": "DEBUG",
-      "group": "WM_DEBUG_ADD_REMOVE",
-      "at": "com\/android\/server\/wm\/ActivityRecord.java"
-    },
     "-1872288685": {
       "message": "applyAnimation: anim=%s nextAppTransition=%s transit=%s isEntrance=%b Callers=%s",
       "level": "VERBOSE",
@@ -463,12 +457,6 @@
       "group": "WM_DEBUG_ADD_REMOVE",
       "at": "com\/android\/server\/wm\/Task.java"
     },
-    "-1698815688": {
-      "message": "Resetting app token %s of replacing window marks.",
-      "level": "DEBUG",
-      "group": "WM_DEBUG_ADD_REMOVE",
-      "at": "com\/android\/server\/wm\/ActivityRecord.java"
-    },
     "-1679411993": {
       "message": "setVr2dDisplayId called for: %d",
       "level": "DEBUG",
@@ -481,12 +469,6 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "-1661704580": {
-      "message": "Attempted to set replacing window on non-existing app token %s",
-      "level": "WARN",
-      "group": "WM_ERROR",
-      "at": "com\/android\/server\/wm\/WindowManagerService.java"
-    },
     "-1647332198": {
       "message": "remove RecentTask %s when finishing user %d",
       "level": "INFO",
@@ -613,12 +595,6 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "-1515151503": {
-      "message": ">>> OPEN TRANSACTION removeReplacedWindows",
-      "level": "INFO",
-      "group": "WM_SHOW_TRANSACTIONS",
-      "at": "com\/android\/server\/wm\/RootWindowContainer.java"
-    },
     "-1501564055": {
       "message": "Organized TaskFragment is not ready= %s",
       "level": "VERBOSE",
@@ -691,12 +667,6 @@
       "group": "WM_DEBUG_TASKS",
       "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
     },
-    "-1471946192": {
-      "message": "Marking app token %s with replacing child windows.",
-      "level": "DEBUG",
-      "group": "WM_DEBUG_ADD_REMOVE",
-      "at": "com\/android\/server\/wm\/ActivityRecord.java"
-    },
     "-1471518109": {
       "message": "Set animatingExit: reason=onAppVisibilityChanged win=%s",
       "level": "DEBUG",
@@ -919,12 +889,6 @@
       "group": "WM_DEBUG_BACK_PREVIEW",
       "at": "com\/android\/server\/wm\/BackNavigationController.java"
     },
-    "-1270731689": {
-      "message": "Attempted to set replacing window on app token with no content %s",
-      "level": "WARN",
-      "group": "WM_ERROR",
-      "at": "com\/android\/server\/wm\/WindowManagerService.java"
-    },
     "-1263316010": {
       "message": "Computed rotation=%s (%d) for display id=%d based on lastOrientation=%s (%d) and oldRotation=%s (%d)",
       "level": "VERBOSE",
@@ -1417,12 +1381,6 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
     },
-    "-799003045": {
-      "message": "Set animatingExit: reason=remove\/replaceWindow win=%s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_ANIM",
-      "at": "com\/android\/server\/wm\/WindowState.java"
-    },
     "-787664727": {
       "message": "Cannot launch dream activity due to invalid state. dream component: %s packageName: %s",
       "level": "ERROR",
@@ -1453,6 +1411,12 @@
       "group": "WM_DEBUG_WINDOW_TRANSITIONS",
       "at": "com\/android\/server\/wm\/Transition.java"
     },
+    "-778347463": {
+      "message": "Remove %s: mSurfaceController=%s mAnimatingExit=%b mRemoveOnExit=%b mHasSurface=%b surfaceShowing=%b animating=%b app-animation=%b mDisplayFrozen=%b callers=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_APP_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/WindowState.java"
+    },
     "-775004869": {
       "message": "Not a match: %s",
       "level": "DEBUG",
@@ -1963,12 +1927,6 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "-320419645": {
-      "message": "Removing replaced window: %s",
-      "level": "DEBUG",
-      "group": "WM_DEBUG_ADD_REMOVE",
-      "at": "com\/android\/server\/wm\/WindowState.java"
-    },
     "-319689203": {
       "message": "Reparenting to original parent: %s for %s",
       "level": "INFO",
@@ -2335,12 +2293,6 @@
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
     },
-    "38267433": {
-      "message": "Attempted to reset replacing window on non-existing app token %s",
-      "level": "WARN",
-      "group": "WM_ERROR",
-      "at": "com\/android\/server\/wm\/WindowManagerService.java"
-    },
     "45285419": {
       "message": "startingWindow was set but startingSurface==null, couldn't remove",
       "level": "VERBOSE",
@@ -2989,12 +2941,6 @@
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
     },
-    "594260654": {
-      "message": "Remove %s: mSurfaceController=%s mAnimatingExit=%b mRemoveOnExit=%b mHasSurface=%b surfaceShowing=%b animating=%b app-animation=%b mWillReplaceWindow=%b mDisplayFrozen=%b callers=%s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_APP_TRANSITIONS",
-      "at": "com\/android\/server\/wm\/WindowState.java"
-    },
     "600140673": {
       "message": "checkBootAnimationComplete: Waiting for anim complete",
       "level": "INFO",
@@ -3829,12 +3775,6 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "1423592961": {
-      "message": "<<< CLOSE TRANSACTION removeReplacedWindows",
-      "level": "INFO",
-      "group": "WM_SHOW_TRANSACTIONS",
-      "at": "com\/android\/server\/wm\/RootWindowContainer.java"
-    },
     "1430336882": {
       "message": "findFocusedWindow: focusedApp windows not focusable using new focus @ %s",
       "level": "VERBOSE",
@@ -3901,12 +3841,6 @@
       "group": "WM_DEBUG_ADD_REMOVE",
       "at": "com\/android\/server\/wm\/WindowState.java"
     },
-    "1515161239": {
-      "message": "removeDeadWindows: %s",
-      "level": "WARN",
-      "group": "WM_DEBUG_ADD_REMOVE",
-      "at": "com\/android\/server\/wm\/ActivityRecord.java"
-    },
     "1518495446": {
       "message": "removeWindowToken: Attempted to remove non-existing token: %s",
       "level": "WARN",
@@ -4273,12 +4207,6 @@
       "group": "WM_DEBUG_WINDOW_ORGANIZER",
       "at": "com\/android\/server\/wm\/TaskOrganizerController.java"
     },
-    "1921821199": {
-      "message": "Preserving %s until the new one is added",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_ADD_REMOVE",
-      "at": "com\/android\/server\/wm\/WindowState.java"
-    },
     "1928325128": {
       "message": "Run showImeRunner",
       "level": "DEBUG",
@@ -4489,12 +4417,6 @@
       "group": "WM_DEBUG_APP_TRANSITIONS",
       "at": "com\/android\/server\/wm\/AppTransitionController.java"
     },
-    "2114149926": {
-      "message": "Not removing %s because app died while it's visible",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_ADD_REMOVE",
-      "at": "com\/android\/server\/wm\/WindowState.java"
-    },
     "2117696413": {
       "message": "moveTaskToFront: moving taskId=%d",
       "level": "DEBUG",
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index 8e2a59c..0563519 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -251,13 +251,16 @@
     <alias name="courier new" to="serif-monospace" />
 
     <family name="casual">
-        <font weight="400" style="normal">ComingSoon.ttf</font>
+        <font weight="400" style="normal" postScriptName="ComingSoon-Regular">ComingSoon.ttf</font>
     </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/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml b/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml
index 298ad30..8d1da0f7 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml
@@ -63,11 +63,11 @@
             android:tint="@color/bubbles_icon_tint"/>
 
         <TextView
+            android:id="@+id/bubble_manage_menu_dont_bubble_text"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_marginStart="16dp"
-            android:textAppearance="@*android:style/TextAppearance.DeviceDefault"
-            android:text="@string/bubbles_dont_bubble_conversation" />
+            android:textAppearance="@*android:style/TextAppearance.DeviceDefault" />
 
     </LinearLayout>
 
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 3082962..9f6cf79 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -146,6 +146,8 @@
     <string name="bubbles_app_settings"><xliff:g id="notification_title" example="Android Messages">%1$s</xliff:g> settings</string>
     <!-- Text used for the bubble dismiss area. Bubbles dragged to, or flung towards, this area will go away. [CHAR LIMIT=30] -->
     <string name="bubble_dismiss_text">Dismiss bubble</string>
+    <!-- Button text to stop an app from bubbling [CHAR LIMIT=60]-->
+    <string name="bubbles_dont_bubble">Don\u2019t bubble</string>
     <!-- Button text to stop a conversation from bubbling [CHAR LIMIT=60]-->
     <string name="bubbles_dont_bubble_conversation">Don\u2019t bubble conversation</string>
     <!-- Title text for the bubbles feature education cling shown when a bubble is on screen for the first time. [CHAR LIMIT=60]-->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
index 22b841a..913239f7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
@@ -75,7 +75,7 @@
                 };
         mWaitingAnimation = false;
         try {
-            mRunner.onAnimationStart(TRANSIT_OLD_UNSET, apps, wallpapers,
+            getRunner().onAnimationStart(TRANSIT_OLD_UNSET, apps, wallpapers,
                     nonApps, callback);
         } catch (RemoteException e) {
             Log.w(TAG, "Failed call onAnimationStart", e);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TEST_MAPPING b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TEST_MAPPING
new file mode 100644
index 0000000..837d5ff
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TEST_MAPPING
@@ -0,0 +1,32 @@
+{
+  "presubmit": [
+    {
+      "name": "WMShellUnitTests",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "include-filter": "com.android.wm.shell.back"
+        }
+      ]
+    },
+    {
+      "name": "CtsWindowManagerDeviceTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "include-filter": "android.server.wm.BackGestureInvokedTest"
+        },
+        {
+          "include-filter": "android.server.wm.BackNavigationTests"
+        },
+        {
+          "include-filter": "android.server.wm.OnBackInvokedCallbackGestureTest"
+        }
+      ]
+    }
+  ]
+}
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..9ccd6eb 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
@@ -229,6 +229,7 @@
                     options.setLaunchedFromBubble(true);
                     options.setPendingIntentBackgroundActivityStartMode(
                             MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+                    options.setPendingIntentBackgroundActivityLaunchAllowedByPermission(true);
 
                     Intent fillInIntent = new Intent();
                     // Apply flags to make behaviour match documentLaunchMode=always.
@@ -236,12 +237,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/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 0b947c8..deb4fd5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -844,6 +844,8 @@
     private DismissView mDismissView;
 
     private ViewGroup mManageMenu;
+    private TextView mManageDontBubbleText;
+    private ViewGroup mManageSettingsView;
     private ImageView mManageSettingsIcon;
     private TextView mManageSettingsText;
     private boolean mShowingManage = false;
@@ -1217,7 +1219,11 @@
                     mUnbubbleConversationCallback.accept(mBubbleData.getSelectedBubble().getKey());
                 });
 
-        mManageMenu.findViewById(R.id.bubble_manage_menu_settings_container).setOnClickListener(
+        mManageDontBubbleText = mManageMenu
+                .findViewById(R.id.bubble_manage_menu_dont_bubble_text);
+
+        mManageSettingsView = mManageMenu.findViewById(R.id.bubble_manage_menu_settings_container);
+        mManageSettingsView.setOnClickListener(
                 view -> {
                     showManageMenu(false /* show */);
                     final BubbleViewProvider bubble = mBubbleData.getSelectedBubble();
@@ -2868,10 +2874,19 @@
         // name and icon.
         if (show) {
             final Bubble bubble = mBubbleData.getBubbleInStackWithKey(mExpandedBubble.getKey());
-            if (bubble != null) {
+            if (bubble != null && !bubble.isAppBubble()) {
+                // Setup options for non app bubbles
+                mManageDontBubbleText.setText(R.string.bubbles_dont_bubble_conversation);
                 mManageSettingsIcon.setImageBitmap(bubble.getRawAppBadge());
                 mManageSettingsText.setText(getResources().getString(
                         R.string.bubbles_app_settings, bubble.getAppName()));
+                mManageSettingsView.setVisibility(VISIBLE);
+            } else {
+                // Setup options for app bubbles
+                mManageDontBubbleText.setText(R.string.bubbles_dont_bubble);
+                // App bubbles are not notification based
+                // so we don't show the option to go to notification settings
+                mManageSettingsView.setVisibility(GONE);
             }
         }
 
@@ -2936,6 +2951,15 @@
         }
     }
 
+    /**
+     * Checks whether manage menu notification settings action is available and visible
+     * Used for testing
+     */
+    @VisibleForTesting
+    public boolean isManageMenuSettingsVisible() {
+        return mManageSettingsView != null && mManageSettingsView.getVisibility() == VISIBLE;
+    }
+
     private void updateExpandedBubble() {
         if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "updateExpandedBubble()");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/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/kidsmode/KidsModeTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
index 2d84d21..318a49a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
@@ -21,6 +21,8 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import android.app.ActivityManager;
@@ -33,6 +35,7 @@
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
+import android.view.Display;
 import android.view.InsetsSource;
 import android.view.InsetsState;
 import android.view.SurfaceControl;
@@ -44,6 +47,7 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
@@ -80,6 +84,12 @@
     private final DisplayController mDisplayController;
     private final DisplayInsetsController mDisplayInsetsController;
 
+    /**
+     * The value of the {@link R.bool.config_reverseDefaultRotation} property which defines how
+     * {@link Display#getRotation} values are mapped to screen orientations
+     */
+    private final boolean mReverseDefaultRotationEnabled;
+
     @VisibleForTesting
     ActivityManager.RunningTaskInfo mLaunchRootTask;
     @VisibleForTesting
@@ -188,6 +198,8 @@
         mDisplayInsetsController = displayInsetsController;
         mKidsModeSettingsObserver = kidsModeSettingsObserver;
         shellInit.addInitCallback(this::onInit, this);
+        mReverseDefaultRotationEnabled = context.getResources().getBoolean(
+                R.bool.config_reverseDefaultRotation);
     }
 
     public KidsModeTaskOrganizer(
@@ -211,6 +223,8 @@
         mDisplayController = displayController;
         mDisplayInsetsController = displayInsetsController;
         shellInit.addInitCallback(this::onInit, this);
+        mReverseDefaultRotationEnabled = context.getResources().getBoolean(
+                R.bool.config_reverseDefaultRotation);
     }
 
     /**
@@ -294,7 +308,14 @@
         // Needed since many Kids apps aren't optimised to support both orientations and it will be
         // hard for kids to understand the app compat mode.
         // TODO(229961548): Remove ignoreOrientationRequest exception for Kids Mode once possible.
-        setIsIgnoreOrientationRequestDisabled(true);
+        if (mReverseDefaultRotationEnabled) {
+            setOrientationRequestPolicy(/* isIgnoreOrientationRequestDisabled */ true,
+                    /* fromOrientations */ new int[]{SCREEN_ORIENTATION_REVERSE_LANDSCAPE},
+                    /* toOrientations */ new int[]{SCREEN_ORIENTATION_LANDSCAPE});
+        } else {
+            setOrientationRequestPolicy(/* isIgnoreOrientationRequestDisabled */ true,
+                    /* fromOrientations */ null, /* toOrientations */ null);
+        }
         final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(DEFAULT_DISPLAY);
         if (displayLayout != null) {
             mDisplayWidth = displayLayout.width();
@@ -315,7 +336,8 @@
 
     @VisibleForTesting
     void disable() {
-        setIsIgnoreOrientationRequestDisabled(false);
+        setOrientationRequestPolicy(/* isIgnoreOrientationRequestDisabled */ false,
+                /* fromOrientations */ null, /* toOrientations */ null);
         mDisplayInsetsController.removeInsetsChangedListener(DEFAULT_DISPLAY,
                 mOnInsetsChangedListener);
         mDisplayController.removeDisplayWindowListener(mOnDisplaysChangedListener);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
index d961d86..78de5f3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
@@ -75,4 +75,9 @@
      * Sets the height and visibility of the Launcher keep clear area.
      */
     oneway void setLauncherKeepClearAreaHeight(boolean visible, int height) = 6;
+
+    /**
+     * Sets the app icon size in pixel used by Launcher
+     */
+     oneway void setLauncherAppIconSize(int iconSizePx) = 7;
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index fe8ede6..1187126 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -371,10 +371,11 @@
                     new PipContentOverlay.PipSnapshotOverlay(snapshot, sourceRectHint));
         }
 
-        void setAppIconContentOverlay(Context context, Rect bounds, ActivityInfo activityInfo) {
+        void setAppIconContentOverlay(Context context, Rect bounds, ActivityInfo activityInfo,
+                int appIconSizePx) {
             reattachContentOverlay(
                     new PipContentOverlay.PipAppIconOverlay(context, bounds,
-                            () -> new IconProvider(context).getIcon(activityInfo)));
+                            new IconProvider(context).getIcon(activityInfo), appIconSizePx));
         }
 
         private void reattachContentOverlay(PipContentOverlay overlay) {
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 f08742d..9a775df 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
@@ -86,6 +86,7 @@
     private int mStashedState = STASH_TYPE_NONE;
     private int mStashOffset;
     private @Nullable PipReentryState mPipReentryState;
+    private final LauncherState mLauncherState = new LauncherState();
     private final @Nullable PipSizeSpecHandler mPipSizeSpecHandler;
     private @Nullable ComponentName mLastPipComponentName;
     private final @NonNull MotionBoundsState mMotionBoundsState = new MotionBoundsState();
@@ -482,6 +483,10 @@
         mOnPipExclusionBoundsChangeCallbacks.remove(onPipExclusionBoundsChangeCallback);
     }
 
+    public LauncherState getLauncherState() {
+        return mLauncherState;
+    }
+
     /** Source of truth for the current bounds of PIP that may be in motion. */
     public static class MotionBoundsState {
         /** The bounds used when PIP is in motion (e.g. during a drag or animation) */
@@ -534,6 +539,25 @@
         }
     }
 
+    /** Data class for Launcher state. */
+    public static final class LauncherState {
+        private int mAppIconSizePx;
+
+        public void setAppIconSizePx(int appIconSizePx) {
+            mAppIconSizePx = appIconSizePx;
+        }
+
+        public int getAppIconSizePx() {
+            return mAppIconSizePx;
+        }
+
+        void dump(PrintWriter pw, String prefix) {
+            final String innerPrefix = prefix + "    ";
+            pw.println(prefix + LauncherState.class.getSimpleName());
+            pw.println(innerPrefix + "getAppIconSizePx=" + getAppIconSizePx());
+        }
+    }
+
     static final class PipReentryState {
         private static final String TAG = PipReentryState.class.getSimpleName();
 
@@ -587,6 +611,7 @@
         } else {
             mPipReentryState.dump(pw, innerPrefix);
         }
+        mLauncherState.dump(pw, innerPrefix);
         mMotionBoundsState.dump(pw, innerPrefix);
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
index d228dfb..9fa57ca 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
@@ -32,8 +32,6 @@
 import android.view.SurfaceSession;
 import android.window.TaskSnapshot;
 
-import java.util.function.Supplier;
-
 /**
  * Represents the content overlay used during the entering PiP animation.
  */
@@ -176,9 +174,8 @@
     /** A {@link PipContentOverlay} shows app icon on solid color background. */
     public static final class PipAppIconOverlay extends PipContentOverlay {
         private static final String TAG = PipAppIconOverlay.class.getSimpleName();
-        // Align with the practical / reasonable launcher:iconImageSize as in
-        // vendor/unbundled_google/packages/NexusLauncher/res/xml/device_profiles.xml
-        private static final int APP_ICON_SIZE_DP = 66;
+        // The maximum size for app icon in pixel.
+        private static final int MAX_APP_ICON_SIZE_DP = 72;
 
         private final Context mContext;
         private final int mAppIconSizePx;
@@ -188,14 +185,16 @@
 
         private Bitmap mBitmap;
 
-        public PipAppIconOverlay(Context context, Rect appBounds, Supplier<Drawable> iconSupplier) {
+        public PipAppIconOverlay(Context context, Rect appBounds,
+                Drawable appIcon, int appIconSizePx) {
             mContext = context;
-            mAppIconSizePx = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, APP_ICON_SIZE_DP,
-                    context.getResources().getDisplayMetrics());
+            final int maxAppIconSizePx = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP,
+                    MAX_APP_ICON_SIZE_DP, context.getResources().getDisplayMetrics());
+            mAppIconSizePx = Math.min(maxAppIconSizePx, appIconSizePx);
             mAppBounds = new Rect(appBounds);
             mBitmap = Bitmap.createBitmap(appBounds.width(), appBounds.height(),
                     Bitmap.Config.ARGB_8888);
-            prepareAppIconOverlay(iconSupplier);
+            prepareAppIconOverlay(appIcon);
             mLeash = new SurfaceControl.Builder(new SurfaceSession())
                     .setCallsite(TAG)
                     .setName(LAYER_NAME)
@@ -238,7 +237,7 @@
             }
         }
 
-        private void prepareAppIconOverlay(Supplier<Drawable> iconSupplier) {
+        private void prepareAppIconOverlay(Drawable appIcon) {
             final Canvas canvas = new Canvas();
             canvas.setBitmap(mBitmap);
             final TypedArray ta = mContext.obtainStyledAttributes(new int[] {
@@ -252,7 +251,6 @@
             } finally {
                 ta.recycle();
             }
-            final Drawable appIcon = iconSupplier.get();
             final Rect appIconBounds = new Rect(
                     mAppBounds.centerX() - mAppIconSizePx / 2,
                     mAppBounds.centerY() - mAppIconSizePx / 2,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index d5b9c5e..52f5a8c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -1608,7 +1608,8 @@
                 if (SystemProperties.getBoolean(
                         "persist.wm.debug.enable_pip_app_icon_overlay", true)) {
                     animator.setAppIconContentOverlay(
-                            mContext, currentBounds, mTaskInfo.topActivityInfo);
+                            mContext, currentBounds, mTaskInfo.topActivityInfo,
+                            mPipBoundsState.getLauncherState().getAppIconSizePx());
                 } else {
                     animator.setColorContentOverlay(mContext);
                 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 45bb73b..49a27c5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -808,7 +808,8 @@
                         "persist.wm.debug.enable_pip_app_icon_overlay", true)
                         && hasTopActivityInfo) {
                     animator.setAppIconContentOverlay(
-                            mContext, currentBounds, taskInfo.topActivityInfo);
+                            mContext, currentBounds, taskInfo.topActivityInfo,
+                            mPipBoundsState.getLauncherState().getAppIconSizePx());
                 } else {
                     animator.setColorContentOverlay(mContext);
                 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index be9b529..9ee4b65 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -104,7 +104,6 @@
 import com.android.wm.shell.transition.Transitions;
 
 import java.io.PrintWriter;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
@@ -119,6 +118,8 @@
         UserChangeListener {
     private static final String TAG = "PipController";
 
+    private static final String LAUNCHER_KEEP_CLEAR_AREA_TAG = "hotseat";
+
     private static final long PIP_KEEP_CLEAR_AREAS_DELAY =
             SystemProperties.getLong("persist.wm.debug.pip_keep_clear_areas_delay", 200);
 
@@ -192,7 +193,7 @@
             Rect destBounds = mPipKeepClearAlgorithm.adjust(mPipBoundsState,
                     mPipBoundsAlgorithm);
             // only move if the bounds are actually different
-            if (destBounds != mPipBoundsState.getBounds()) {
+            if (!destBounds.equals(mPipBoundsState.getBounds())) {
                 if (mPipTransitionState.hasEnteredPip()) {
                     // if already in PiP, schedule separate animation
                     mPipTaskOrganizer.scheduleAnimateResizePip(destBounds,
@@ -934,15 +935,17 @@
                     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);
         }
     }
 
+    private void setLauncherAppIconSize(int iconSizePx) {
+        mPipBoundsState.getLauncherState().setAppIconSizePx(iconSizePx);
+    }
+
     private void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {
         mOnIsInPipStateChangedListener = callback;
         if (mOnIsInPipStateChangedListener != null) {
@@ -1287,26 +1290,26 @@
                 overlay.setUnreleasedWarningCallSite("PipController.stopSwipePipToHome");
             }
             executeRemoteCallWithTaskPermission(mController, "stopSwipePipToHome",
-                    (controller) -> {
-                        controller.stopSwipePipToHome(taskId, componentName, destinationBounds,
-                                overlay);
-                    });
+                    (controller) -> controller.stopSwipePipToHome(
+                            taskId, componentName, destinationBounds, overlay));
         }
 
         @Override
         public void setShelfHeight(boolean visible, int height) {
             executeRemoteCallWithTaskPermission(mController, "setShelfHeight",
-                    (controller) -> {
-                        controller.setShelfHeight(visible, height);
-                    });
+                    (controller) -> controller.setShelfHeight(visible, height));
         }
 
         @Override
         public void setLauncherKeepClearAreaHeight(boolean visible, int height) {
             executeRemoteCallWithTaskPermission(mController, "setLauncherKeepClearAreaHeight",
-                    (controller) -> {
-                        controller.setLauncherKeepClearAreaHeight(visible, height);
-                    });
+                    (controller) -> controller.setLauncherKeepClearAreaHeight(visible, height));
+        }
+
+        @Override
+        public void setLauncherAppIconSize(int iconSizePx) {
+            executeRemoteCallWithTaskPermission(mController, "setLauncherAppIconSize",
+                    (controller) -> controller.setLauncherAppIconSize(iconSizePx));
         }
 
         @Override
@@ -1324,9 +1327,7 @@
         @Override
         public void setPipAnimationTypeToAlpha() {
             executeRemoteCallWithTaskPermission(mController, "setPipAnimationTypeToAlpha",
-                    (controller) -> {
-                        controller.setPinnedStackAnimationType(ANIM_TYPE_ALPHA);
-                    });
+                    (controller) -> controller.setPinnedStackAnimationType(ANIM_TYPE_ALPHA));
         }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index 4b5ad1d..e09c3c9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -68,6 +68,7 @@
 
     DismissTransition mPendingDismiss = null;
     TransitSession mPendingEnter = null;
+    TransitSession mPendingRecent = null;
     TransitSession mPendingResize = null;
 
     private IBinder mAnimatingTransition = null;
@@ -259,6 +260,10 @@
         return mPendingEnter != null && mPendingEnter.mTransition == transition;
     }
 
+    boolean isPendingRecent(IBinder transition) {
+        return mPendingRecent != null && mPendingRecent.mTransition == transition;
+    }
+
     boolean isPendingDismiss(IBinder transition) {
         return mPendingDismiss != null && mPendingDismiss.mTransition == transition;
     }
@@ -271,6 +276,8 @@
     private TransitSession getPendingTransition(IBinder transition) {
         if (isPendingEnter(transition)) {
             return mPendingEnter;
+        } else if (isPendingRecent(transition)) {
+            return mPendingRecent;
         } else if (isPendingDismiss(transition)) {
             return mPendingDismiss;
         } else if (isPendingResize(transition)) {
@@ -354,10 +361,32 @@
                 + " deduced Resize split screen");
     }
 
+    void setRecentTransition(@NonNull IBinder transition,
+            @Nullable RemoteTransition remoteTransition,
+            @Nullable TransitionFinishedCallback finishCallback) {
+        mPendingRecent = new TransitSession(transition, null /* consumedCb */, finishCallback);
+
+        if (remoteTransition != null) {
+            // Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff)
+            mPendingRemoteHandler = new OneShotRemoteHandler(
+                    mTransitions.getMainExecutor(), remoteTransition);
+            mPendingRemoteHandler.setTransition(transition);
+        }
+
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  splitTransition "
+                + " deduced Enter recent panel");
+    }
+
     void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t,
             IBinder mergeTarget, Transitions.TransitionFinishCallback finishCallback) {
         if (mergeTarget != mAnimatingTransition) return;
 
+        if (isPendingEnter(transition) && isPendingRecent(mergeTarget)) {
+            // Since there's an entering transition merged, recent transition no longer
+            // need to handle entering split screen after the transition finished.
+            mPendingRecent.setFinishedCallback(null);
+        }
+
         if (mActiveRemoteHandler != null) {
             mActiveRemoteHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
         } else {
@@ -394,6 +423,10 @@
         } else if (isPendingDismiss(transition)) {
             mPendingDismiss.onConsumed(aborted);
             mPendingDismiss = null;
+        } else if (isPendingRecent(transition)) {
+            mPendingRecent.onConsumed(aborted);
+            mPendingRecent = null;
+            mPendingRemoteHandler = null;
         } else if (isPendingResize(transition)) {
             mPendingResize.onConsumed(aborted);
             mPendingResize = null;
@@ -407,6 +440,9 @@
         if (isPendingEnter(mAnimatingTransition)) {
             mPendingEnter.onFinished(wct, mFinishTransaction);
             mPendingEnter = null;
+        } else if (isPendingRecent(mAnimatingTransition)) {
+            mPendingRecent.onFinished(wct, mFinishTransaction);
+            mPendingRecent = null;
         } else if (isPendingDismiss(mAnimatingTransition)) {
             mPendingDismiss.onFinished(wct, mFinishTransaction);
             mPendingDismiss = null;
@@ -516,7 +552,7 @@
     }
 
     /** Calls when the transition finished. */
-    public interface TransitionFinishedCallback {
+    interface TransitionFinishedCallback {
         void onFinished(WindowContainerTransaction wct, SurfaceControl.Transaction t);
     }
 
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 9d1a7ea..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
@@ -2238,10 +2238,16 @@
                 }
             } else if (isOpening && inFullscreen) {
                 final int activityType = triggerTask.getActivityType();
-                if (activityType == ACTIVITY_TYPE_HOME
-                        || activityType == ACTIVITY_TYPE_RECENTS) {
-                    // starting recents, so don't handle this.
-                    return null;
+                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 {
@@ -2370,6 +2376,8 @@
         if (mSplitTransitions.isPendingEnter(transition)) {
             shouldAnimate = startPendingEnterAnimation(
                     transition, info, startTransaction, finishTransaction);
+        } else if (mSplitTransitions.isPendingRecent(transition)) {
+            onRecentsInSplitAnimationStart(startTransaction);
         } else if (mSplitTransitions.isPendingDismiss(transition)) {
             shouldAnimate = startPendingDismissAnimation(
                     mSplitTransitions.mPendingDismiss, info, startTransaction, finishTransaction);
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 b9f6a01..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
@@ -180,13 +180,15 @@
                 && request.getTriggerTask() != null
                 && request.getTriggerTask().getWindowingMode() == WINDOWING_MODE_FULLSCREEN
                 && (request.getTriggerTask().getActivityType() == ACTIVITY_TYPE_HOME
-                        || request.getTriggerTask().getActivityType() == ACTIVITY_TYPE_RECENTS)) {
+                        || 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) {
-                // fall through -- it will probably be picked-up by normal split handler.
+                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(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 0a9c331..8cb575c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -103,6 +103,7 @@
                     null /* hostInputToken */,
                     FLAG_NOT_FOCUSABLE,
                     PRIVATE_FLAG_TRUSTED_OVERLAY,
+                    0 /* inputFeatures */,
                     TYPE_APPLICATION,
                     null /* windowToken */,
                     mFocusGrantToken,
@@ -208,6 +209,7 @@
                     mDecorationSurface,
                     FLAG_NOT_FOCUSABLE,
                     PRIVATE_FLAG_TRUSTED_OVERLAY,
+                    0 /* inputFeatures */,
                     touchRegion);
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
index 11bb0cc..f52e877 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
@@ -74,10 +74,18 @@
 
     @Presubmit
     @Test
-    override fun pipAppLayerOrOverlayAlwaysVisible() {
+    override fun pipAppLayerAlwaysVisible() {
         // pip layer in gesture nav will disappear during transition
         Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
-        super.pipAppLayerOrOverlayAlwaysVisible()
+        super.pipAppLayerAlwaysVisible()
+    }
+
+    @Presubmit
+    @Test
+    override fun pipOverlayLayerAppearThenDisappear() {
+        // no overlay in gesture nav for non-auto enter PiP transition
+        Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
+        super.pipOverlayLayerAppearThenDisappear()
     }
 
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
index 3272254..e40e5ea 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
@@ -43,13 +43,23 @@
     /** Checks [pipApp] layer remains visible throughout the animation */
     @Presubmit
     @Test
-    open fun pipAppLayerOrOverlayAlwaysVisible() {
+    open fun pipAppLayerAlwaysVisible() {
         flicker.assertLayers {
             this.isVisible(pipApp)
+        }
+    }
+
+    /** Checks the content overlay appears then disappears during the animation */
+    @Presubmit
+    @Test
+    open fun pipOverlayLayerAppearThenDisappear() {
+        val overlay = ComponentNameMatcher.PIP_CONTENT_OVERLAY
+        flicker.assertLayers {
+            this.notContains(overlay)
                 .then()
-                .isVisible(ComponentNameMatcher.PIP_CONTENT_OVERLAY)
+                .contains(overlay)
                 .then()
-                .isVisible(pipApp)
+                .notContains(overlay)
         }
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp
index 2ac1dc0..57a6981 100644
--- a/libs/WindowManager/Shell/tests/unittest/Android.bp
+++ b/libs/WindowManager/Shell/tests/unittest/Android.bp
@@ -69,6 +69,8 @@
         enabled: false,
     },
 
+    test_suites: ["device-tests"],
+
     platform_apis: true,
     certificate: "platform",
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 6dae479..169b9bd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -411,6 +411,53 @@
         verify(mAnimatorCallback, never()).onBackInvoked();
     }
 
+    @Test
+    public void testBackToActivity() throws RemoteException {
+        final CrossActivityAnimation animation = new CrossActivityAnimation(mContext,
+                mAnimationBackground);
+        verifySystemBackBehavior(
+                BackNavigationInfo.TYPE_CROSS_ACTIVITY, animation.mBackAnimationRunner);
+    }
+
+    @Test
+    public void testBackToTask() throws RemoteException {
+        final CrossTaskBackAnimation animation = new CrossTaskBackAnimation(mContext,
+                mAnimationBackground);
+        verifySystemBackBehavior(
+                BackNavigationInfo.TYPE_CROSS_TASK, animation.mBackAnimationRunner);
+    }
+
+    private void verifySystemBackBehavior(int type, BackAnimationRunner animation)
+            throws RemoteException {
+        final BackAnimationRunner animationRunner = spy(animation);
+        final IRemoteAnimationRunner runner = spy(animationRunner.getRunner());
+        final IOnBackInvokedCallback callback = spy(animationRunner.getCallback());
+
+        // Set up the monitoring objects.
+        doNothing().when(runner).onAnimationStart(anyInt(), any(), any(), any(), any());
+        doReturn(runner).when(animationRunner).getRunner();
+        doReturn(callback).when(animationRunner).getCallback();
+
+        mController.registerAnimation(type, animationRunner);
+
+        createNavigationInfo(type, true);
+
+        doMotionEvent(MotionEvent.ACTION_DOWN, 0);
+
+        // Check that back start and progress is dispatched when first move.
+        doMotionEvent(MotionEvent.ACTION_MOVE, 100);
+
+        simulateRemoteAnimationStart(type);
+
+        verify(callback).onBackStarted(any(BackMotionEvent.class));
+        verify(animationRunner).startAnimation(any(), any(), any(), any());
+
+        // Check that back invocation is dispatched.
+        mController.setTriggerBack(true);   // Fake trigger back
+        doMotionEvent(MotionEvent.ACTION_UP, 0);
+        verify(callback).onBackInvoked();
+    }
+
     private void doMotionEvent(int actionDown, int coordinate) {
         mController.onMotionEvent(
                 coordinate, coordinate,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java
index ecfb427..58e91cb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java
@@ -31,6 +31,7 @@
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.pm.ParceledListSlice;
+import android.content.res.Resources;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -77,6 +78,7 @@
     @Mock private ShellInit mShellInit;
     @Mock private ShellCommandHandler mShellCommandHandler;
     @Mock private DisplayInsetsController mDisplayInsetsController;
+    @Mock private Resources mResources;
 
     KidsModeTaskOrganizer mOrganizer;
 
@@ -89,10 +91,12 @@
         } catch (RemoteException e) {
         }
         // NOTE: KidsModeTaskOrganizer should have a null CompatUIController.
-        mOrganizer = spy(new KidsModeTaskOrganizer(mContext, mShellInit, mShellCommandHandler,
-                mTaskOrganizerController, mSyncTransactionQueue, mDisplayController,
-                mDisplayInsetsController, Optional.empty(), Optional.empty(), mObserver,
-                mTestExecutor, mHandler));
+        doReturn(mResources).when(mContext).getResources();
+        final KidsModeTaskOrganizer kidsModeTaskOrganizer = new KidsModeTaskOrganizer(mContext,
+                mShellInit, mShellCommandHandler, mTaskOrganizerController, mSyncTransactionQueue,
+                mDisplayController, mDisplayInsetsController, Optional.empty(), Optional.empty(),
+                mObserver, mTestExecutor, mHandler);
+        mOrganizer = spy(kidsModeTaskOrganizer);
         doReturn(mTransaction).when(mOrganizer).getWindowContainerTransaction();
         doReturn(new InsetsState()).when(mDisplayController).getInsetsState(DEFAULT_DISPLAY);
     }
@@ -112,6 +116,8 @@
         verify(mOrganizer, times(1)).registerOrganizer();
         verify(mOrganizer, times(1)).createRootTask(
                 eq(DEFAULT_DISPLAY), eq(WINDOWING_MODE_FULLSCREEN), eq(mOrganizer.mCookie));
+        verify(mOrganizer, times(1))
+                .setOrientationRequestPolicy(eq(true), any(), any());
 
         final ActivityManager.RunningTaskInfo rootTask = createTaskInfo(12,
                 WINDOWING_MODE_FULLSCREEN, mOrganizer.mCookie);
@@ -132,10 +138,11 @@
         doReturn(false).when(mObserver).isEnabled();
         mOrganizer.updateKidsModeState();
 
-
         verify(mOrganizer, times(1)).disable();
         verify(mOrganizer, times(1)).unregisterOrganizer();
         verify(mOrganizer, times(1)).deleteRootTask(rootTask.token);
+        verify(mOrganizer, times(1))
+                .setOrientationRequestPolicy(eq(false), any(), any());
         assertThat(mOrganizer.mLaunchRootLeash).isNull();
         assertThat(mOrganizer.mLaunchRootTask).isNull();
     }
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 2edec7d..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
@@ -259,7 +259,8 @@
                 .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);
         // Don't handle recents opening
@@ -292,7 +293,8 @@
                 .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);
         // Don't handle recents opening
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 c398405..c3ad767 100644
--- a/libs/input/MouseCursorController.cpp
+++ b/libs/input/MouseCursorController.cpp
@@ -53,8 +53,6 @@
     mLocked.resolvedPointerType = PointerIconStyle::TYPE_NOT_SPECIFIED;
 
     mLocked.resourcesLoaded = false;
-
-    mLocked.buttonState = 0;
 }
 
 MouseCursorController::~MouseCursorController() {
@@ -95,22 +93,6 @@
     setPositionLocked(mLocked.pointerX + deltaX, mLocked.pointerY + deltaY);
 }
 
-void MouseCursorController::setButtonState(int32_t buttonState) {
-#if DEBUG_MOUSE_CURSOR_UPDATES
-    ALOGD("Set button state 0x%08x", buttonState);
-#endif
-    std::scoped_lock lock(mLock);
-
-    if (mLocked.buttonState != buttonState) {
-        mLocked.buttonState = buttonState;
-    }
-}
-
-int32_t MouseCursorController::getButtonState() const {
-    std::scoped_lock lock(mLock);
-    return mLocked.buttonState;
-}
-
 void MouseCursorController::setPosition(float x, float y) {
 #if DEBUG_MOUSE_CURSOR_UPDATES
     ALOGD("Set pointer position to x=%0.3f, y=%0.3f", x, y);
diff --git a/libs/input/MouseCursorController.h b/libs/input/MouseCursorController.h
index 26be2a8..00dc085 100644
--- a/libs/input/MouseCursorController.h
+++ b/libs/input/MouseCursorController.h
@@ -45,8 +45,6 @@
 
     std::optional<FloatRect> getBounds() const;
     void move(float deltaX, float deltaY);
-    void setButtonState(int32_t buttonState);
-    int32_t getButtonState() const;
     void setPosition(float x, float y);
     FloatPoint getPosition() const;
     int32_t getDisplayId() const;
@@ -96,8 +94,6 @@
         PointerIconStyle requestedPointerType;
         PointerIconStyle resolvedPointerType;
 
-        int32_t buttonState;
-
         bool animating{false};
 
     } mLocked GUARDED_BY(mLock);
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index 544edc2..88e3519 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -136,14 +136,6 @@
     mCursorController.move(transformed.x, transformed.y);
 }
 
-void PointerController::setButtonState(int32_t buttonState) {
-    mCursorController.setButtonState(buttonState);
-}
-
-int32_t PointerController::getButtonState() const {
-    return mCursorController.getButtonState();
-}
-
 void PointerController::setPosition(float x, float y) {
     const int32_t displayId = mCursorController.getDisplayId();
     vec2 transformed;
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index 6d3557c..ca14b6e 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -50,21 +50,19 @@
 
     ~PointerController() override;
 
-    virtual std::optional<FloatRect> getBounds() const;
-    virtual void move(float deltaX, float deltaY);
-    virtual void setButtonState(int32_t buttonState);
-    virtual int32_t getButtonState() const;
-    virtual void setPosition(float x, float y);
-    virtual FloatPoint getPosition() const;
-    virtual int32_t getDisplayId() const;
-    virtual void fade(Transition transition);
-    virtual void unfade(Transition transition);
-    virtual void setDisplayViewport(const DisplayViewport& viewport);
+    std::optional<FloatRect> getBounds() const override;
+    void move(float deltaX, float deltaY) override;
+    void setPosition(float x, float y) override;
+    FloatPoint getPosition() const override;
+    int32_t getDisplayId() const override;
+    void fade(Transition transition) override;
+    void unfade(Transition transition) override;
+    void setDisplayViewport(const DisplayViewport& viewport) override;
 
-    virtual void setPresentation(Presentation presentation);
-    virtual void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
-                          BitSet32 spotIdBits, int32_t displayId);
-    virtual void clearSpots();
+    void setPresentation(Presentation presentation) override;
+    void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
+                  BitSet32 spotIdBits, int32_t displayId) override;
+    void clearSpots() override;
 
     void updatePointerIcon(PointerIconStyle iconId);
     void setCustomPointerIcon(const SpriteIcon& icon);
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index f9b6ce0..8ab7159 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -6251,7 +6251,6 @@
      * Volume behavior for an audio device where no software attenuation is applied, and
      *     the volume is kept synchronized between the host and the device itself through a
      *     device-specific protocol such as BT AVRCP.
-     * @see #setDeviceVolumeBehavior(AudioDeviceAttributes, int)
      */
     @SystemApi
     public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE = 3;
@@ -6262,7 +6261,6 @@
      *     device-specific protocol (such as for hearing aids), based on the audio mode (e.g.
      *     normal vs in phone call).
      * @see #setMode(int)
-     * @see #setDeviceVolumeBehavior(AudioDeviceAttributes, int)
      */
     @SystemApi
     public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE = 4;
@@ -6272,6 +6270,11 @@
      * A variant of {@link #DEVICE_VOLUME_BEHAVIOR_ABSOLUTE} where the host cannot reliably set
      * the volume percentage of the audio device. Specifically, {@link #setStreamVolume} will have
      * no effect, or an unreliable effect.
+     *
+     * {@link #DEVICE_VOLUME_BEHAVIOR_FULL} will be returned instead by
+     * {@link #getDeviceVolumeBehavior} for target SDK versions before U.
+     *
+     * @see #RETURN_DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY
      */
     @SystemApi
     public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY = 5;
@@ -6313,18 +6316,27 @@
     public @interface AbsoluteDeviceVolumeBehavior {}
 
     /**
+     * Volume behaviors that can be set with {@link #setDeviceVolumeBehavior}.
      * @hide
-     * Throws IAE on an invalid volume behavior value
+     */
+    @IntDef({
+        DEVICE_VOLUME_BEHAVIOR_VARIABLE,
+        DEVICE_VOLUME_BEHAVIOR_FULL,
+        DEVICE_VOLUME_BEHAVIOR_FIXED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SettableDeviceVolumeBehavior {}
+
+    /**
+     * @hide
+     * Throws IAE on a non-settable volume behavior value
      * @param volumeBehavior behavior value to check
      */
-    public static void enforceValidVolumeBehavior(int volumeBehavior) {
+    public static void enforceSettableVolumeBehavior(int volumeBehavior) {
         switch (volumeBehavior) {
             case DEVICE_VOLUME_BEHAVIOR_VARIABLE:
             case DEVICE_VOLUME_BEHAVIOR_FULL:
             case DEVICE_VOLUME_BEHAVIOR_FIXED:
-            case DEVICE_VOLUME_BEHAVIOR_ABSOLUTE:
-            case DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE:
-            case DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY:
                 return;
             default:
                 throw new IllegalArgumentException("Illegal volume behavior " + volumeBehavior);
@@ -6334,11 +6346,8 @@
     /**
      * @hide
      * Sets the volume behavior for an audio output device.
-     * @see #DEVICE_VOLUME_BEHAVIOR_VARIABLE
-     * @see #DEVICE_VOLUME_BEHAVIOR_FULL
-     * @see #DEVICE_VOLUME_BEHAVIOR_FIXED
-     * @see #DEVICE_VOLUME_BEHAVIOR_ABSOLUTE
-     * @see #DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE
+     *
+     * @see SettableDeviceVolumeBehavior
      * @param device the device to be affected
      * @param deviceVolumeBehavior one of the device behaviors
      */
@@ -6348,10 +6357,10 @@
             Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED
     })
     public void setDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device,
-            @DeviceVolumeBehavior int deviceVolumeBehavior) {
+            @SettableDeviceVolumeBehavior int deviceVolumeBehavior) {
         // verify arguments (validity of device type is enforced in server)
         Objects.requireNonNull(device);
-        enforceValidVolumeBehavior(deviceVolumeBehavior);
+        enforceSettableVolumeBehavior(deviceVolumeBehavior);
         // communicate with service
         final IAudioService service = getService();
         try {
@@ -6752,7 +6761,7 @@
 
     /**
      * @hide
-     * Lower media volume to RS1
+     * Lower media volume to RS1 interval
      */
     public void lowerVolumeToRs1() {
         try {
@@ -6764,13 +6773,13 @@
 
     /**
      * @hide
-     * @return the RS2 value used for momentary exposure warnings
+     * @return the RS2 upper bound used for momentary exposure warnings
      */
     @TestApi
     @RequiresPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
     public float getRs2Value() {
         try {
-            return getService().getRs2Value();
+            return getService().getOutputRs2UpperBound();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -6778,13 +6787,13 @@
 
     /**
      * @hide
-     * Sets the RS2 value used for momentary exposure warnings
+     * Sets the RS2 upper bound used for momentary exposure warnings
      */
     @TestApi
     @RequiresPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
     public void setRs2Value(float rs2Value) {
         try {
-            getService().setRs2Value(rs2Value);
+            getService().setOutputRs2UpperBound(rs2Value);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index cebdc82..f9d4efe 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -296,10 +296,10 @@
     void lowerVolumeToRs1(String callingPackage);
 
     @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
-    float getRs2Value();
+    float getOutputRs2UpperBound();
 
     @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
-    oneway void setRs2Value(float rs2Value);
+    oneway void setOutputRs2UpperBound(float rs2Value);
 
     @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
     float getCsd();
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index 14c020e..901ea46 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -120,6 +120,7 @@
 
     // For TV Message
     void notifyTvMessage(in IBinder sessionToken, int type, in Bundle data, int userId);
+    void setTvMessageEnabled(in IBinder sessionToken, int type, boolean enabled, int userId);
 
     // For TV input hardware binding
     List<TvInputHardwareInfo> getHardwareList();
diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl
index a7bd8d3..5246f5c4 100644
--- a/media/java/android/media/tv/ITvInputSession.aidl
+++ b/media/java/android/media/tv/ITvInputSession.aidl
@@ -79,4 +79,5 @@
 
     // For TV messages
     void notifyTvMessage(int type, in Bundle data);
+    void setTvMessageEnabled(int type, boolean enabled);
 }
diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java
index 7946baee..3a990b3 100644
--- a/media/java/android/media/tv/ITvInputSessionWrapper.java
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -268,6 +268,7 @@
             case DO_SET_TV_MESSAGE_ENABLED: {
                 SomeArgs args = (SomeArgs) msg.obj;
                 mTvInputSessionImpl.setTvMessageEnabled((Integer) args.arg1, (Boolean) args.arg2);
+                args.recycle();
                 break;
             }
             case DO_REQUEST_AD: {
@@ -280,7 +281,7 @@
             }
             case DO_NOTIFY_TV_MESSAGE: {
                 SomeArgs args = (SomeArgs) msg.obj;
-                mTvInputSessionImpl.onTvMessageReceived((String) args.arg1, (Bundle) args.arg2);
+                mTvInputSessionImpl.onTvMessageReceived((Integer) args.arg1, (Bundle) args.arg2);
                 break;
             }
             default: {
@@ -474,6 +475,12 @@
         mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_NOTIFY_TV_MESSAGE, type, data));
     }
 
+    @Override
+    public void setTvMessageEnabled(int type, boolean enabled) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_SET_TV_MESSAGE_ENABLED, type,
+                enabled));
+    }
+
     private final class TvInputEventReceiver extends InputEventReceiver {
         TvInputEventReceiver(InputChannel inputChannel, Looper looper) {
             super(inputChannel, looper);
diff --git a/media/java/android/media/tv/OWNERS b/media/java/android/media/tv/OWNERS
index fa04293..0b022e5 100644
--- a/media/java/android/media/tv/OWNERS
+++ b/media/java/android/media/tv/OWNERS
@@ -1,6 +1,7 @@
 quxiangfang@google.com
 shubang@google.com
 hgchen@google.com
+qingxun@google.com
 
 # For android remote service
 per-file ITvRemoteServiceInput.aidl = file:/media/lib/tvremote/OWNERS
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index ef2b5a5..f344fd3 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -156,20 +156,51 @@
             "android.media.tv.TvInputManager.stream_id";
 
     /**
+     * This value for {@link #TV_MESSAGE_KEY_GROUP_ID} denotes that the message doesn't
+     * belong to any group.
+     */
+    public static final long TV_MESSAGE_GROUP_ID_NONE = -1;
+
+    /**
+     * This constant is used as a {@link Bundle} key for TV messages. This is used to
+     * optionally identify messages that belong together, such as headers and bodies
+     * of the same event. For messages that do not have a group, this value
+     * should be {@link #TV_MESSAGE_GROUP_ID_NONE}.
+     *
+     * <p> As -1 is a reserved value, -1 should not be used as a valid groupId.
+     *
+     * <p> Type: long
+     */
+    public static final String TV_MESSAGE_KEY_GROUP_ID =
+            "android.media.tv.TvInputManager.group_id";
+
+    /**
+     * This is a subtype for TV messages that can be potentially found as a value
+     * at {@link #TV_MESSAGE_KEY_SUBTYPE}. It identifies the subtype of the message
+     * as the watermarking format ATSC A/335.
+     */
+    public static final String TV_MESSAGE_SUBTYPE_WATERMARKING_A335 = "ATSC A/335";
+
+    /**
+     * This is a subtype for TV messages that can be potentially found as a value
+     * at {@link #TV_MESSAGE_KEY_SUBTYPE}. It identifies the subtype of the message
+     * as the CC format CTA 608-E.
+     */
+    public static final String TV_MESSAGE_SUBTYPE_CC_608E = "CTA 608-E";
+
+    /**
      * 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> To parse the raw data based on the subtype, please refer to the official
+     * documentation of the concerning subtype. For example, for the subtype
+     * {@link #TV_MESSAGE_SUBTYPE_WATERMARKING_A335}, the document for A/335 from the ATSC
+     * standard details how this data is formatted. Similarly, the subtype
+     * {@link #TV_MESSAGE_SUBTYPE_CC_608E} is documented in the ANSI/CTA standard for
+     * 608-E. These subtypes are examples of common formats for their respective uses
+     * and other subtypes may exist.
      *
      * <p> Type: String
      */
@@ -178,7 +209,7 @@
 
     /**
      * 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
+     * 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.
@@ -839,6 +870,7 @@
          * @param type The type of message received, such as {@link #TV_MESSAGE_TYPE_WATERMARK}
          * @param data The raw data of the message. The bundle keys are:
          *             {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
+         *             {@link TvInputManager#TV_MESSAGE_KEY_GROUP_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
@@ -3273,7 +3305,7 @@
         /**
          * Sends TV messages to the service for testing purposes
          */
-        public void notifyTvMessage(@NonNull @TvMessageType int type, @NonNull Bundle data) {
+        public void notifyTvMessage(int type, Bundle data) {
             try {
                 mService.notifyTvMessage(mToken, type, data, mUserId);
             } catch (RemoteException e) {
@@ -3282,6 +3314,17 @@
         }
 
         /**
+         * Sets whether the TV message of the specific type should be enabled.
+         */
+        public void setTvMessageEnabled(int type, boolean enabled) {
+            try {
+                mService.setTvMessageEnabled(mToken, type, enabled, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
          * Starts TV program recording in the current recording session.
          *
          * @param programUri The URI for the TV program to record as a hint, built by
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 4e380c4..85b02ad 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -1032,6 +1032,7 @@
          * {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK}
          * @param data The raw data of the message. The bundle keys are:
          *             {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
+         *             {@link TvInputManager#TV_MESSAGE_KEY_GROUP_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
@@ -1507,12 +1508,13 @@
          * {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK}
          * @param data The raw data of the message. The bundle keys are:
          *             {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
+         *             {@link TvInputManager#TV_MESSAGE_KEY_GROUP_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,
+        public void onTvMessage(@TvInputManager.TvMessageType int type,
                 @NonNull Bundle data) {
         }
 
@@ -2065,7 +2067,7 @@
             onAdBufferReady(buffer);
         }
 
-        void onTvMessageReceived(String type, Bundle data) {
+        void onTvMessageReceived(int type, Bundle data) {
             onTvMessage(type, data);
         }
 
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index 8a886832..a4e5fb6 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -745,6 +745,9 @@
      */
     public void setTvMessageEnabled(@TvInputManager.TvMessageType int type,
             boolean enabled) {
+        if (mSession != null) {
+            mSession.setTvMessageEnabled(type, enabled);
+        }
     }
 
     @Override
@@ -1256,6 +1259,7 @@
          *             {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK}
          * @param data The raw data of the message. The bundle keys are:
          *             {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
+         *             {@link TvInputManager#TV_MESSAGE_KEY_GROUP_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
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index 69ff9c6..06dfe4f 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -921,6 +921,7 @@
          * {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK}
          * @param data The raw data of the message. The bundle keys are:
          *             {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
+         *             {@link TvInputManager#TV_MESSAGE_KEY_GROUP_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
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index 80a1435..cbaf5e4 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -946,6 +946,7 @@
      * {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK}
      * @param data The raw data of the message. The bundle keys are:
      *             {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
+     *             {@link TvInputManager#TV_MESSAGE_KEY_GROUP_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
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/native/android/surface_control.cpp b/native/android/surface_control.cpp
index 904fa74..4b63fbf 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -628,14 +628,15 @@
     CHECK_NOT_NULL(aSurfaceControl);
 
     if (!isfinite(currentBufferRatio) || currentBufferRatio < 1.0f) {
-        ALOGE("Ignore setExtendedRangeBrightness, currentBufferRatio %f isn't finite or >= 1.0f",
-              currentBufferRatio);
+        LOG_ALWAYS_FATAL("setExtendedRangeBrightness, currentBufferRatio %f isn't finite or >= "
+                         "1.0f",
+                         currentBufferRatio);
         return;
     }
 
     if (!isfinite(desiredRatio) || desiredRatio < 1.0f) {
-        ALOGE("Ignore setExtendedRangeBrightness, desiredRatio %f isn't finite or >= 1.0f",
-              desiredRatio);
+        LOG_ALWAYS_FATAL("setExtendedRangeBrightness, desiredRatio %f isn't finite or >= 1.0f",
+                         desiredRatio);
         return;
     }
 
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/CompanionDeviceManager/res/drawable/btn_negative_multiple_devices.xml b/packages/CompanionDeviceManager/res/drawable/btn_negative_multiple_devices.xml
index ebe16a7..e6ac209 100644
--- a/packages/CompanionDeviceManager/res/drawable/btn_negative_multiple_devices.xml
+++ b/packages/CompanionDeviceManager/res/drawable/btn_negative_multiple_devices.xml
@@ -18,8 +18,7 @@
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
     <solid android:color="@android:color/transparent" />
-    <corners android:topLeftRadius="16dp" android:topRightRadius="16dp"
-             android:bottomLeftRadius="16dp" android:bottomRightRadius="16dp"/>
+    <corners android:radius="24dp" />
     <stroke
         android:width="1dp"
         android:color="@android:color/system_accent1_600" />
diff --git a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
index 22805f6..d1d2c70 100644
--- a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
+++ b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
@@ -123,21 +123,30 @@
             <LinearLayout
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
+                android:orientation="horizontal"
                 android:gravity="bottom|end"
-                android:orientation="vertical"
                 android:layout_marginEnd="16dp"
                 android:layout_marginBottom="16dp">
 
                 <!-- Do NOT change the IDs of the buttons: they are referenced in CTS tests. -->
+                <LinearLayout
+                    android:id="@+id/negative_multiple_devices_layout"
+                    android:layout_width="wrap_content"
+                    android:layout_height="48dp"
+                    android:gravity="center"
+                    android:visibility="gone">
 
-                <Button
-                    android:id="@+id/btn_negative_multiple_devices"
-                    style="@style/NegativeButtonMultipleDevices"
-                    android:textColor="?android:textColorPrimary"
-                    android:visibility="gone"
-                    android:layout_marginTop="12dp"
-                    android:layout_marginBottom="12dp"
-                    android:text="@string/consent_no" />
+                    <Button
+                        android:id="@+id/btn_negative_multiple_devices"
+                        style="@style/NegativeButtonMultipleDevices"
+                        android:textColor="?android:textColorPrimary"
+                        android:visibility="gone"
+                        android:duplicateParentState="true"
+                        android:clickable="false"
+                        android:text="@string/consent_no" />
+
+                </LinearLayout>
+
             </LinearLayout>
 
         </LinearLayout>
diff --git a/packages/CompanionDeviceManager/res/values/styles.xml b/packages/CompanionDeviceManager/res/values/styles.xml
index 3c75cd5..b167377 100644
--- a/packages/CompanionDeviceManager/res/values/styles.xml
+++ b/packages/CompanionDeviceManager/res/values/styles.xml
@@ -94,12 +94,12 @@
 
     <style name="NegativeButtonMultipleDevices"
            parent="@android:style/Widget.Material.Button.Colored">
-        <item name="android:layout_width">100dp</item>
+        <item name="android:layout_width">wrap_content</item>
         <item name="android:layout_height">36dp</item>
         <item name="android:textAllCaps">false</item>
         <item name="android:textSize">14sp</item>
-        <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item>
         <item name="android:background">@drawable/btn_negative_multiple_devices</item>
+        <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item>
     </style>
 
     <style name="DeviceListBorder">
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index 8316f9d..99b776c 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -156,6 +156,9 @@
     private ConstraintLayout mConstraintList;
     // Only present for self-managed association requests.
     private RelativeLayout mVendorHeader;
+    // A linearLayout for mButtonNotAllowMultipleDevices, user will press this layout instead
+    // of the button for accessibility.
+    private LinearLayout mNotAllowMultipleDevicesLayout;
 
     // The recycler view is only shown for multiple-device regular association request, after
     // at least one matching device is found.
@@ -327,10 +330,11 @@
         mButtonAllow = findViewById(R.id.btn_positive);
         mButtonNotAllow = findViewById(R.id.btn_negative);
         mButtonNotAllowMultipleDevices = findViewById(R.id.btn_negative_multiple_devices);
+        mNotAllowMultipleDevicesLayout = findViewById(R.id.negative_multiple_devices_layout);
 
         mButtonAllow.setOnClickListener(this::onPositiveButtonClick);
         mButtonNotAllow.setOnClickListener(this::onNegativeButtonClick);
-        mButtonNotAllowMultipleDevices.setOnClickListener(this::onNegativeButtonClick);
+        mNotAllowMultipleDevicesLayout.setOnClickListener(this::onNegativeButtonClick);
 
         mVendorHeaderButton.setOnClickListener(this::onShowHelperDialog);
 
@@ -617,6 +621,7 @@
         mButtonNotAllow.setVisibility(View.GONE);
         mDeviceListRecyclerView.setVisibility(View.VISIBLE);
         mButtonNotAllowMultipleDevices.setVisibility(View.VISIBLE);
+        mNotAllowMultipleDevicesLayout.setVisibility(View.VISIBLE);
         mConstraintList.setVisibility(View.VISIBLE);
         mMultipleDeviceSpinner.setVisibility(View.VISIBLE);
     }
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/DeviceStateRotationLock/Android.bp b/packages/SettingsLib/DeviceStateRotationLock/Android.bp
index c642bd1..103309a 100644
--- a/packages/SettingsLib/DeviceStateRotationLock/Android.bp
+++ b/packages/SettingsLib/DeviceStateRotationLock/Android.bp
@@ -10,7 +10,10 @@
 android_library {
     name: "SettingsLibDeviceStateRotationLock",
 
-    srcs: ["src/**/*.java"],
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
 
     min_sdk_version: "21",
 }
diff --git a/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java
index 10b004e..76e1df1 100644
--- a/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java
+++ b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java
@@ -57,17 +57,19 @@
     private final Handler mMainHandler = new Handler(Looper.getMainLooper());
     private final Set<DeviceStateRotationLockSettingsListener> mListeners = new HashSet<>();
     private final SecureSettings mSecureSettings;
-    private String[] mDeviceStateRotationLockDefaults;
-    private SparseIntArray mDeviceStateRotationLockSettings;
-    private SparseIntArray mDeviceStateDefaultRotationLockSettings;
-    private SparseIntArray mDeviceStateRotationLockFallbackSettings;
+    private final PosturesHelper mPosturesHelper;
+    private String[] mPostureRotationLockDefaults;
+    private SparseIntArray mPostureRotationLockSettings;
+    private SparseIntArray mPostureDefaultRotationLockSettings;
+    private SparseIntArray mPostureRotationLockFallbackSettings;
     private String mLastSettingValue;
     private List<SettableDeviceState> mSettableDeviceStates;
 
     @VisibleForTesting
     DeviceStateRotationLockSettingsManager(Context context, SecureSettings secureSettings) {
-        this.mSecureSettings = secureSettings;
-        mDeviceStateRotationLockDefaults =
+        mSecureSettings = secureSettings;
+        mPosturesHelper = new PosturesHelper(context);
+        mPostureRotationLockDefaults =
                 context.getResources()
                         .getStringArray(R.array.config_perDeviceStateRotationLockDefaults);
         loadDefaults();
@@ -134,13 +136,14 @@
 
     /** Updates the rotation lock setting for a specified device state. */
     public void updateSetting(int deviceState, boolean rotationLocked) {
-        if (mDeviceStateRotationLockFallbackSettings.indexOfKey(deviceState) >= 0) {
-            // The setting for this device state is IGNORED, and has a fallback device state.
-            // The setting for that fallback device state should be the changed in this case.
-            deviceState = mDeviceStateRotationLockFallbackSettings.get(deviceState);
+        int posture = mPosturesHelper.deviceStateToPosture(deviceState);
+        if (mPostureRotationLockFallbackSettings.indexOfKey(posture) >= 0) {
+            // The setting for this device posture is IGNORED, and has a fallback posture.
+            // The setting for that fallback posture should be the changed in this case.
+            posture = mPostureRotationLockFallbackSettings.get(posture);
         }
-        mDeviceStateRotationLockSettings.put(
-                deviceState,
+        mPostureRotationLockSettings.put(
+                posture,
                 rotationLocked
                         ? DEVICE_STATE_ROTATION_LOCK_LOCKED
                         : DEVICE_STATE_ROTATION_LOCK_UNLOCKED);
@@ -159,22 +162,23 @@
      */
     @Settings.Secure.DeviceStateRotationLockSetting
     public int getRotationLockSetting(int deviceState) {
-        int rotationLockSetting = mDeviceStateRotationLockSettings.get(
-                deviceState, /* valueIfKeyNotFound= */ DEVICE_STATE_ROTATION_LOCK_IGNORED);
+        int devicePosture = mPosturesHelper.deviceStateToPosture(deviceState);
+        int rotationLockSetting = mPostureRotationLockSettings.get(
+                devicePosture, /* valueIfKeyNotFound= */ DEVICE_STATE_ROTATION_LOCK_IGNORED);
         if (rotationLockSetting == DEVICE_STATE_ROTATION_LOCK_IGNORED) {
-            rotationLockSetting = getFallbackRotationLockSetting(deviceState);
+            rotationLockSetting = getFallbackRotationLockSetting(devicePosture);
         }
         return rotationLockSetting;
     }
 
-    private int getFallbackRotationLockSetting(int deviceState) {
-        int indexOfFallbackState = mDeviceStateRotationLockFallbackSettings.indexOfKey(deviceState);
-        if (indexOfFallbackState < 0) {
+    private int getFallbackRotationLockSetting(int devicePosture) {
+        int indexOfFallback = mPostureRotationLockFallbackSettings.indexOfKey(devicePosture);
+        if (indexOfFallback < 0) {
             Log.w(TAG, "Setting is ignored, but no fallback was specified.");
             return DEVICE_STATE_ROTATION_LOCK_IGNORED;
         }
-        int fallbackState = mDeviceStateRotationLockFallbackSettings.valueAt(indexOfFallbackState);
-        return mDeviceStateRotationLockSettings.get(fallbackState,
+        int fallbackPosture = mPostureRotationLockFallbackSettings.valueAt(indexOfFallback);
+        return mPostureRotationLockSettings.get(fallbackPosture,
                 /* valueIfKeyNotFound= */ DEVICE_STATE_ROTATION_LOCK_IGNORED);
     }
 
@@ -189,8 +193,8 @@
      * DEVICE_STATE_ROTATION_LOCK_UNLOCKED}.
      */
     public boolean isRotationLockedForAllStates() {
-        for (int i = 0; i < mDeviceStateRotationLockSettings.size(); i++) {
-            if (mDeviceStateRotationLockSettings.valueAt(i)
+        for (int i = 0; i < mPostureRotationLockSettings.size(); i++) {
+            if (mPostureRotationLockSettings.valueAt(i)
                     == DEVICE_STATE_ROTATION_LOCK_UNLOCKED) {
                 return false;
             }
@@ -221,7 +225,7 @@
             fallbackOnDefaults();
             return;
         }
-        mDeviceStateRotationLockSettings = new SparseIntArray(values.length / 2);
+        mPostureRotationLockSettings = new SparseIntArray(values.length / 2);
         int key;
         int value;
 
@@ -230,7 +234,7 @@
                 key = Integer.parseInt(values[i++]);
                 value = Integer.parseInt(values[i++]);
                 boolean isPersistedValueIgnored = value == DEVICE_STATE_ROTATION_LOCK_IGNORED;
-                boolean isDefaultValueIgnored = mDeviceStateDefaultRotationLockSettings.get(key)
+                boolean isDefaultValueIgnored = mPostureDefaultRotationLockSettings.get(key)
                         == DEVICE_STATE_ROTATION_LOCK_IGNORED;
                 if (isPersistedValueIgnored != isDefaultValueIgnored) {
                     Log.w(TAG, "Conflict for ignored device state " + key
@@ -238,7 +242,7 @@
                     fallbackOnDefaults();
                     return;
                 }
-                mDeviceStateRotationLockSettings.put(key, value);
+                mPostureRotationLockSettings.put(key, value);
             } catch (NumberFormatException e) {
                 Log.wtf(TAG, "Error deserializing one of the saved settings", e);
                 fallbackOnDefaults();
@@ -253,7 +257,7 @@
      */
     @VisibleForTesting
     public void resetStateForTesting(Resources resources) {
-        mDeviceStateRotationLockDefaults =
+        mPostureRotationLockDefaults =
                 resources.getStringArray(R.array.config_perDeviceStateRotationLockDefaults);
         fallbackOnDefaults();
     }
@@ -264,23 +268,23 @@
     }
 
     private void persistSettings() {
-        if (mDeviceStateRotationLockSettings.size() == 0) {
+        if (mPostureRotationLockSettings.size() == 0) {
             persistSettingIfChanged(/* newSettingValue= */ "");
             return;
         }
 
         StringBuilder stringBuilder = new StringBuilder();
         stringBuilder
-                .append(mDeviceStateRotationLockSettings.keyAt(0))
+                .append(mPostureRotationLockSettings.keyAt(0))
                 .append(SEPARATOR_REGEX)
-                .append(mDeviceStateRotationLockSettings.valueAt(0));
+                .append(mPostureRotationLockSettings.valueAt(0));
 
-        for (int i = 1; i < mDeviceStateRotationLockSettings.size(); i++) {
+        for (int i = 1; i < mPostureRotationLockSettings.size(); i++) {
             stringBuilder
                     .append(SEPARATOR_REGEX)
-                    .append(mDeviceStateRotationLockSettings.keyAt(i))
+                    .append(mPostureRotationLockSettings.keyAt(i))
                     .append(SEPARATOR_REGEX)
-                    .append(mDeviceStateRotationLockSettings.valueAt(i));
+                    .append(mPostureRotationLockSettings.valueAt(i));
         }
         persistSettingIfChanged(stringBuilder.toString());
     }
@@ -300,22 +304,20 @@
     }
 
     private void loadDefaults() {
-        mSettableDeviceStates = new ArrayList<>(mDeviceStateRotationLockDefaults.length);
-        mDeviceStateDefaultRotationLockSettings = new SparseIntArray(
-                mDeviceStateRotationLockDefaults.length);
-        mDeviceStateRotationLockSettings = new SparseIntArray(
-                mDeviceStateRotationLockDefaults.length);
-        mDeviceStateRotationLockFallbackSettings = new SparseIntArray(1);
-        for (String entry : mDeviceStateRotationLockDefaults) {
+        mSettableDeviceStates = new ArrayList<>(mPostureRotationLockDefaults.length);
+        mPostureDefaultRotationLockSettings = new SparseIntArray(
+                mPostureRotationLockDefaults.length);
+        mPostureRotationLockSettings = new SparseIntArray(mPostureRotationLockDefaults.length);
+        mPostureRotationLockFallbackSettings = new SparseIntArray(1);
+        for (String entry : mPostureRotationLockDefaults) {
             String[] values = entry.split(SEPARATOR_REGEX);
             try {
-                int deviceState = Integer.parseInt(values[0]);
+                int posture = Integer.parseInt(values[0]);
                 int rotationLockSetting = Integer.parseInt(values[1]);
                 if (rotationLockSetting == DEVICE_STATE_ROTATION_LOCK_IGNORED) {
                     if (values.length == 3) {
-                        int fallbackDeviceState = Integer.parseInt(values[2]);
-                        mDeviceStateRotationLockFallbackSettings.put(deviceState,
-                                fallbackDeviceState);
+                        int fallbackPosture = Integer.parseInt(values[2]);
+                        mPostureRotationLockFallbackSettings.put(posture, fallbackPosture);
                     } else {
                         Log.w(TAG,
                                 "Rotation lock setting is IGNORED, but values have unexpected "
@@ -324,9 +326,14 @@
                     }
                 }
                 boolean isSettable = rotationLockSetting != DEVICE_STATE_ROTATION_LOCK_IGNORED;
-                mSettableDeviceStates.add(new SettableDeviceState(deviceState, isSettable));
-                mDeviceStateRotationLockSettings.put(deviceState, rotationLockSetting);
-                mDeviceStateDefaultRotationLockSettings.put(deviceState, rotationLockSetting);
+                Integer deviceState = mPosturesHelper.postureToDeviceState(posture);
+                if (deviceState != null) {
+                    mSettableDeviceStates.add(new SettableDeviceState(deviceState, isSettable));
+                } else {
+                    Log.wtf(TAG, "No matching device state for posture: " + posture);
+                }
+                mPostureRotationLockSettings.put(posture, rotationLockSetting);
+                mPostureDefaultRotationLockSettings.put(posture, rotationLockSetting);
             } catch (NumberFormatException e) {
                 Log.wtf(TAG, "Error parsing settings entry. Entry was: " + entry, e);
                 return;
@@ -338,13 +345,11 @@
     public void dump(IndentingPrintWriter pw) {
         pw.println("DeviceStateRotationLockSettingsManager");
         pw.increaseIndent();
-        pw.println("mDeviceStateRotationLockDefaults: " + Arrays.toString(
-                mDeviceStateRotationLockDefaults));
-        pw.println("mDeviceStateDefaultRotationLockSettings: "
-                + mDeviceStateDefaultRotationLockSettings);
-        pw.println("mDeviceStateRotationLockSettings: " + mDeviceStateRotationLockSettings);
-        pw.println("mDeviceStateRotationLockFallbackSettings: "
-                + mDeviceStateRotationLockFallbackSettings);
+        pw.println("mPostureRotationLockDefaults: "
+                + Arrays.toString(mPostureRotationLockDefaults));
+        pw.println("mPostureDefaultRotationLockSettings: " + mPostureDefaultRotationLockSettings);
+        pw.println("mDeviceStateRotationLockSettings: " + mPostureRotationLockSettings);
+        pw.println("mPostureRotationLockFallbackSettings: " + mPostureRotationLockFallbackSettings);
         pw.println("mSettableDeviceStates: " + mSettableDeviceStates);
         pw.println("mLastSettingValue: " + mLastSettingValue);
         pw.decreaseIndent();
diff --git a/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/PosturesHelper.kt b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/PosturesHelper.kt
new file mode 100644
index 0000000..9c70be9
--- /dev/null
+++ b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/PosturesHelper.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.devicestate
+
+import android.content.Context
+import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_KEY_FOLDED
+import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_KEY_HALF_FOLDED
+import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_KEY_UNFOLDED
+import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_KEY_UNKNOWN
+import android.provider.Settings.Secure.DeviceStateRotationLockKey
+import com.android.internal.R
+
+/** Helps to convert between device state and posture. */
+class PosturesHelper(context: Context) {
+
+    private val foldedDeviceStates =
+        context.resources.getIntArray(R.array.config_foldedDeviceStates)
+    private val halfFoldedDeviceStates =
+        context.resources.getIntArray(R.array.config_halfFoldedDeviceStates)
+    private val unfoldedDeviceStates =
+        context.resources.getIntArray(R.array.config_openDeviceStates)
+
+    @DeviceStateRotationLockKey
+    fun deviceStateToPosture(deviceState: Int): Int {
+        return when (deviceState) {
+            in foldedDeviceStates -> DEVICE_STATE_ROTATION_KEY_FOLDED
+            in halfFoldedDeviceStates -> DEVICE_STATE_ROTATION_KEY_HALF_FOLDED
+            in unfoldedDeviceStates -> DEVICE_STATE_ROTATION_KEY_UNFOLDED
+            else -> DEVICE_STATE_ROTATION_KEY_UNKNOWN
+        }
+    }
+
+    fun postureToDeviceState(@DeviceStateRotationLockKey posture: Int): Int? {
+        return when (posture) {
+            DEVICE_STATE_ROTATION_KEY_FOLDED -> foldedDeviceStates.firstOrNull()
+            DEVICE_STATE_ROTATION_KEY_HALF_FOLDED -> halfFoldedDeviceStates.firstOrNull()
+            DEVICE_STATE_ROTATION_KEY_UNFOLDED -> unfoldedDeviceStates.firstOrNull()
+            else -> null
+        }
+    }
+}
diff --git a/packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java b/packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java
index a45e853..60ec915 100644
--- a/packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java
+++ b/packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java
@@ -55,11 +55,15 @@
     public static final String METHOD_NAME_SET_EMERGENCY_NUMBER_OVERRIDE =
             "SET_EMERGENCY_NUMBER_OVERRIDE";
     public static final String METHOD_NAME_SET_EMERGENCY_GESTURE = "SET_EMERGENCY_GESTURE";
+    public static final String METHOD_NAME_SET_EMERGENCY_GESTURE_UI_SHOWING =
+            "SET_EMERGENCY_GESTURE_UI_SHOWING";
     public static final String METHOD_NAME_SET_EMERGENCY_SOUND = "SET_EMERGENCY_SOUND";
     public static final String METHOD_NAME_GET_EMERGENCY_GESTURE_ENABLED = "GET_EMERGENCY_GESTURE";
     public static final String METHOD_NAME_GET_EMERGENCY_GESTURE_SOUND_ENABLED =
             "GET_EMERGENCY_SOUND";
     public static final String EMERGENCY_GESTURE_CALL_NUMBER = "emergency_gesture_call_number";
+    public static final String EMERGENCY_GESTURE_UI_SHOWING_VALUE =
+            "emergency_gesture_ui_showing_value";
     public static final String EMERGENCY_SETTING_VALUE = "emergency_setting_value";
     public static final int EMERGENCY_SETTING_ON = 1;
     public static final int EMERGENCY_SETTING_OFF = 0;
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt
index ca88f8d..215f6b9 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt
@@ -16,7 +16,6 @@
 
 package com.android.settingslib.spa.framework.common
 
-import android.app.settings.SettingsEnums
 import android.os.Bundle
 
 // Defines the category of the log, for quick filter
@@ -32,14 +31,14 @@
 }
 
 // Defines the log events in Spa.
-enum class LogEvent(val action: Int) {
+enum class LogEvent {
     // Page related events.
-    PAGE_ENTER(SettingsEnums.PAGE_VISIBLE),
-    PAGE_LEAVE(SettingsEnums.PAGE_HIDE),
+    PAGE_ENTER,
+    PAGE_LEAVE,
 
     // Entry related events.
-    ENTRY_CLICK(SettingsEnums.ACTION_SETTINGS_TILE_CLICK),
-    ENTRY_SWITCH(SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE),
+    ENTRY_CLICK,
+    ENTRY_SWITCH,
 }
 
 internal const val LOG_DATA_DISPLAY_NAME = "name"
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java
index c61ebc0..0630a2e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java
@@ -261,19 +261,21 @@
         Pattern pattern = Pattern.compile(PATTERN_BT_BROADCAST_METADATA);
         Matcher match = pattern.matcher(qrCodeString);
         if (match.find()) {
-            mSourceAddressType = Integer.parseInt(match.group(MATCH_INDEX_ADDRESS_TYPE));
-            mSourceDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(
+            try {
+                mSourceAddressType = Integer.parseInt(match.group(MATCH_INDEX_ADDRESS_TYPE));
+                mSourceDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(
                     match.group(MATCH_INDEX_DEVICE));
-            mSourceAdvertisingSid = Integer.parseInt(match.group(MATCH_INDEX_ADVERTISING_SID));
-            mBroadcastId = Integer.parseInt(match.group(MATCH_INDEX_BROADCAST_ID));
-            mPaSyncInterval = Integer.parseInt(match.group(MATCH_INDEX_SYNC_INTERVAL));
-            mIsEncrypted = Boolean.valueOf(match.group(MATCH_INDEX_IS_ENCRYPTED));
-            mBroadcastCode = match.group(MATCH_INDEX_BROADCAST_CODE).getBytes();
-            mPresentationDelayMicros =
-                  Integer.parseInt(match.group(MATCH_INDEX_PRESENTATION_DELAY));
+                mSourceAdvertisingSid = Integer.parseInt(
+                    match.group(MATCH_INDEX_ADVERTISING_SID));
+                mBroadcastId = Integer.parseInt(match.group(MATCH_INDEX_BROADCAST_ID));
+                mPaSyncInterval = Integer.parseInt(match.group(MATCH_INDEX_SYNC_INTERVAL));
+                mIsEncrypted = Boolean.valueOf(match.group(MATCH_INDEX_IS_ENCRYPTED));
+                mBroadcastCode = match.group(MATCH_INDEX_BROADCAST_CODE).getBytes();
+                mPresentationDelayMicros =
+                    Integer.parseInt(match.group(MATCH_INDEX_PRESENTATION_DELAY));
 
-            if (DEBUG) {
-                Log.d(TAG, "Converted qrCodeString result: "
+                if (DEBUG) {
+                    Log.d(TAG, "Converted qrCodeString result: "
                         + " ,Type = " + mSourceAddressType
                         + " ,Device = " + mSourceDevice
                         + " ,AdSid = " + mSourceAdvertisingSid
@@ -282,11 +284,11 @@
                         + " ,encrypted = " + mIsEncrypted
                         + " ,BroadcastCode = " + Arrays.toString(mBroadcastCode)
                         + " ,delay = " + mPresentationDelayMicros);
-            }
+                }
 
-            mSubgroup = convertToSubgroup(match.group(MATCH_INDEX_SUBGROUPS));
+                mSubgroup = convertToSubgroup(match.group(MATCH_INDEX_SUBGROUPS));
 
-            return new BluetoothLeBroadcastMetadata.Builder()
+                return new BluetoothLeBroadcastMetadata.Builder()
                     .setSourceDevice(mSourceDevice, mSourceAddressType)
                     .setSourceAdvertisingSid(mSourceAdvertisingSid)
                     .setBroadcastId(mBroadcastId)
@@ -296,10 +298,13 @@
                     .setPresentationDelayMicros(mPresentationDelayMicros)
                     .addSubgroup(mSubgroup)
                     .build();
+            } catch (IllegalArgumentException e) {
+                Log.d(TAG, "IllegalArgumentException when convert : " + e);
+                return null;
+            }
         } else {
             if (DEBUG) {
-                Log.d(TAG,
-                        "The match fail, can not convert it to BluetoothLeBroadcastMetadata.");
+                Log.d(TAG, "The match fail, can not convert it to BluetoothLeBroadcastMetadata.");
             }
             return null;
         }
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/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java
index 0fa15eb..fdefcde 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java
@@ -70,12 +70,20 @@
         when(mMockContext.getApplicationContext()).thenReturn(mMockContext);
         when(mMockContext.getResources()).thenReturn(mMockResources);
         when(mMockContext.getContentResolver()).thenReturn(context.getContentResolver());
+        when(mMockResources.getStringArray(R.array.config_perDeviceStateRotationLockDefaults))
+                .thenReturn(new String[]{"0:1", "1:0:2", "2:2"});
+        when(mMockResources.getIntArray(R.array.config_foldedDeviceStates))
+                .thenReturn(new int[]{0});
+        when(mMockResources.getIntArray(R.array.config_halfFoldedDeviceStates))
+                .thenReturn(new int[]{1});
+        when(mMockResources.getIntArray(R.array.config_openDeviceStates))
+                .thenReturn(new int[]{2});
         mFakeSecureSettings.registerContentObserver(
                 Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
                 /* notifyForDescendents= */ false, //NOTYPO
                 mContentObserver,
                 UserHandle.USER_CURRENT);
-        mManager = new DeviceStateRotationLockSettingsManager(context, mFakeSecureSettings);
+        mManager = new DeviceStateRotationLockSettingsManager(mMockContext, mFakeSecureSettings);
     }
 
     @Test
@@ -109,7 +117,7 @@
     public void getSettableDeviceStates_returnsExpectedValuesInOriginalOrder() {
         when(mMockResources.getStringArray(
                 R.array.config_perDeviceStateRotationLockDefaults)).thenReturn(
-                new String[]{"2:2", "4:0", "1:1", "0:0"});
+                new String[]{"2:1", "1:0:1", "0:2"});
 
         List<SettableDeviceState> settableDeviceStates =
                 DeviceStateRotationLockSettingsManager.getInstance(
@@ -117,9 +125,8 @@
 
         assertThat(settableDeviceStates).containsExactly(
                 new SettableDeviceState(/* deviceState= */ 2, /* isSettable= */ true),
-                new SettableDeviceState(/* deviceState= */ 4, /* isSettable= */ false),
-                new SettableDeviceState(/* deviceState= */ 1, /* isSettable= */ true),
-                new SettableDeviceState(/* deviceState= */ 0, /* isSettable= */ false)
+                new SettableDeviceState(/* deviceState= */ 1, /* isSettable= */ false),
+                new SettableDeviceState(/* deviceState= */ 0, /* isSettable= */ true)
         ).inOrder();
     }
 
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/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 8d07fb6..d5386c1 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -126,6 +126,8 @@
                 NON_NEGATIVE_INTEGER_VALIDATOR);
         VALIDATORS.put(Global.EMERGENCY_GESTURE_TAP_DETECTION_MIN_TIME_MS,
                 NON_NEGATIVE_INTEGER_VALIDATOR);
+        VALIDATORS.put(Global.EMERGENCY_GESTURE_STICKY_UI_MAX_DURATION_MILLIS,
+                NON_NEGATIVE_INTEGER_VALIDATOR);
         VALIDATORS.put(Global.CALL_AUTO_RETRY, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.DOCK_AUDIO_MEDIA_ENABLED, 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..f6c2f69 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(
@@ -313,6 +314,9 @@
         VALIDATORS.put(Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.EMERGENCY_GESTURE_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.EMERGENCY_GESTURE_SOUND_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.EMERGENCY_GESTURE_UI_SHOWING, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(
+                Secure.EMERGENCY_GESTURE_UI_LAST_STARTED_MILLIS, NONE_NEGATIVE_LONG_VALIDATOR);
         VALIDATORS.put(Secure.ADAPTIVE_CONNECTIVITY_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(
                 Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS, NONE_NEGATIVE_LONG_VALIDATOR);
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..7e89bfc 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -19,9 +19,9 @@
 import static android.os.Process.ROOT_UID;
 import static android.os.Process.SHELL_UID;
 import static android.os.Process.SYSTEM_UID;
-import static android.provider.Settings.Config.SYNC_DISABLED_MODE_NONE;
-import static android.provider.Settings.Config.SYNC_DISABLED_MODE_PERSISTENT;
-import static android.provider.Settings.Config.SYNC_DISABLED_MODE_UNTIL_REBOOT;
+import static android.provider.DeviceConfig.SYNC_DISABLED_MODE_NONE;
+import static android.provider.DeviceConfig.SYNC_DISABLED_MODE_PERSISTENT;
+import static android.provider.DeviceConfig.SYNC_DISABLED_MODE_UNTIL_REBOOT;
 import static android.provider.Settings.SET_ALL_RESULT_DISABLED;
 import static android.provider.Settings.SET_ALL_RESULT_FAILURE;
 import static android.provider.Settings.SET_ALL_RESULT_SUCCESS;
@@ -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 1f14723..47abb35 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -257,6 +257,7 @@
                     Settings.Global.EMERGENCY_AFFORDANCE_NEEDED,
                     Settings.Global.EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS,
                     Settings.Global.EMERGENCY_GESTURE_TAP_DETECTION_MIN_TIME_MS,
+                    Settings.Global.EMERGENCY_GESTURE_STICKY_UI_MAX_DURATION_MILLIS,
                     Settings.Global.EMULATE_DISPLAY_CUTOUT,
                     Settings.Global.ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED,
                     Settings.Global.ENABLE_CACHE_QUOTA_CALCULATION,
@@ -718,6 +719,8 @@
                  Settings.Secure.DOCKED_CLOCK_FACE,
                  Settings.Secure.DOZE_PULSE_ON_LONG_PRESS,
                  Settings.Secure.EMERGENCY_ASSISTANCE_APPLICATION,
+                 Settings.Secure.EMERGENCY_GESTURE_UI_SHOWING,
+                 Settings.Secure.EMERGENCY_GESTURE_UI_LAST_STARTED_MILLIS,
                  Settings.Secure.ENABLED_INPUT_METHODS,  // Intentionally removed in P
                  Settings.Secure.ENABLED_NOTIFICATION_ASSISTANT,
                  Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
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/Android.bp b/packages/SystemUI/Android.bp
index b236ac5..8b38deb 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -368,6 +368,7 @@
     plugins: ["dagger2-compiler"],
     lint: {
         test: true,
+        extra_check_modules: ["SystemUILintChecker"],
     },
 }
 
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 1136c11..650d5fa 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -27,6 +27,7 @@
 
     <!-- Used to read wallpaper -->
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.READ_WALLPAPER_INTERNAL" />
 
     <!-- Used to read storage for all users -->
     <uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE" />
@@ -924,7 +925,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 +947,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/accessibility/accessibilitymenu/res/values/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values/strings.xml
index 0747ef0..81fa8e6 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/values/strings.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values/strings.xml
@@ -49,7 +49,6 @@
   <!-- Short summary of app that appears as subtext on the service preference in Settings -->
   <string name="accessibility_menu_summary">Control device via large menu</string>
 
-  <!-- TODO(b/113371047): string need to be reviewed -->
   <!-- String defining the settings name -->
   <string name="accessibility_menu_settings_name">Accessibility Menu Settings</string>
 
@@ -57,8 +56,6 @@
   <string name="accessibility_menu_large_buttons_title">Large buttons</string>
   <!-- String defining the summary of Large button setting -->
   <string name="accessibility_menu_large_buttons_summary">Increase size of Accessibility Menu Buttons</string>
-  <!-- String defining the title of the preference to show help and feedback menu [CHAR LIMIT=40] -->
-  <string name="pref_help_and_feedback_title">Help &#38; feedback</string>
   <!-- String defining the title of the preference to show help menu [CHAR LIMIT=40] -->
   <string name="pref_help_title">Help</string>
 
@@ -67,7 +64,4 @@
   <!-- The percentage of the music volume, and double "%" is required to represent the symbol "%" -->
   <string name="music_volume_percentage_label">Music volume <xliff:g id="percentage">%1$s</xliff:g> %%</string>
 
-  <!-- The label of a settings item that displays legal information about the licenses used in this app. [CHAR LIMIT=NONE] -->
-  <string name="pref_item_licenses">Open Source Licenses</string>
-
 </resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/xml/accessibilitymenu_preferences.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/xml/accessibilitymenu_preferences.xml
index 3b79287..e42c3cd 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/xml/accessibilitymenu_preferences.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/xml/accessibilitymenu_preferences.xml
@@ -28,6 +28,6 @@
 
     <Preference
         android:key="@string/pref_help"
-        android:title="@string/pref_help_and_feedback_title"/>
+        android:title="@string/pref_help_title"/>
 
 </androidx.preference.PreferenceScreen>
\ No newline at end of file
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
index 8f29348..4b6f9a4 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
@@ -71,8 +71,6 @@
         private void initializeHelpAndFeedbackPreference() {
             final Preference prefHelp = findPreference(getString(R.string.pref_help));
             if (prefHelp != null) {
-                prefHelp.setTitle(R.string.pref_help_title);
-
                 // Do not allow access to web during setup.
                 if (Settings.Secure.getInt(
                         getContext().getContentResolver(),
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
index 0e89dcd..7277392 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
@@ -29,6 +29,8 @@
 import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_TOGGLE_MENU;
 import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.PACKAGE_NAME;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.app.Instrumentation;
 import android.app.UiAutomation;
@@ -39,6 +41,7 @@
 import android.hardware.display.BrightnessInfo;
 import android.hardware.display.DisplayManager;
 import android.media.AudioManager;
+import android.os.PowerManager;
 import android.provider.Settings;
 import android.util.Log;
 import android.view.accessibility.AccessibilityManager;
@@ -375,4 +378,26 @@
                 () -> sLastGlobalAction.compareAndSet(
                         GLOBAL_ACTION_TAKE_SCREENSHOT, NO_GLOBAL_ACTION));
     }
+
+    @Test
+    public void testOnScreenLock_closesMenu() throws Throwable {
+        openMenu();
+        Context context = sInstrumentation.getTargetContext();
+        PowerManager powerManager = context.getSystemService(PowerManager.class);
+
+        assertThat(powerManager).isNotNull();
+        assertThat(powerManager.isInteractive()).isTrue();
+
+        sUiAutomation.performGlobalAction(GLOBAL_ACTION_LOCK_SCREEN);
+        TestUtils.waitUntil("Screen did not become locked",
+                TIMEOUT_UI_CHANGE_S,
+                () -> !powerManager.isInteractive());
+
+        sUiAutomation.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+        TestUtils.waitUntil("Screen did not wake up",
+                TIMEOUT_UI_CHANGE_S,
+                () -> powerManager.isInteractive());
+
+        assertThat(isMenuVisible()).isFalse();
+    }
 }
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/animation/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt
index 280e7ed9..23fcb69 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt
@@ -15,163 +15,166 @@
  */
 package com.android.systemui.surfaceeffects.shaderutil
 
-/** A common utility functions that are used for computing shaders. */
-class ShaderUtilLibrary {
+/** Common utility functions that are used for computing shaders. */
+object ShaderUtilLibrary {
     // language=AGSL
-    companion object {
-        const val SHADER_LIB =
-            """
-            float triangleNoise(vec2 n) {
-                n  = fract(n * vec2(5.3987, 5.4421));
-                n += dot(n.yx, n.xy + vec2(21.5351, 14.3137));
-                float xy = n.x * n.y;
-                // compute in [0..2[ and remap to [-1.0..1.0[
-                return fract(xy * 95.4307) + fract(xy * 75.04961) - 1.0;
+    const val SHADER_LIB =
+        """
+        float triangleNoise(vec2 n) {
+            n  = fract(n * vec2(5.3987, 5.4421));
+            n += dot(n.yx, n.xy + vec2(21.5351, 14.3137));
+            float xy = n.x * n.y;
+            // compute in [0..2[ and remap to [-1.0..1.0[
+            return fract(xy * 95.4307) + fract(xy * 75.04961) - 1.0;
+        }
+
+        const float PI = 3.1415926535897932384626;
+
+        float sparkles(vec2 uv, float t) {
+            float n = triangleNoise(uv);
+            float s = 0.0;
+            for (float i = 0; i < 4; i += 1) {
+                float l = i * 0.01;
+                float h = l + 0.1;
+                float o = smoothstep(n - l, h, n);
+                o *= abs(sin(PI * o * (t + 0.55 * i)));
+                s += o;
             }
+            return s;
+        }
 
-            const float PI = 3.1415926535897932384626;
+        vec2 distort(vec2 p, float time, float distort_amount_radial,
+            float distort_amount_xy) {
+                float angle = atan(p.y, p.x);
+                  return p + vec2(sin(angle * 8 + time * 0.003 + 1.641),
+                            cos(angle * 5 + 2.14 + time * 0.00412)) * distort_amount_radial
+                     + vec2(sin(p.x * 0.01 + time * 0.00215 + 0.8123),
+                            cos(p.y * 0.01 + time * 0.005931)) * distort_amount_xy;
+        }
 
-            float sparkles(vec2 uv, float t) {
-                float n = triangleNoise(uv);
-                float s = 0.0;
-                for (float i = 0; i < 4; i += 1) {
-                    float l = i * 0.01;
-                    float h = l + 0.1;
-                    float o = smoothstep(n - l, h, n);
-                    o *= abs(sin(PI * o * (t + 0.55 * i)));
-                    s += o;
-                }
-                return s;
-            }
+        // Perceived luminosity (L′), not absolute luminosity.
+        half getLuminosity(vec3 c) {
+            return 0.3 * c.r + 0.59 * c.g + 0.11 * c.b;
+        }
 
-            vec2 distort(vec2 p, float time, float distort_amount_radial,
-                float distort_amount_xy) {
-                    float angle = atan(p.y, p.x);
-                      return p + vec2(sin(angle * 8 + time * 0.003 + 1.641),
-                                cos(angle * 5 + 2.14 + time * 0.00412)) * distort_amount_radial
-                         + vec2(sin(p.x * 0.01 + time * 0.00215 + 0.8123),
-                                cos(p.y * 0.01 + time * 0.005931)) * distort_amount_xy;
-            }
+        // Creates a luminosity mask and clamp to the legal range.
+        vec3 maskLuminosity(vec3 dest, float lum) {
+            dest.rgb *= vec3(lum);
+            // Clip back into the legal range
+            dest = clamp(dest, vec3(0.), vec3(1.0));
+            return dest;
+        }
 
-            // Perceived luminosity (L′), not absolute luminosity.
-            half getLuminosity(vec3 c) {
-                return 0.3 * c.r + 0.59 * c.g + 0.11 * c.b;
-            }
+        // Return range [-1, 1].
+        vec3 hash(vec3 p) {
+            p = fract(p * vec3(.3456, .1234, .9876));
+            p += dot(p, p.yxz + 43.21);
+            p = (p.xxy + p.yxx) * p.zyx;
+            return (fract(sin(p) * 4567.1234567) - .5) * 2.;
+        }
 
-            // Creates a luminosity mask and clamp to the legal range.
-            vec3 maskLuminosity(vec3 dest, float lum) {
-                dest.rgb *= vec3(lum);
-                // Clip back into the legal range
-                dest = clamp(dest, vec3(0.), vec3(1.0));
-                return dest;
-            }
+        // Skew factors (non-uniform).
+        const half SKEW = 0.3333333;  // 1/3
+        const half UNSKEW = 0.1666667;  // 1/6
 
-            // Return range [-1, 1].
-            vec3 hash(vec3 p) {
-                p = fract(p * vec3(.3456, .1234, .9876));
-                p += dot(p, p.yxz + 43.21);
-                p = (p.xxy + p.yxx) * p.zyx;
-                return (fract(sin(p) * 4567.1234567) - .5) * 2.;
-            }
+        // Return range roughly [-1,1].
+        // It's because the hash function (that returns a random gradient vector) returns
+        // different magnitude of vectors. Noise doesn't have to be in the precise range thus
+        // skipped normalize.
+        half simplex3d(vec3 p) {
+            // Skew the input coordinate, so that we get squashed cubical grid
+            vec3 s = floor(p + (p.x + p.y + p.z) * SKEW);
 
-            // Skew factors (non-uniform).
-            const half SKEW = 0.3333333;  // 1/3
-            const half UNSKEW = 0.1666667;  // 1/6
+            // Unskew back
+            vec3 u = s - (s.x + s.y + s.z) * UNSKEW;
 
-            // Return range roughly [-1,1].
-            // It's because the hash function (that returns a random gradient vector) returns
-            // different magnitude of vectors. Noise doesn't have to be in the precise range thus
-            // skipped normalize.
-            half simplex3d(vec3 p) {
-                // Skew the input coordinate, so that we get squashed cubical grid
-                vec3 s = floor(p + (p.x + p.y + p.z) * SKEW);
+            // Unskewed coordinate that is relative to p, to compute the noise contribution
+            // based on the distance.
+            vec3 c0 = p - u;
 
-                // Unskew back
-                vec3 u = s - (s.x + s.y + s.z) * UNSKEW;
+            // We have six simplices (in this case tetrahedron, since we are in 3D) that we
+            // could possibly in.
+            // Here, we are finding the correct tetrahedron (simplex shape), and traverse its
+            // four vertices (c0..3) when computing noise contribution.
+            // The way we find them is by comparing c0's x,y,z values.
+            // For example in 2D, we can find the triangle (simplex shape in 2D) that we are in
+            // by comparing x and y values. i.e. x>y lower, x<y, upper triangle.
+            // Same applies in 3D.
+            //
+            // Below indicates the offsets (or offset directions) when c0=(x0,y0,z0)
+            // x0>y0>z0: (1,0,0), (1,1,0), (1,1,1)
+            // x0>z0>y0: (1,0,0), (1,0,1), (1,1,1)
+            // z0>x0>y0: (0,0,1), (1,0,1), (1,1,1)
+            // z0>y0>x0: (0,0,1), (0,1,1), (1,1,1)
+            // y0>z0>x0: (0,1,0), (0,1,1), (1,1,1)
+            // y0>x0>z0: (0,1,0), (1,1,0), (1,1,1)
+            //
+            // The rule is:
+            // * For offset1, set 1 at the max component, otherwise 0.
+            // * For offset2, set 0 at the min component, otherwise 1.
+            // * For offset3, set 1 for all.
+            //
+            // Encode x0-y0, y0-z0, z0-x0 in a vec3
+            vec3 en = c0 - c0.yzx;
+            // Each represents whether x0>y0, y0>z0, z0>x0
+            en = step(vec3(0.), en);
+            // en.zxy encodes z0>x0, x0>y0, y0>x0
+            vec3 offset1 = en * (1. - en.zxy); // find max
+            vec3 offset2 = 1. - en.zxy * (1. - en); // 1-(find min)
+            vec3 offset3 = vec3(1.);
 
-                // Unskewed coordinate that is relative to p, to compute the noise contribution
-                // based on the distance.
-                vec3 c0 = p - u;
+            vec3 c1 = c0 - offset1 + UNSKEW;
+            vec3 c2 = c0 - offset2 + UNSKEW * 2.;
+            vec3 c3 = c0 - offset3 + UNSKEW * 3.;
 
-                // We have six simplices (in this case tetrahedron, since we are in 3D) that we
-                // could possibly in.
-                // Here, we are finding the correct tetrahedron (simplex shape), and traverse its
-                // four vertices (c0..3) when computing noise contribution.
-                // The way we find them is by comparing c0's x,y,z values.
-                // For example in 2D, we can find the triangle (simplex shape in 2D) that we are in
-                // by comparing x and y values. i.e. x>y lower, x<y, upper triangle.
-                // Same applies in 3D.
-                //
-                // Below indicates the offsets (or offset directions) when c0=(x0,y0,z0)
-                // x0>y0>z0: (1,0,0), (1,1,0), (1,1,1)
-                // x0>z0>y0: (1,0,0), (1,0,1), (1,1,1)
-                // z0>x0>y0: (0,0,1), (1,0,1), (1,1,1)
-                // z0>y0>x0: (0,0,1), (0,1,1), (1,1,1)
-                // y0>z0>x0: (0,1,0), (0,1,1), (1,1,1)
-                // y0>x0>z0: (0,1,0), (1,1,0), (1,1,1)
-                //
-                // The rule is:
-                // * For offset1, set 1 at the max component, otherwise 0.
-                // * For offset2, set 0 at the min component, otherwise 1.
-                // * For offset3, set 1 for all.
-                //
-                // Encode x0-y0, y0-z0, z0-x0 in a vec3
-                vec3 en = c0 - c0.yzx;
-                // Each represents whether x0>y0, y0>z0, z0>x0
-                en = step(vec3(0.), en);
-                // en.zxy encodes z0>x0, x0>y0, y0>x0
-                vec3 offset1 = en * (1. - en.zxy); // find max
-                vec3 offset2 = 1. - en.zxy * (1. - en); // 1-(find min)
-                vec3 offset3 = vec3(1.);
+            // Kernel summation: dot(max(0, r^2-d^2))^4, noise contribution)
+            //
+            // First compute d^2, squared distance to the point.
+            vec4 w; // w = max(0, r^2 - d^2))
+            w.x = dot(c0, c0);
+            w.y = dot(c1, c1);
+            w.z = dot(c2, c2);
+            w.w = dot(c3, c3);
 
-                vec3 c1 = c0 - offset1 + UNSKEW;
-                vec3 c2 = c0 - offset2 + UNSKEW * 2.;
-                vec3 c3 = c0 - offset3 + UNSKEW * 3.;
+            // Noise contribution should decay to zero before they cross the simplex boundary.
+            // Usually r^2 is 0.5 or 0.6;
+            // 0.5 ensures continuity but 0.6 increases the visual quality for the application
+            // where discontinuity isn't noticeable.
+            w = max(0.6 - w, 0.);
 
-                // Kernel summation: dot(max(0, r^2-d^2))^4, noise contribution)
-                //
-                // First compute d^2, squared distance to the point.
-                vec4 w; // w = max(0, r^2 - d^2))
-                w.x = dot(c0, c0);
-                w.y = dot(c1, c1);
-                w.z = dot(c2, c2);
-                w.w = dot(c3, c3);
+            // Noise contribution from each point.
+            vec4 nc;
+            nc.x = dot(hash(s), c0);
+            nc.y = dot(hash(s + offset1), c1);
+            nc.z = dot(hash(s + offset2), c2);
+            nc.w = dot(hash(s + offset3), c3);
 
-                // Noise contribution should decay to zero before they cross the simplex boundary.
-                // Usually r^2 is 0.5 or 0.6;
-                // 0.5 ensures continuity but 0.6 increases the visual quality for the application
-                // where discontinuity isn't noticeable.
-                w = max(0.6 - w, 0.);
+            nc *= w*w*w*w;
 
-                // Noise contribution from each point.
-                vec4 nc;
-                nc.x = dot(hash(s), c0);
-                nc.y = dot(hash(s + offset1), c1);
-                nc.z = dot(hash(s + offset2), c2);
-                nc.w = dot(hash(s + offset3), c3);
+            // Add all the noise contributions.
+            // Should multiply by the possible max contribution to adjust the range in [-1,1].
+            return dot(vec4(32.), nc);
+        }
 
-                nc *= w*w*w*w;
+        // Random rotations.
+        // The way you create fractal noise is layering simplex noise with some rotation.
+        // To make random cloud looking noise, the rotations should not align. (Otherwise it
+        // creates patterned noise).
+        // Below rotations only rotate in one axis.
+        const mat3 rot1 = mat3(1.0, 0. ,0., 0., 0.15, -0.98, 0., 0.98, 0.15);
+        const mat3 rot2 = mat3(-0.95, 0. ,-0.3, 0., 1., 0., 0.3, 0., -0.95);
+        const mat3 rot3 = mat3(1.0, 0. ,0., 0., -0.44, -0.89, 0., 0.89, -0.44);
 
-                // Add all the noise contributions.
-                // Should multiply by the possible max contribution to adjust the range in [-1,1].
-                return dot(vec4(32.), nc);
-            }
+        // Octave = 4
+        // Divide each coefficient by 3 to produce more grainy noise.
+        half simplex3d_fractal(vec3 p) {
+            return 0.675 * simplex3d(p * rot1) + 0.225 * simplex3d(2.0 * p * rot2)
+                    + 0.075 * simplex3d(4.0 * p * rot3) + 0.025 * simplex3d(8.0 * p);
+        }
 
-            // Random rotations.
-            // The way you create fractal noise is layering simplex noise with some rotation.
-            // To make random cloud looking noise, the rotations should not align. (Otherwise it
-            // creates patterned noise).
-            // Below rotations only rotate in one axis.
-            const mat3 rot1 = mat3(1.0, 0. ,0., 0., 0.15, -0.98, 0., 0.98, 0.15);
-            const mat3 rot2 = mat3(-0.95, 0. ,-0.3, 0., 1., 0., 0.3, 0., -0.95);
-            const mat3 rot3 = mat3(1.0, 0. ,0., 0., -0.44, -0.89, 0., 0.89, -0.44);
-
-            // Octave = 4
-            // Divide each coefficient by 3 to produce more grainy noise.
-            half simplex3d_fractal(vec3 mat) {
-                return 0.675 * simplex3d(mat * rot1) + 0.225 * simplex3d(2.0 * mat * rot2)
-                        + 0.075 * simplex3d(4.0 * mat * rot3) + 0.025 * simplex3d(8.0 * mat);
-            }
-            """
-    }
+        // Screen blend
+        vec3 screen(vec3 dest, vec3 src) {
+            return dest + src - dest * src;
+        }
+    """
 }
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
index 0e22667..d1ba7c4 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
@@ -36,6 +36,7 @@
             uniform float in_aspectRatio;
             uniform float in_opacity;
             uniform float in_pixelDensity;
+            uniform float in_inverseLuma;
             layout(color) uniform vec4 in_color;
             layout(color) uniform vec4 in_backgroundColor;
         """
@@ -47,7 +48,7 @@
                 uv.x *= in_aspectRatio;
 
                 vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum;
-                float luma = simplex3d(noiseP) * in_opacity;
+                float luma = abs(in_inverseLuma - simplex3d(noiseP)) * in_opacity;
                 vec3 mask = maskLuminosity(in_color.rgb, luma);
                 vec3 color = in_backgroundColor.rgb + mask * 0.6;
 
@@ -69,7 +70,7 @@
                 uv.x *= in_aspectRatio;
 
                 vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum;
-                float luma = simplex3d_fractal(noiseP) * in_opacity;
+                float luma = abs(in_inverseLuma - simplex3d_fractal(noiseP)) * in_opacity;
                 vec3 mask = maskLuminosity(in_color.rgb, luma);
                 vec3 color = in_backgroundColor.rgb + mask * 0.6;
 
@@ -123,6 +124,17 @@
         setFloatUniform("in_aspectRatio", width / max(height, 0.001f))
     }
 
+    /**
+     * Sets whether to inverse the luminosity of the noise.
+     *
+     * By default noise will be used as a luma matte as is. This means that you will see color in
+     * the brighter area. If you want to invert it, meaning blend color onto the darker side, set to
+     * true.
+     */
+    fun setInverseNoiseLuminosity(inverse: Boolean) {
+        setFloatUniform("in_inverseLuma", if (inverse) 1f else 0f)
+    }
+
     /** Current noise movements in x, y, and z axes. */
     var noiseOffsetX: Float = 0f
         private set
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/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/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index 00c0a0b..e73afe7 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -29,14 +29,15 @@
 import com.android.systemui.plugins.ClockProvider
 import com.android.systemui.plugins.ClockProviderPlugin
 import com.android.systemui.plugins.ClockSettings
+import com.android.systemui.plugins.PluginLifecycleManager
 import com.android.systemui.plugins.PluginListener
 import com.android.systemui.plugins.PluginManager
 import com.android.systemui.util.Assert
+import java.util.concurrent.atomic.AtomicBoolean
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
 
-private val TAG = ClockRegistry::class.simpleName!!
 private const val DEBUG = true
 private val KEY_TIMESTAMP = "appliedTimestamp"
 
@@ -51,7 +52,10 @@
     val handleAllUsers: Boolean,
     defaultClockProvider: ClockProvider,
     val fallbackClockId: ClockId = DEFAULT_CLOCK_ID,
+    val keepAllLoaded: Boolean,
+    val subTag: String,
 ) {
+    private val TAG = "${ClockRegistry::class.simpleName} ($subTag)"
     interface ClockChangeListener {
         // Called when the active clock changes
         fun onCurrentClockChanged() {}
@@ -76,11 +80,85 @@
 
     private val pluginListener =
         object : PluginListener<ClockProviderPlugin> {
-            override fun onPluginConnected(plugin: ClockProviderPlugin, context: Context) =
-                connectClocks(plugin)
+            override fun onPluginAttached(manager: PluginLifecycleManager<ClockProviderPlugin>) {
+                manager.loadPlugin()
+            }
 
-            override fun onPluginDisconnected(plugin: ClockProviderPlugin) =
-                disconnectClocks(plugin)
+            override fun onPluginLoaded(
+                plugin: ClockProviderPlugin,
+                pluginContext: Context,
+                manager: PluginLifecycleManager<ClockProviderPlugin>
+            ) {
+                var isClockListChanged = false
+                for (clock in plugin.getClocks()) {
+                    val id = clock.clockId
+                    var isNew = false
+                    val info =
+                        availableClocks.getOrPut(id) {
+                            isNew = true
+                            ClockInfo(clock, plugin, manager)
+                        }
+
+                    if (isNew) {
+                        isClockListChanged = true
+                        onConnected(id)
+                    }
+
+                    if (manager != info.manager) {
+                        Log.e(
+                            TAG,
+                            "Clock Id conflict on load: $id is registered to another provider"
+                        )
+                        continue
+                    }
+
+                    info.provider = plugin
+                    onLoaded(id)
+                }
+
+                if (isClockListChanged) {
+                    triggerOnAvailableClocksChanged()
+                }
+                verifyLoadedProviders()
+            }
+
+            override fun onPluginUnloaded(
+                plugin: ClockProviderPlugin,
+                manager: PluginLifecycleManager<ClockProviderPlugin>
+            ) {
+                for (clock in plugin.getClocks()) {
+                    val id = clock.clockId
+                    val info = availableClocks[id]
+                    if (info?.manager != manager) {
+                        Log.e(
+                            TAG,
+                            "Clock Id conflict on unload: $id is registered to another provider"
+                        )
+                        continue
+                    }
+                    info.provider = null
+                    onUnloaded(id)
+                }
+
+                verifyLoadedProviders()
+            }
+
+            override fun onPluginDetached(manager: PluginLifecycleManager<ClockProviderPlugin>) {
+                val removed = mutableListOf<ClockId>()
+                availableClocks.entries.removeAll {
+                    if (it.value.manager != manager) {
+                        return@removeAll false
+                    }
+
+                    removed.add(it.key)
+                    return@removeAll true
+                }
+
+                removed.forEach(::onDisconnected)
+                if (removed.size > 0) {
+                    triggerOnAvailableClocksChanged()
+                }
+            }
         }
 
     private val userSwitchObserver =
@@ -96,7 +174,8 @@
         protected set(value) {
             if (field != value) {
                 field = value
-                scope.launch(mainDispatcher) { onClockChanged { it.onCurrentClockChanged() } }
+                verifyLoadedProviders()
+                triggerOnCurrentClockChanged()
             }
         }
 
@@ -168,9 +247,36 @@
         Assert.isNotMainThread()
     }
 
-    private fun onClockChanged(func: (ClockChangeListener) -> Unit) {
-        assertMainThread()
-        clockChangeListeners.forEach(func)
+    private var isClockChanged = AtomicBoolean(false)
+    private fun triggerOnCurrentClockChanged() {
+        val shouldSchedule = isClockChanged.compareAndSet(false, true)
+        if (!shouldSchedule) {
+            return
+        }
+
+        android.util.Log.e("HAWK", "triggerOnCurrentClockChanged")
+        scope.launch(mainDispatcher) {
+            assertMainThread()
+            android.util.Log.e("HAWK", "isClockChanged")
+            isClockChanged.set(false)
+            clockChangeListeners.forEach { it.onCurrentClockChanged() }
+        }
+    }
+
+    private var isClockListChanged = AtomicBoolean(false)
+    private fun triggerOnAvailableClocksChanged() {
+        val shouldSchedule = isClockListChanged.compareAndSet(false, true)
+        if (!shouldSchedule) {
+            return
+        }
+
+        android.util.Log.e("HAWK", "triggerOnAvailableClocksChanged")
+        scope.launch(mainDispatcher) {
+            assertMainThread()
+            android.util.Log.e("HAWK", "isClockListChanged")
+            isClockListChanged.set(false)
+            clockChangeListeners.forEach { it.onAvailableClocksChanged() }
+        }
     }
 
     public fun mutateSetting(mutator: (ClockSettings) -> ClockSettings) {
@@ -190,7 +296,12 @@
         }
 
     init {
-        connectClocks(defaultClockProvider)
+        // Register default clock designs
+        for (clock in defaultClockProvider.getClocks()) {
+            availableClocks[clock.clockId] = ClockInfo(clock, defaultClockProvider, null)
+        }
+
+        // Something has gone terribly wrong if the default clock isn't present
         if (!availableClocks.containsKey(DEFAULT_CLOCK_ID)) {
             throw IllegalArgumentException(
                 "$defaultClockProvider did not register clock at $DEFAULT_CLOCK_ID"
@@ -244,59 +355,87 @@
         }
     }
 
-    private fun connectClocks(provider: ClockProvider) {
-        var isAvailableChanged = false
-        val currentId = currentClockId
-        for (clock in provider.getClocks()) {
-            val id = clock.clockId
-            val current = availableClocks[id]
-            if (current != null) {
-                Log.e(
-                    TAG,
-                    "Clock Id conflict: $id is registered by both " +
-                        "${provider::class.simpleName} and ${current.provider::class.simpleName}"
-                )
-                continue
-            }
-
-            availableClocks[id] = ClockInfo(clock, provider)
-            isAvailableChanged = true
-            if (DEBUG) {
-                Log.i(TAG, "Added ${clock.clockId}")
-            }
-
-            if (currentId == id) {
-                if (DEBUG) {
-                    Log.i(TAG, "Current clock ($currentId) was connected")
-                }
-                onClockChanged { it.onCurrentClockChanged() }
-            }
+    private var isVerifying = AtomicBoolean(false)
+    private fun verifyLoadedProviders() {
+        val shouldSchedule = isVerifying.compareAndSet(false, true)
+        if (!shouldSchedule) {
+            return
         }
 
-        if (isAvailableChanged) {
-            onClockChanged { it.onAvailableClocksChanged() }
+        scope.launch(bgDispatcher) {
+            if (keepAllLoaded) {
+                // Enforce that all plugins are loaded if requested
+                for ((_, info) in availableClocks) {
+                    info.manager?.loadPlugin()
+                }
+                isVerifying.set(false)
+                return@launch
+            }
+
+            val currentClock = availableClocks[currentClockId]
+            if (currentClock == null) {
+                // Current Clock missing, load no plugins and use default
+                for ((_, info) in availableClocks) {
+                    info.manager?.unloadPlugin()
+                }
+                isVerifying.set(false)
+                return@launch
+            }
+
+            val currentManager = currentClock.manager
+            currentManager?.loadPlugin()
+
+            for ((_, info) in availableClocks) {
+                val manager = info.manager
+                if (manager != null && manager.isLoaded && currentManager != manager) {
+                    manager.unloadPlugin()
+                }
+            }
+            isVerifying.set(false)
         }
     }
 
-    private fun disconnectClocks(provider: ClockProvider) {
-        var isAvailableChanged = false
-        val currentId = currentClockId
-        for (clock in provider.getClocks()) {
-            availableClocks.remove(clock.clockId)
-            isAvailableChanged = true
-
-            if (DEBUG) {
-                Log.i(TAG, "Removed ${clock.clockId}")
-            }
-
-            if (currentId == clock.clockId) {
-                Log.w(TAG, "Current clock ($currentId) was disconnected")
-                onClockChanged { it.onCurrentClockChanged() }
-            }
+    private fun onConnected(clockId: ClockId) {
+        if (DEBUG) {
+            Log.i(TAG, "Connected $clockId")
         }
 
-        if (isAvailableChanged) {
-            onClockChanged { it.onAvailableClocksChanged() }
+        if (currentClockId == clockId) {
+            if (DEBUG) {
+                Log.i(TAG, "Current clock ($clockId) was connected")
+            }
+        }
+    }
+
+    private fun onLoaded(clockId: ClockId) {
+        if (DEBUG) {
+            Log.i(TAG, "Loaded $clockId")
+        }
+
+        if (currentClockId == clockId) {
+            Log.i(TAG, "Current clock ($clockId) was loaded")
+            triggerOnCurrentClockChanged()
+        }
+    }
+
+    private fun onUnloaded(clockId: ClockId) {
+        if (DEBUG) {
+            Log.i(TAG, "Unloaded $clockId")
+        }
+
+        if (currentClockId == clockId) {
+            Log.w(TAG, "Current clock ($clockId) was unloaded")
+            triggerOnCurrentClockChanged()
+        }
+    }
+
+    private fun onDisconnected(clockId: ClockId) {
+        if (DEBUG) {
+            Log.i(TAG, "Disconnected $clockId")
+        }
+
+        if (currentClockId == clockId) {
+            Log.w(TAG, "Current clock ($clockId) was disconnected")
         }
     }
 
@@ -345,6 +484,7 @@
 
     private data class ClockInfo(
         val metadata: ClockMetadata,
-        val provider: ClockProvider,
+        var provider: ClockProvider?,
+        val manager: PluginLifecycleManager<ClockProviderPlugin>?,
     )
 }
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
index 0d88075..f9e8aaf 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
@@ -184,6 +184,9 @@
         /** Flag denoting whether the Monochromatic Theme is enabled. */
         const val FLAG_NAME_MONOCHROMATIC_THEME = "is_monochromatic_theme_enabled"
 
+        /** Flag denoting AI Wallpapers are enabled in wallpaper picker. */
+        const val FLAG_NAME_WALLPAPER_PICKER_UI_FOR_AIWP = "wallpaper_picker_ui_for_aiwp"
+
         object Columns {
             /** String. Unique ID for the flag. */
             const val NAME = "name"
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java
index 70b5d73..b7088d5 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java
@@ -50,6 +50,13 @@
     boolean isPulsing();
 
     /**
+     * Is device dreaming. This method is more inclusive than
+     * {@link android.service.dreams.IDreamManager.isDreaming}, as it will return true during the
+     * dream's wake-up phase.
+     */
+    boolean isDreaming();
+
+    /**
      * Adds a state listener
      */
     void addCallback(StateListener listener);
diff --git a/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginLifecycleManager.java b/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginLifecycleManager.java
new file mode 100644
index 0000000..cc6a46f
--- /dev/null
+++ b/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginLifecycleManager.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.plugins;
+
+/**
+ * Provides the ability for consumers to control plugin lifecycle.
+ *
+ * @param <T> is the target plugin type
+ */
+public interface PluginLifecycleManager<T extends Plugin> {
+    /** Returns the currently loaded plugin instance (if plugin is loaded) */
+    T getPlugin();
+
+    /** returns true if the plugin is currently loaded */
+    default boolean isLoaded() {
+        return getPlugin() != null;
+    }
+
+    /**
+     * Loads and creates the plugin instance if it does not exist.
+     *
+     * This will trigger {@link PluginListener#onPluginLoaded} with the new instance if it did not
+     * already exist.
+     */
+    void loadPlugin();
+
+    /**
+     * Unloads and destroys the plugin instance if it exists.
+     *
+     * This will trigger {@link PluginListener#onPluginUnloaded} if a concrete plugin instance
+     * existed when this call was made.
+     */
+    void unloadPlugin();
+}
diff --git a/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginListener.java b/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginListener.java
index b488d2a..c5f5032 100644
--- a/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginListener.java
+++ b/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginListener.java
@@ -17,7 +17,32 @@
 import android.content.Context;
 
 /**
- * Interface for listening to plugins being connected.
+ * Interface for listening to plugins being connected and disconnected.
+ *
+ * The call order for a plugin is
+ *  1) {@link #onPluginAttached}
+ *          Called when a new plugin is added to the device, or an existing plugin was replaced by
+ *          the package manager. Will only be called once per package manager event. If multiple
+ *          non-conflicting packages which have the same plugin interface are installed on the
+ *          device, then this method can be called multiple times with different instances of
+ *          {@link PluginLifecycleManager} (as long as `allowMultiple` was set to true when the
+ *          listener was registered with {@link PluginManager#addPluginListener}).
+ *  2) {@link #onPluginLoaded}
+ *          Called whenever a new instance of the plugin object is created and ready for use. Can be
+ *          called multiple times per {@link PluginLifecycleManager}, but will always pass a newly
+ *          created plugin object. {@link #onPluginUnloaded} with the previous plugin object will
+ *          be called before another call to {@link #onPluginLoaded} is made. This method will be
+ *          called once automatically after {@link #onPluginAttached}. Besides the initial call,
+ *          {@link #onPluginLoaded} will occur due to {@link PluginLifecycleManager#loadPlugin}.
+ *  3) {@link #onPluginUnloaded}
+ *          Called when a request to unload the plugin has been received. This can be triggered from
+ *          a related call to {@link PluginLifecycleManager#unloadPlugin} or for any reason that
+ *          {@link #onPluginDetached} would be triggered.
+ *  4) {@link #onPluginDetached}
+ *          Called when the package is removed from the device, disabled, or replaced due to an
+ *          external trigger. These are events from the android package manager.
+ *
+ * @param <T> is the target plugin type
  */
 public interface PluginListener<T extends Plugin> {
     /**
@@ -25,14 +50,69 @@
      * This may be called multiple times if multiple plugins are allowed.
      * It may also be called in the future if the plugin package changes
      * and needs to be reloaded.
+     *
+     * @deprecated Migrate to {@link #onPluginLoaded} or {@link #onPluginAttached}
      */
-    void onPluginConnected(T plugin, Context pluginContext);
+    @Deprecated
+    default void onPluginConnected(T plugin, Context pluginContext) {
+        // Optional
+    }
+
+    /**
+     * Called when the plugin is first attached to the host application. {@link #onPluginLoaded}
+     * will be automatically called as well when first attached. This may be called multiple times
+     * if multiple plugins are allowed. It may also be called in the future if the plugin package
+     * changes and needs to be reloaded. Each call to {@link #onPluginAttached} will provide a new
+     * or different {@link PluginLifecycleManager}.
+     */
+    default void onPluginAttached(PluginLifecycleManager<T> manager) {
+        // Optional
+    }
 
     /**
      * Called when a plugin has been uninstalled/updated and should be removed
      * from use.
+     *
+     * @deprecated Migrate to {@link #onPluginDetached} or {@link #onPluginUnloaded}
      */
+    @Deprecated
     default void onPluginDisconnected(T plugin) {
         // Optional.
     }
-}
+
+    /**
+     * Called when the plugin has been detached from the host application. Implementers should no
+     * longer attempt to reload it via this {@link PluginLifecycleManager}. If the package was
+     * updated and not removed, then {@link #onPluginAttached} will be called again when the updated
+     * package is available.
+     */
+    default void onPluginDetached(PluginLifecycleManager<T> manager) {
+        // Optional.
+    }
+
+    /**
+     * Called when the plugin is loaded into the host's process and is available for use. This can
+     * happen several times if clients are using {@link PluginLifecycleManager} to manipulate a
+     * plugin's load state. Each call to {@link #onPluginLoaded} will have a matched call to
+     * {@link #onPluginUnloaded} when that plugin object should no longer be used.
+     */
+    default void onPluginLoaded(
+            T plugin,
+            Context pluginContext,
+            PluginLifecycleManager<T> manager
+    ) {
+        // Optional, default to deprecated version
+        onPluginConnected(plugin, pluginContext);
+    }
+
+    /**
+     * Called when the plugin should no longer be used. Listeners should clean up all references to
+     * the relevant plugin so that it can be garbage collected. If the plugin object is required in
+     * the future a call can be made to {@link PluginLifecycleManager#loadPlugin} to create a new
+     * plugin object and trigger {@link #onPluginLoaded}.
+     */
+    default void onPluginUnloaded(T plugin, PluginLifecycleManager<T> manager) {
+        // Optional, default to deprecated version
+        onPluginDisconnected(plugin);
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/activity_rear_display_education.xml b/packages/SystemUI/res/layout/activity_rear_display_education.xml
index 094807e..c295cfe 100644
--- a/packages/SystemUI/res/layout/activity_rear_display_education.xml
+++ b/packages/SystemUI/res/layout/activity_rear_display_education.xml
@@ -30,6 +30,7 @@
 
             <com.airbnb.lottie.LottieAnimationView
                 android:id="@+id/rear_display_folded_animation"
+                android:importantForAccessibility="no"
                 android:layout_width="@dimen/rear_display_animation_width"
                 android:layout_height="@dimen/rear_display_animation_height"
                 android:layout_gravity="center"
diff --git a/packages/SystemUI/res/layout/activity_rear_display_education_opened.xml b/packages/SystemUI/res/layout/activity_rear_display_education_opened.xml
index e970bc5..c12bfcc 100644
--- a/packages/SystemUI/res/layout/activity_rear_display_education_opened.xml
+++ b/packages/SystemUI/res/layout/activity_rear_display_education_opened.xml
@@ -31,6 +31,7 @@
 
         <com.airbnb.lottie.LottieAnimationView
             android:id="@+id/rear_display_folded_animation"
+            android:importantForAccessibility="no"
             android:layout_width="@dimen/rear_display_animation_width"
             android:layout_height="@dimen/rear_display_animation_height"
             android:layout_gravity="center"
diff --git a/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml b/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml
index 5b2ec48..0cd0623 100644
--- a/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml
@@ -14,22 +14,15 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
 -->
-<FrameLayout
+<com.android.systemui.animation.view.LaunchableImageView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_height="wrap_content"
-    android:layout_width="wrap_content"
-    android:paddingVertical="@dimen/dream_overlay_complication_home_controls_padding">
-
-    <com.android.systemui.animation.view.LaunchableImageView
-        android:id="@+id/home_controls_chip"
-        android:layout_height="@dimen/dream_overlay_bottom_affordance_height"
-        android:layout_width="@dimen/dream_overlay_bottom_affordance_width"
-        android:layout_gravity="bottom|start"
-        android:padding="@dimen/dream_overlay_bottom_affordance_padding"
-        android:background="@drawable/dream_overlay_bottom_affordance_bg"
-        android:scaleType="fitCenter"
-        android:tint="?android:attr/textColorPrimary"
-        android:src="@drawable/controls_icon"
-        android:contentDescription="@string/quick_controls_title" />
-
-</FrameLayout>
+    android:id="@+id/home_controls_chip"
+    android:layout_height="@dimen/dream_overlay_bottom_affordance_height"
+    android:layout_width="@dimen/dream_overlay_bottom_affordance_width"
+    android:layout_gravity="bottom|start"
+    android:padding="@dimen/dream_overlay_bottom_affordance_padding"
+    android:background="@drawable/dream_overlay_bottom_affordance_bg"
+    android:scaleType="fitCenter"
+    android:tint="?android:attr/textColorPrimary"
+    android:src="@drawable/controls_icon"
+    android:contentDescription="@string/quick_controls_title" />
diff --git a/packages/SystemUI/res/layout/dream_overlay_media_entry_chip.xml b/packages/SystemUI/res/layout/dream_overlay_media_entry_chip.xml
index 50f3ffc..b75c638 100644
--- a/packages/SystemUI/res/layout/dream_overlay_media_entry_chip.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_media_entry_chip.xml
@@ -17,13 +17,12 @@
 <ImageView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/media_entry_chip"
-    android:layout_height="@dimen/keyguard_affordance_fixed_height"
-    android:layout_width="@dimen/keyguard_affordance_fixed_width"
+    android:layout_height="@dimen/dream_overlay_bottom_affordance_height"
+    android:layout_width="@dimen/dream_overlay_bottom_affordance_width"
     android:layout_gravity="bottom|start"
-    android:scaleType="center"
+    android:scaleType="fitCenter"
+    android:padding="@dimen/dream_overlay_bottom_affordance_padding"
     android:tint="?android:attr/textColorPrimary"
     android:src="@drawable/ic_music_note"
-    android:background="@drawable/keyguard_bottom_affordance_bg"
-    android:layout_marginStart="@dimen/keyguard_affordance_horizontal_offset"
-    android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
+    android:background="@drawable/dream_overlay_bottom_affordance_bg"
     android:contentDescription="@string/controls_media_title" />
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/notification_conversation_info.xml b/packages/SystemUI/res/layout/notification_conversation_info.xml
index 9b2f0ac..79948da 100644
--- a/packages/SystemUI/res/layout/notification_conversation_info.xml
+++ b/packages/SystemUI/res/layout/notification_conversation_info.xml
@@ -311,7 +311,7 @@
                     android:clickable="false"
                     android:focusable="false"
                     android:ellipsize="end"
-                    android:maxLines="3"
+                    android:maxLines="4"
                     android:textAppearance="@style/TextAppearance.NotificationImportanceDetail"/>
             </com.android.systemui.statusbar.notification.row.ButtonLinearLayout>
 
@@ -364,7 +364,7 @@
                     android:clickable="false"
                     android:focusable="false"
                     android:ellipsize="end"
-                    android:maxLines="3"
+                    android:maxLines="4"
                     android:textAppearance="@style/TextAppearance.NotificationImportanceDetail"/>
             </com.android.systemui.statusbar.notification.row.ButtonLinearLayout>
 
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 e5cd0c5..082f385 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -99,6 +99,7 @@
         <item>accessibility_display_daltonizer_enabled:color_correction</item>
         <item>accessibility_display_inversion_enabled:inversion</item>
         <item>one_handed_mode_enabled:onehanded</item>
+        <item>accessibility_font_scaling_has_been_changed:font_scaling</item>
     </string-array>
 
     <!-- Use collapsed layout for media player in landscape QQS -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 4d38541..ee9e07a 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -43,8 +43,6 @@
     <dimen name="navigation_edge_panel_height">268dp</dimen>
     <!-- The threshold to drag to trigger the edge action -->
     <dimen name="navigation_edge_action_drag_threshold">16dp</dimen>
-    <!-- The drag distance to consider evaluating gesture -->
-    <dimen name="navigation_edge_action_min_distance_to_start_animation">24dp</dimen>
     <!-- The threshold to progress back animation for edge swipe -->
     <dimen name="navigation_edge_action_progress_threshold">412dp</dimen>
     <!-- The minimum display position of the arrow on the screen -->
@@ -58,8 +56,6 @@
 
     <!-- The thickness of the arrow -->
     <dimen name="navigation_edge_arrow_thickness">4dp</dimen>
-    <!-- The minimum delta needed to change direction / stop triggering back -->
-    <dimen name="navigation_edge_minimum_x_delta_for_switch">32dp</dimen>
 
     <!-- entry state -->
     <item name="navigation_edge_entry_scale" format="float" type="dimen">0.98</item>
@@ -1636,7 +1632,6 @@
     <dimen name="dream_overlay_bottom_affordance_padding">14dp</dimen>
     <dimen name="dream_overlay_complication_clock_time_text_size">86dp</dimen>
     <dimen name="dream_overlay_complication_clock_time_translation_y">28dp</dimen>
-    <dimen name="dream_overlay_complication_home_controls_padding">28dp</dimen>
     <dimen name="dream_overlay_complication_clock_subtitle_text_size">24sp</dimen>
     <dimen name="dream_overlay_complication_preview_text_size">36sp</dimen>
     <dimen name="dream_overlay_complication_preview_icon_padding">28dp</dimen>
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index 6354752..763930d 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -30,9 +30,6 @@
 
     <bool name="flag_charging_ripple">false</bool>
 
-    <!-- Whether to show chipbar UI whenever the device is unlocked by ActiveUnlock. -->
-    <bool name="flag_active_unlock_chipbar">true</bool>
-
     <!--  Whether the user switcher chip shows in the status bar. When true, the multi user
       avatar will no longer show on the lockscreen -->
     <bool name="flag_user_switcher_chip">false</bool>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index a02c429..2aa912c 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] -->
@@ -2877,7 +2877,7 @@
     <string name="manage_users">Manage users</string>
 
     <!-- Toast shown when a notification does not support dragging to split [CHAR LIMIT=NONE] -->
-    <string name="drag_split_not_supported">This notification does not support dragging to Splitscreen.</string>
+    <string name="drag_split_not_supported">This notification does not support dragging to split screen</string>
 
     <!-- Content description for the Wi-Fi off icon in the dream overlay status bar [CHAR LIMIT=NONE] -->
     <string name="dream_overlay_status_bar_wifi_off">Wi\u2011Fi unavailable</string>
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt
index 9766514..dedf0a7 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt
@@ -53,6 +53,7 @@
 fun View.captureToBitmap(window: Window? = null): ListenableFuture<Bitmap> {
     val bitmapFuture: ResolvableFuture<Bitmap> = ResolvableFuture.create()
     val mainExecutor = HandlerExecutor(Handler(Looper.getMainLooper()))
+    val isRobolectric = if (Build.FINGERPRINT.contains("robolectric")) true else false
 
     // disable drawing again if necessary once work is complete
     if (!HardwareRendererCompat.isDrawingEnabled()) {
@@ -61,8 +62,12 @@
     }
 
     mainExecutor.execute {
-        val forceRedrawFuture = forceRedraw()
-        forceRedrawFuture.addListener({ generateBitmap(bitmapFuture, window) }, mainExecutor)
+        if (isRobolectric) {
+            generateBitmap(bitmapFuture)
+        } else {
+            val forceRedrawFuture = forceRedraw()
+            forceRedrawFuture.addListener({ generateBitmap(bitmapFuture, window) }, mainExecutor)
+        }
     }
 
     return bitmapFuture
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
index 2d47356..f96d1e3 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
@@ -19,6 +19,7 @@
 import android.app.Activity
 import android.app.Dialog
 import android.graphics.Bitmap
+import android.os.Build
 import android.view.View
 import android.view.ViewGroup
 import android.view.ViewGroup.LayoutParams
@@ -26,6 +27,7 @@
 import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
 import androidx.activity.ComponentActivity
 import androidx.test.ext.junit.rules.ActivityScenarioRule
+import java.util.concurrent.TimeUnit
 import org.junit.Assert.assertEquals
 import org.junit.rules.RuleChain
 import org.junit.rules.TestRule
@@ -54,14 +56,14 @@
             )
         )
     private val activityRule = ActivityScenarioRule(ScreenshotActivity::class.java)
-    private val delegateRule =
-        RuleChain.outerRule(colorsRule)
-            .around(deviceEmulationRule)
-            .around(screenshotRule)
-            .around(activityRule)
+    private val roboRule =
+        RuleChain.outerRule(deviceEmulationRule).around(screenshotRule).around(activityRule)
+    private val delegateRule = RuleChain.outerRule(colorsRule).around(roboRule)
+    private val isRobolectric = if (Build.FINGERPRINT.contains("robolectric")) true else false
 
     override fun apply(base: Statement, description: Description): Statement {
-        return delegateRule.apply(base, description)
+        val ruleToApply = if (isRobolectric) roboRule else delegateRule
+        return ruleToApply.apply(base, description)
     }
 
     protected fun takeScreenshot(
@@ -94,7 +96,12 @@
             contentView = content.getChildAt(0)
         }
 
-        return contentView?.toBitmap() ?: error("contentView is null")
+        return if (isRobolectric) {
+            contentView?.captureToBitmap()?.get(10, TimeUnit.SECONDS)
+                ?: error("timeout while trying to capture view to bitmap")
+        } else {
+            contentView?.toBitmap() ?: error("contentView is null")
+        }
     }
 
     /**
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java
index cc3d7a8..3d05542 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java
@@ -210,12 +210,12 @@
     private void onPluginConnected(PluginInstance<T> pluginInstance) {
         if (DEBUG) Log.d(TAG, "onPluginConnected");
         PluginPrefs.setHasPlugins(mContext);
-        pluginInstance.onCreate(mContext, mListener);
+        pluginInstance.onCreate();
     }
 
     private void onPluginDisconnected(PluginInstance<T> pluginInstance) {
         if (DEBUG) Log.d(TAG, "onPluginDisconnected");
-        pluginInstance.onDestroy(mListener);
+        pluginInstance.onDestroy();
     }
 
     private void queryAll() {
@@ -312,7 +312,7 @@
             try {
                 return mPluginInstanceFactory.create(
                         mContext, appInfo, component,
-                        mPluginClass);
+                        mPluginClass, mListener);
             } catch (InvalidVersionException e) {
                 reportInvalidVersion(component, component.getClassName(), e);
             }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
index 2f84602..016d573 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
@@ -21,13 +21,16 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.text.TextUtils;
-import android.util.ArrayMap;
 import android.util.Log;
 
+import androidx.annotation.Nullable;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.plugins.Plugin;
 import com.android.systemui.plugins.PluginFragment;
+import com.android.systemui.plugins.PluginLifecycleManager;
 import com.android.systemui.plugins.PluginListener;
 
 import dalvik.system.PathClassLoader;
@@ -35,7 +38,7 @@
 import java.io.File;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Map;
+import java.util.function.Supplier;
 
 /**
  * Contains a single instantiation of a Plugin.
@@ -45,42 +48,102 @@
  *
  * @param <T> The type of plugin that this contains.
  */
-public class PluginInstance<T extends Plugin> {
+public class PluginInstance<T extends Plugin> implements PluginLifecycleManager {
     private static final String TAG = "PluginInstance";
-    private static final Map<String, ClassLoader> sClassLoaders = new ArrayMap<>();
 
-    private final Context mPluginContext;
-    private final VersionInfo mVersionInfo;
+    private final Context mAppContext;
+    private final PluginListener<T> mListener;
     private final ComponentName mComponentName;
-    private final T mPlugin;
+    private final PluginFactory<T> mPluginFactory;
+
+    private Context mPluginContext;
+    private T mPlugin;
 
     /** */
-    public PluginInstance(ComponentName componentName, T plugin, Context pluginContext,
-            VersionInfo versionInfo) {
+    public PluginInstance(
+            Context appContext,
+            PluginListener<T> listener,
+            ComponentName componentName,
+            PluginFactory<T> pluginFactory,
+            @Nullable T plugin) {
+        mAppContext = appContext;
+        mListener = listener;
         mComponentName = componentName;
+        mPluginFactory = pluginFactory;
         mPlugin = plugin;
-        mPluginContext = pluginContext;
-        mVersionInfo = versionInfo;
+
+        if (mPlugin != null) {
+            mPluginContext = mPluginFactory.createPluginContext();
+        }
     }
 
     /** Alerts listener and plugin that the plugin has been created. */
-    public void onCreate(Context appContext, PluginListener<T> listener) {
-        if (!(mPlugin instanceof PluginFragment)) {
-            // Only call onCreate for plugins that aren't fragments, as fragments
-            // will get the onCreate as part of the fragment lifecycle.
-            mPlugin.onCreate(appContext, mPluginContext);
+    public void onCreate() {
+        mListener.onPluginAttached(this);
+        if (mPlugin == null) {
+            loadPlugin();
+        } else {
+            if (!(mPlugin instanceof PluginFragment)) {
+                // Only call onCreate for plugins that aren't fragments, as fragments
+                // will get the onCreate as part of the fragment lifecycle.
+                mPlugin.onCreate(mAppContext, mPluginContext);
+            }
+            mListener.onPluginLoaded(mPlugin, mPluginContext, this);
         }
-        listener.onPluginConnected(mPlugin, mPluginContext);
     }
 
     /** Alerts listener and plugin that the plugin is being shutdown. */
-    public void onDestroy(PluginListener<T> listener) {
-        listener.onPluginDisconnected(mPlugin);
+    public void onDestroy() {
+        unloadPlugin();
+        mListener.onPluginDetached(this);
+    }
+
+    /** Returns the current plugin instance (if it is loaded). */
+    @Nullable
+    public T getPlugin() {
+        return mPlugin;
+    }
+
+    /**
+     * Loads and creates the plugin if it does not exist.
+     */
+    public void loadPlugin() {
+        if (mPlugin != null) {
+            return;
+        }
+
+        mPlugin = mPluginFactory.createPlugin();
+        mPluginContext = mPluginFactory.createPluginContext();
+        if (mPlugin == null || mPluginContext == null) {
+            return;
+        }
+
+        if (!(mPlugin instanceof PluginFragment)) {
+            // Only call onCreate for plugins that aren't fragments, as fragments
+            // will get the onCreate as part of the fragment lifecycle.
+            mPlugin.onCreate(mAppContext, mPluginContext);
+        }
+        mListener.onPluginLoaded(mPlugin, mPluginContext, this);
+    }
+
+    /**
+     * Unloads and destroys the current plugin instance if it exists.
+     *
+     * This will free the associated memory if there are not other references.
+     */
+    public void unloadPlugin() {
+        if (mPlugin == null) {
+            return;
+        }
+
+        mListener.onPluginUnloaded(mPlugin, this);
         if (!(mPlugin instanceof PluginFragment)) {
             // Only call onDestroy for plugins that aren't fragments, as fragments
             // will get the onDestroy as part of the fragment lifecycle.
             mPlugin.onDestroy();
         }
+        mPlugin = null;
+        mPluginContext = null;
     }
 
     /**
@@ -89,7 +152,7 @@
      * It does this by string comparison of the class names.
      **/
     public boolean containsPluginClass(Class pluginClass) {
-        return mPlugin.getClass().getName().equals(pluginClass.getName());
+        return mComponentName.getClassName().equals(pluginClass.getName());
     }
 
     public ComponentName getComponentName() {
@@ -101,7 +164,7 @@
     }
 
     public VersionInfo getVersionInfo() {
-        return mVersionInfo;
+        return mPluginFactory.checkVersion(mPlugin);
     }
 
     @VisibleForTesting
@@ -134,21 +197,20 @@
                 Context context,
                 ApplicationInfo appInfo,
                 ComponentName componentName,
-                Class<T> pluginClass)
+                Class<T> pluginClass,
+                PluginListener<T> listener)
                 throws PackageManager.NameNotFoundException, ClassNotFoundException,
                 InstantiationException, IllegalAccessException {
 
-            ClassLoader classLoader = getClassLoader(appInfo, mBaseClassLoader);
-            Context pluginContext = new PluginActionManager.PluginContextWrapper(
-                    context.createApplicationContext(appInfo, 0), classLoader);
-            Class<T> instanceClass = (Class<T>) Class.forName(
-                    componentName.getClassName(), true, classLoader);
+            PluginFactory<T> pluginFactory = new PluginFactory<T>(
+                    context, mInstanceFactory, appInfo, componentName, mVersionChecker, pluginClass,
+                    () -> getClassLoader(appInfo, mBaseClassLoader));
             // TODO: Only create the plugin before version check if we need it for
             // legacy version check.
-            T instance = (T) mInstanceFactory.create(instanceClass);
-            VersionInfo version = mVersionChecker.checkVersion(
-                    instanceClass, pluginClass, instance);
-            return new PluginInstance<T>(componentName, instance, pluginContext, version);
+            T instance = pluginFactory.createPlugin();
+            pluginFactory.checkVersion(instance);
+            return new PluginInstance<T>(
+                    context, listener, componentName, pluginFactory, instance);
         }
 
         private boolean isPluginPackagePrivileged(String packageName) {
@@ -179,9 +241,6 @@
                         + appInfo.sourceDir + ", pkg: " + appInfo.packageName);
                 return null;
             }
-            if (sClassLoaders.containsKey(appInfo.packageName)) {
-                return sClassLoaders.get(appInfo.packageName);
-            }
 
             List<String> zipPaths = new ArrayList<>();
             List<String> libPaths = new ArrayList<>();
@@ -190,13 +249,20 @@
                     TextUtils.join(File.pathSeparator, zipPaths),
                     TextUtils.join(File.pathSeparator, libPaths),
                     getParentClassLoader(baseClassLoader));
-            sClassLoaders.put(appInfo.packageName, classLoader);
             return classLoader;
         }
     }
 
     /** Class that compares a plugin class against an implementation for version matching. */
-    public static class VersionChecker {
+    public interface VersionChecker {
+        /** Compares two plugin classes. */
+        <T extends Plugin> VersionInfo checkVersion(
+                Class<T> instanceClass, Class<T> pluginClass, Plugin plugin);
+    }
+
+    /** Class that compares a plugin class against an implementation for version matching. */
+    public static class VersionCheckerImpl implements VersionChecker {
+        @Override
         /** Compares two plugin classes. */
         public <T extends Plugin> VersionInfo checkVersion(
                 Class<T> instanceClass, Class<T> pluginClass, Plugin plugin) {
@@ -204,7 +270,7 @@
             VersionInfo instanceVersion = new VersionInfo().addClass(instanceClass);
             if (instanceVersion.hasVersionInfo()) {
                 pluginVersion.checkVersion(instanceVersion);
-            } else {
+            } else if (plugin != null) {
                 int fallbackVersion = plugin.getVersion();
                 if (fallbackVersion != pluginVersion.getDefaultVersion()) {
                     throw new VersionInfo.InvalidVersionException("Invalid legacy version", false);
@@ -225,4 +291,74 @@
             return (T) cls.newInstance();
         }
     }
+
+    /**
+     * Instanced wrapper of InstanceFactory
+     *
+     * @param <T> is the type of the plugin object to be built
+     **/
+    public static class PluginFactory<T extends Plugin> {
+        private final Context mContext;
+        private final InstanceFactory<?> mInstanceFactory;
+        private final ApplicationInfo mAppInfo;
+        private final ComponentName mComponentName;
+        private final VersionChecker mVersionChecker;
+        private final Class<T> mPluginClass;
+        private final Supplier<ClassLoader> mClassLoaderFactory;
+
+        public PluginFactory(
+                Context context,
+                InstanceFactory<?> instanceFactory,
+                ApplicationInfo appInfo,
+                ComponentName componentName,
+                VersionChecker versionChecker,
+                Class<T> pluginClass,
+                Supplier<ClassLoader> classLoaderFactory) {
+            mContext = context;
+            mInstanceFactory = instanceFactory;
+            mAppInfo = appInfo;
+            mComponentName = componentName;
+            mVersionChecker = versionChecker;
+            mPluginClass = pluginClass;
+            mClassLoaderFactory = classLoaderFactory;
+        }
+
+        /** Creates the related plugin object from the factory */
+        public T createPlugin() {
+            try {
+                ClassLoader loader = mClassLoaderFactory.get();
+                Class<T> instanceClass = (Class<T>) Class.forName(
+                        mComponentName.getClassName(), true, loader);
+                return (T) mInstanceFactory.create(instanceClass);
+            } catch (ClassNotFoundException ex) {
+                Log.e(TAG, "Failed to load plugin", ex);
+            } catch (IllegalAccessException ex) {
+                Log.e(TAG, "Failed to load plugin", ex);
+            } catch (InstantiationException ex) {
+                Log.e(TAG, "Failed to load plugin", ex);
+            }
+            return null;
+        }
+
+        /** Creates a context wrapper for the plugin */
+        public Context createPluginContext() {
+            try {
+                ClassLoader loader = mClassLoaderFactory.get();
+                return new PluginActionManager.PluginContextWrapper(
+                    mContext.createApplicationContext(mAppInfo, 0), loader);
+            } catch (NameNotFoundException ex) {
+                Log.e(TAG, "Failed to create plugin context", ex);
+            }
+            return null;
+        }
+
+        /** Check Version and create VersionInfo for instance */
+        public VersionInfo checkVersion(T instance) {
+            if (instance == null) {
+                instance = createPlugin();
+            }
+            return mVersionChecker.checkVersion(
+                    (Class<T>) instance.getClass(), mPluginClass, instance);
+        }
+    }
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 0d36d04..1351314 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -177,42 +177,96 @@
 
     public static String getSystemUiStateString(int flags) {
         StringJoiner str = new StringJoiner("|");
-        str.add((flags & SYSUI_STATE_SCREEN_PINNING) != 0 ? "screen_pinned" : "");
-        str.add((flags & SYSUI_STATE_OVERVIEW_DISABLED) != 0 ? "overview_disabled" : "");
-        str.add((flags & SYSUI_STATE_HOME_DISABLED) != 0 ? "home_disabled" : "");
-        str.add((flags & SYSUI_STATE_SEARCH_DISABLED) != 0 ? "search_disabled" : "");
-        str.add((flags & SYSUI_STATE_NAV_BAR_HIDDEN) != 0 ? "navbar_hidden" : "");
-        str.add((flags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) != 0 ? "notif_visible" : "");
-        str.add((flags & SYSUI_STATE_QUICK_SETTINGS_EXPANDED) != 0 ? "qs_visible" : "");
-        str.add((flags & SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING) != 0 ? "keygrd_visible" : "");
-        str.add((flags & SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED) != 0
-                ? "keygrd_occluded" : "");
-        str.add((flags & SYSUI_STATE_BOUNCER_SHOWING) != 0 ? "bouncer_visible" : "");
-        str.add((flags & SYSUI_STATE_DIALOG_SHOWING) != 0 ? "dialog_showing" : "");
-        str.add((flags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0 ? "a11y_click" : "");
-        str.add((flags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0 ? "a11y_long_click" : "");
-        str.add((flags & SYSUI_STATE_TRACING_ENABLED) != 0 ? "tracing" : "");
-        str.add((flags & SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED) != 0
-                ? "asst_gesture_constrain" : "");
-        str.add((flags & SYSUI_STATE_BUBBLES_EXPANDED) != 0 ? "bubbles_expanded" : "");
-        str.add((flags & SYSUI_STATE_ONE_HANDED_ACTIVE) != 0 ? "one_handed_active" : "");
-        str.add((flags & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) != 0
-                ? "allow_gesture" : "");
-        str.add((flags & SYSUI_STATE_IME_SHOWING) != 0 ? "ime_visible" : "");
-        str.add((flags & SYSUI_STATE_MAGNIFICATION_OVERLAP) != 0 ? "magnification_overlap" : "");
-        str.add((flags & SYSUI_STATE_IME_SWITCHER_SHOWING) != 0 ? "ime_switcher_showing" : "");
-        str.add((flags & SYSUI_STATE_DEVICE_DOZING) != 0 ? "device_dozing" : "");
-        str.add((flags & SYSUI_STATE_BACK_DISABLED) != 0 ? "back_disabled" : "");
-        str.add((flags & SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED) != 0
-                ? "bubbles_mange_menu_expanded" : "");
-        str.add((flags & SYSUI_STATE_IMMERSIVE_MODE) != 0 ? "immersive_mode" : "");
-        str.add((flags & SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING) != 0 ? "vis_win_showing" : "");
-        str.add((flags & SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE) != 0
-                ? "freeform_active_in_desktop_mode" : "");
-        str.add((flags & SYSUI_STATE_DEVICE_DREAMING) != 0 ? "device_dreaming" : "");
-        str.add("screen_"
-                + ((flags & SYSUI_STATE_SCREEN_TRANSITION) != 0 ? "turning_" : "")
-                + ((flags & SYSUI_STATE_SCREEN_ON) != 0 ? "on" : "off"));
+        if ((flags & SYSUI_STATE_SCREEN_PINNING) != 0) {
+            str.add("screen_pinned");
+        }
+        if ((flags & SYSUI_STATE_OVERVIEW_DISABLED) != 0) {
+            str.add("overview_disabled");
+        }
+        if ((flags & SYSUI_STATE_HOME_DISABLED) != 0) {
+            str.add("home_disabled");
+        }
+        if ((flags & SYSUI_STATE_SEARCH_DISABLED) != 0) {
+            str.add("search_disabled");
+        }
+        if ((flags & SYSUI_STATE_NAV_BAR_HIDDEN) != 0) {
+            str.add("navbar_hidden");
+        }
+        if ((flags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) != 0) {
+            str.add("notif_visible");
+        }
+        if ((flags & SYSUI_STATE_QUICK_SETTINGS_EXPANDED) != 0) {
+            str.add("qs_visible");
+        }
+        if ((flags & SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING) != 0) {
+            str.add("keygrd_visible");
+        }
+        if ((flags & SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED) != 0) {
+            str.add("keygrd_occluded");
+        }
+        if ((flags & SYSUI_STATE_BOUNCER_SHOWING) != 0) {
+            str.add("bouncer_visible");
+        }
+        if ((flags & SYSUI_STATE_DIALOG_SHOWING) != 0) {
+            str.add("dialog_showing");
+        }
+        if ((flags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0) {
+            str.add("a11y_click");
+        }
+        if ((flags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0) {
+            str.add("a11y_long_click");
+        }
+        if ((flags & SYSUI_STATE_TRACING_ENABLED) != 0) {
+            str.add("tracing");
+        }
+        if ((flags & SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED) != 0) {
+            str.add("asst_gesture_constrain");
+        }
+        if ((flags & SYSUI_STATE_BUBBLES_EXPANDED) != 0) {
+            str.add("bubbles_expanded");
+        }
+        if ((flags & SYSUI_STATE_ONE_HANDED_ACTIVE) != 0) {
+            str.add("one_handed_active");
+        }
+        if ((flags & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) != 0) {
+            str.add("allow_gesture");
+        }
+        if ((flags & SYSUI_STATE_IME_SHOWING) != 0) {
+            str.add("ime_visible");
+        }
+        if ((flags & SYSUI_STATE_MAGNIFICATION_OVERLAP) != 0) {
+            str.add("magnification_overlap");
+        }
+        if ((flags & SYSUI_STATE_IME_SWITCHER_SHOWING) != 0) {
+            str.add("ime_switcher_showing");
+        }
+        if ((flags & SYSUI_STATE_DEVICE_DOZING) != 0) {
+            str.add("device_dozing");
+        }
+        if ((flags & SYSUI_STATE_BACK_DISABLED) != 0) {
+            str.add("back_disabled");
+        }
+        if ((flags & SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED) != 0) {
+            str.add("bubbles_mange_menu_expanded");
+        }
+        if ((flags & SYSUI_STATE_IMMERSIVE_MODE) != 0) {
+            str.add("immersive_mode");
+        }
+        if ((flags & SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING) != 0) {
+            str.add("vis_win_showing");
+        }
+        if ((flags & SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE) != 0) {
+            str.add("freeform_active_in_desktop_mode");
+        }
+        if ((flags & SYSUI_STATE_DEVICE_DREAMING) != 0) {
+            str.add("device_dreaming");
+        }
+        if ((flags & SYSUI_STATE_SCREEN_TRANSITION) != 0) {
+            str.add("screen_transition");
+        }
+        if ((flags & SYSUI_STATE_SCREEN_ON) != 0) {
+            str.add("screen_on");
+        }
 
         return str.toString();
     }
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/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index b3a641c..0e2f8f0 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -4125,6 +4125,19 @@
                     KeyguardFingerprintListenModel.TABLE_HEADERS,
                     mFingerprintListenBuffer.toList()
             ).printTableData(pw);
+        } else if (mFpm != null && mFingerprintSensorProperties.isEmpty()) {
+            final int userId = mUserTracker.getUserId();
+            pw.println("  Fingerprint state (user=" + userId + ")");
+            pw.println("    mFingerprintSensorProperties.isEmpty="
+                    + mFingerprintSensorProperties.isEmpty());
+            pw.println("    mFpm.isHardwareDetected="
+                    + mFpm.isHardwareDetected());
+
+            new DumpsysTableLogger(
+                    "KeyguardFingerprintListen",
+                    KeyguardFingerprintListenModel.TABLE_HEADERS,
+                    mFingerprintListenBuffer.toList()
+            ).printTableData(pw);
         }
         if (mFaceManager != null && !mFaceSensorProperties.isEmpty()) {
             final int userId = mUserTracker.getUserId();
@@ -4155,6 +4168,19 @@
                     KeyguardFaceListenModel.TABLE_HEADERS,
                     mFaceListenBuffer.toList()
             ).printTableData(pw);
+        } else if (mFaceManager != null && mFaceSensorProperties.isEmpty()) {
+            final int userId = mUserTracker.getUserId();
+            pw.println("  Face state (user=" + userId + ")");
+            pw.println("    mFaceSensorProperties.isEmpty="
+                    + mFaceSensorProperties.isEmpty());
+            pw.println("    mFaceManager.isHardwareDetected="
+                    + mFaceManager.isHardwareDetected());
+
+            new DumpsysTableLogger(
+                    "KeyguardFaceListen",
+                    KeyguardFingerprintListenModel.TABLE_HEADERS,
+                    mFingerprintListenBuffer.toList()
+            ).printTableData(pw);
         }
 
         new DumpsysTableLogger(
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
index b1a83fb..6e98a18 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
@@ -60,7 +60,9 @@
                 featureFlags.isEnabled(Flags.LOCKSCREEN_CUSTOM_CLOCKS),
                 /* handleAllUsers= */ true,
                 new DefaultClockProvider(context, layoutInflater, resources),
-                context.getString(R.string.lockscreen_clock_id_fallback));
+                context.getString(R.string.lockscreen_clock_id_fallback),
+                /* keepAllLoaded = */ false,
+                /* subTag = */ "System");
         registry.registerListeners();
         return registry;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/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/accessibility/fontscaling/FontScalingDialog.kt b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt
index 53a421d..1836ce8 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.accessibility.fontscaling
 
+import android.annotation.WorkerThread
 import android.content.Context
 import android.content.pm.ActivityInfo
 import android.content.res.Configuration
@@ -27,18 +28,26 @@
 import android.widget.TextView
 import com.android.systemui.R
 import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.util.settings.SecureSettings
 import com.android.systemui.util.settings.SystemSettings
+import java.util.concurrent.Executor
 import kotlin.math.roundToInt
 
 /** The Dialog that contains a seekbar for changing the font size. */
-class FontScalingDialog(context: Context, private val systemSettings: SystemSettings) :
-    SystemUIDialog(context) {
+class FontScalingDialog(
+    context: Context,
+    private val systemSettings: SystemSettings,
+    private val secureSettings: SecureSettings,
+    @Background private val backgroundExecutor: Executor
+) : SystemUIDialog(context) {
     private val strEntryValues: Array<String> =
         context.resources.getStringArray(com.android.settingslib.R.array.entryvalues_font_size)
     private lateinit var title: TextView
     private lateinit var doneButton: Button
     private lateinit var seekBarWithIconButtonsView: SeekBarWithIconButtonsView
+    private var lastProgress: Int = -1
 
     private val configuration: Configuration =
         Configuration(context.getResources().getConfiguration())
@@ -70,12 +79,22 @@
         seekBarWithIconButtonsView.setMax((strEntryValues).size - 1)
 
         val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, 1.0f)
-        seekBarWithIconButtonsView.setProgress(fontSizeValueToIndex(currentScale))
+        lastProgress = fontSizeValueToIndex(currentScale)
+        seekBarWithIconButtonsView.setProgress(lastProgress)
 
         seekBarWithIconButtonsView.setOnSeekBarChangeListener(
             object : OnSeekBarChangeListener {
                 override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
-                    systemSettings.putString(Settings.System.FONT_SCALE, strEntryValues[progress])
+                    if (progress != lastProgress) {
+                        if (!fontSizeHasBeenChangedFromTile) {
+                            backgroundExecutor.execute { updateSecureSettingsIfNeeded() }
+                            fontSizeHasBeenChangedFromTile = true
+                        }
+
+                        backgroundExecutor.execute { updateFontScale(strEntryValues[progress]) }
+
+                        lastProgress = progress
+                    }
                 }
 
                 override fun onStartTrackingTouch(seekBar: SeekBar) {
@@ -115,4 +134,28 @@
             }
         }
     }
+
+    @WorkerThread
+    fun updateFontScale(newScale: String) {
+        systemSettings.putString(Settings.System.FONT_SCALE, newScale)
+    }
+
+    @WorkerThread
+    fun updateSecureSettingsIfNeeded() {
+        if (
+            secureSettings.getString(Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED) !=
+                ON
+        ) {
+            secureSettings.putString(
+                Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED,
+                ON
+            )
+        }
+    }
+
+    companion object {
+        private const val ON = "1"
+        private const val OFF = "0"
+        private var fontSizeHasBeenChangedFromTile = false
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
index a7b6e6a..13bb6d3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
@@ -658,7 +658,9 @@
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
         mIconController.onConfigurationChanged(newConfig);
-        updateState(mSavedState.getInt(AuthDialog.KEY_BIOMETRIC_STATE));
+        if (mSavedState != null) {
+            updateState(mSavedState.getInt(AuthDialog.KEY_BIOMETRIC_STATE));
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 4aa985b..705fc8c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -823,7 +823,7 @@
 
             final Rect overlayBounds = new Rect(
                     0, /* left */
-                    mCachedDisplayInfo.getNaturalHeight() / 2, /* top */
+                    0, /* top */
                     mCachedDisplayInfo.getNaturalWidth(), /* right */
                     mCachedDisplayInfo.getNaturalHeight() /* botom */);
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index bb35355..e7ec3eb 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -42,6 +42,7 @@
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.hardware.fingerprint.IUdfpsOverlayControllerCallback;
+import android.hardware.input.InputManager;
 import android.os.Build;
 import android.os.Handler;
 import android.os.PowerManager;
@@ -169,6 +170,7 @@
     @NonNull private final AlternateBouncerInteractor mAlternateBouncerInteractor;
     @NonNull private final SecureSettings mSecureSettings;
     @NonNull private final UdfpsUtils mUdfpsUtils;
+    @NonNull private final InputManager mInputManager;
 
     // Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
     // sensors, this, in addition to a lot of the code here, will be updated.
@@ -576,6 +578,10 @@
                         data.getTime(),
                         data.getGestureStart(),
                         mStatusBarStateController.isDozing());
+
+                // Pilfer if valid overlap, don't allow following events to reach keyguard
+                mInputManager.pilferPointers(
+                        mOverlay.getOverlayView().getViewRootImpl().getInputToken());
                 break;
 
             case UP:
@@ -599,9 +605,8 @@
                 break;
 
             case UNCHANGED:
-                if (!isWithinSensorArea(mOverlay.getOverlayView(), event.getX(), event.getY(),
+                if (!isWithinSensorArea(mOverlay.getOverlayView(), event.getRawX(), event.getRawY(),
                         true) && mActivePointerId == MotionEvent.INVALID_POINTER_ID
-                        && event.getActionMasked() == MotionEvent.ACTION_DOWN
                         && mAlternateBouncerInteractor.isVisibleState()) {
                     // No pointer on sensor, forward to keyguard if alternateBouncer is visible
                     mKeyguardViewManager.onTouch(event);
@@ -612,6 +617,13 @@
         }
         logBiometricTouch(processedTouch.getEvent(), data);
 
+        // Always pilfer pointers that are within sensor area
+        if (isWithinSensorArea(mOverlay.getOverlayView(), event.getRawX(), event.getRawY(), true)) {
+            Log.d("Austin", "pilferTouch invalid overlap");
+            mInputManager.pilferPointers(
+                    mOverlay.getOverlayView().getViewRootImpl().getInputToken());
+        }
+
         return processedTouch.getTouchData().isWithinSensor(mOverlayParams.getNativeSensorBounds());
     }
 
@@ -798,6 +810,7 @@
             @NonNull SessionTracker sessionTracker,
             @NonNull AlternateBouncerInteractor alternateBouncerInteractor,
             @NonNull SecureSettings secureSettings,
+            @NonNull InputManager inputManager,
             @NonNull UdfpsUtils udfpsUtils) {
         mContext = context;
         mExecution = execution;
@@ -841,6 +854,7 @@
         mAlternateBouncerInteractor = alternateBouncerInteractor;
         mSecureSettings = secureSettings;
         mUdfpsUtils = udfpsUtils;
+        mInputManager = inputManager;
 
         mTouchProcessor = mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
                 ? singlePointerTouchProcessor : null;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
index 92a7094..9a0792e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
@@ -37,12 +37,12 @@
 @Inject
 constructor(private val authController: AuthController, @Application scope: CoroutineScope) {
 
-    /** Whether a touch should be intercepted or allowed to pass to the UdfpsOverlay */
-    fun canInterceptTouchInUdfpsBounds(ev: MotionEvent): Boolean {
+    /** Whether a touch is within the under-display fingerprint sensor area */
+    fun isTouchWithinUdfpsArea(ev: MotionEvent): Boolean {
         val isUdfpsEnrolled = authController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser())
-        val isWithinUdfpsOverlayBounds =
+        val isWithinOverlayBounds =
             udfpsOverlayParams.value.overlayBounds.contains(ev.rawX.toInt(), ev.rawY.toInt())
-        return !isUdfpsEnrolled || !isWithinUdfpsOverlayBounds
+        return isUdfpsEnrolled && isWithinOverlayBounds
     }
 
     /** Returns the current udfpsOverlayParams */
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java
index e8c83b1..0123857 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java
@@ -81,19 +81,19 @@
         int action = motionEvent.getActionMasked();
 
         if (action == MotionEvent.ACTION_DOWN) {
-            mGestureStartTimeNs = motionEvent.getEventTimeNano();
+            mGestureStartTimeNs = motionEvent.getEventTimeNanos();
             if (mPrevNearTimeNs > 0) {
                 // We only care about if the proximity sensor is triggered while a move event is
                 // happening.
-                mPrevNearTimeNs = motionEvent.getEventTimeNano();
+                mPrevNearTimeNs = motionEvent.getEventTimeNanos();
             }
             logDebug("Gesture start time: " + mGestureStartTimeNs);
             mNearDurationNs = 0;
         }
 
         if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
-            update(mNear, motionEvent.getEventTimeNano());
-            long duration = motionEvent.getEventTimeNano() - mGestureStartTimeNs;
+            update(mNear, motionEvent.getEventTimeNanos());
+            long duration = motionEvent.getEventTimeNanos() - mGestureStartTimeNs;
 
             logDebug("Gesture duration, Proximity duration: " + duration + ", " + mNearDurationNs);
 
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/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
index 7f395d8..82a8858 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
@@ -33,7 +33,6 @@
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.CoreStartable;
-import com.android.systemui.R;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.controls.ControlsServiceInfo;
 import com.android.systemui.controls.dagger.ControlsComponent;
@@ -157,14 +156,14 @@
      * Contains values/logic associated with the dream complication view.
      */
     public static class DreamHomeControlsChipViewHolder implements ViewHolder {
-        private final View mView;
+        private final ImageView mView;
         private final ComplicationLayoutParams mLayoutParams;
         private final DreamHomeControlsChipViewController mViewController;
 
         @Inject
         DreamHomeControlsChipViewHolder(
                 DreamHomeControlsChipViewController dreamHomeControlsChipViewController,
-                @Named(DREAM_HOME_CONTROLS_CHIP_VIEW) View view,
+                @Named(DREAM_HOME_CONTROLS_CHIP_VIEW) ImageView view,
                 @Named(DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS) ComplicationLayoutParams layoutParams
         ) {
             mView = view;
@@ -174,7 +173,7 @@
         }
 
         @Override
-        public View getView() {
+        public ImageView getView() {
             return mView;
         }
 
@@ -187,7 +186,7 @@
     /**
      * Controls behavior of the dream complication.
      */
-    static class DreamHomeControlsChipViewController extends ViewController<View> {
+    static class DreamHomeControlsChipViewController extends ViewController<ImageView> {
         private static final String TAG = "DreamHomeControlsCtrl";
         private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
@@ -216,7 +215,7 @@
 
         @Inject
         DreamHomeControlsChipViewController(
-                @Named(DREAM_HOME_CONTROLS_CHIP_VIEW) View view,
+                @Named(DREAM_HOME_CONTROLS_CHIP_VIEW) ImageView view,
                 ActivityStarter activityStarter,
                 Context context,
                 ControlsComponent controlsComponent,
@@ -231,10 +230,9 @@
 
         @Override
         protected void onViewAttached() {
-            final ImageView chip = mView.findViewById(R.id.home_controls_chip);
-            chip.setImageResource(mControlsComponent.getTileImageId());
-            chip.setContentDescription(mContext.getString(mControlsComponent.getTileTitleId()));
-            chip.setOnClickListener(this::onClickHomeControls);
+            mView.setImageResource(mControlsComponent.getTileImageId());
+            mView.setContentDescription(mContext.getString(mControlsComponent.getTileTitleId()));
+            mView.setOnClickListener(this::onClickHomeControls);
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java
index a7aa97f..cf05d2d 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java
@@ -19,7 +19,7 @@
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
 import android.view.LayoutInflater;
-import android.view.View;
+import android.widget.ImageView;
 
 import com.android.systemui.R;
 import com.android.systemui.dreams.complication.DreamHomeControlsComplication;
@@ -74,8 +74,8 @@
         @Provides
         @DreamHomeControlsComplicationScope
         @Named(DREAM_HOME_CONTROLS_CHIP_VIEW)
-        static View provideHomeControlsChipView(LayoutInflater layoutInflater) {
-            return layoutInflater.inflate(R.layout.dream_overlay_home_controls_chip,
+        static ImageView provideHomeControlsChipView(LayoutInflater layoutInflater) {
+            return (ImageView) layoutInflater.inflate(R.layout.dream_overlay_home_controls_chip,
                     null, 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 6808142..3be42cb 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,6 +49,7 @@
     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;
@@ -58,7 +61,15 @@
      */
     @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
@@ -90,8 +101,8 @@
     @Named(DREAM_MEDIA_ENTRY_LAYOUT_PARAMS)
     static ComplicationLayoutParams provideMediaEntryLayoutParams(@Main Resources res) {
         return new ComplicationLayoutParams(
-                res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width),
-                res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height),
+                ViewGroup.LayoutParams.WRAP_CONTENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT,
                 ComplicationLayoutParams.POSITION_BOTTOM
                         | ComplicationLayoutParams.POSITION_START,
                 ComplicationLayoutParams.DIRECTION_END,
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 c202416..b08822d9 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -162,12 +162,6 @@
     val CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES =
         releasedFlag(216, "customizable_lock_screen_quick_affordances")
 
-    /** Shows chipbar UI whenever the device is unlocked by ActiveUnlock (watch). */
-    // TODO(b/256513609): Tracking Bug
-    @JvmField
-    val ACTIVE_UNLOCK_CHIPBAR =
-        resourceBooleanFlag(217, R.bool.flag_active_unlock_chipbar, "active_unlock_chipbar")
-
     /**
      * Migrates control of the LightRevealScrim's reveal effect and amount from legacy code to the
      * new KeyguardTransitionRepository.
@@ -220,6 +214,15 @@
             "lock_screen_long_press_enabled"
         )
 
+    /** Enables UI updates for AI wallpapers in the wallpaper picker. */
+    // TODO(b/267722622): Tracking Bug
+    @JvmField
+    val WALLPAPER_PICKER_UI_FOR_AIWP =
+            releasedFlag(
+                    229,
+                    "wallpaper_picker_ui_for_aiwp"
+            )
+
     /** Whether to inflate the bouncer view on a background thread. */
     // TODO(b/272091103): Tracking Bug
     @JvmField
@@ -242,12 +245,21 @@
 
     // TODO(b/270223352): Tracking Bug
     @JvmField
-    val HIDE_SMARTSPACE_ON_DREAM_OVERLAY = unreleasedFlag(404, "hide_smartspace_on_dream_overlay")
+    val HIDE_SMARTSPACE_ON_DREAM_OVERLAY =
+        unreleasedFlag(
+            404,
+            "hide_smartspace_on_dream_overlay",
+            teamfood = true
+    )
 
     // TODO(b/271460958): Tracking Bug
     @JvmField
-    val SHOW_WEATHER_COMPLICATION_ON_DREAM_OVERLAY = unreleasedFlag(405,
-        "show_weather_complication_on_dream_overlay")
+    val SHOW_WEATHER_COMPLICATION_ON_DREAM_OVERLAY =
+        unreleasedFlag(
+            405,
+            "show_weather_complication_on_dream_overlay",
+            teamfood = true
+        )
 
     // 500 - quick settings
 
@@ -284,8 +296,7 @@
     /** Enables new QS Edit Mode visual refresh */
     // TODO(b/269787742): Tracking Bug
     @JvmField
-    val ENABLE_NEW_QS_EDIT_MODE =
-        unreleasedFlag(510, "enable_new_qs_edit_mode", teamfood = false)
+    val ENABLE_NEW_QS_EDIT_MODE = unreleasedFlag(510, "enable_new_qs_edit_mode", teamfood = false)
 
     // 600- status bar
 
@@ -529,7 +540,7 @@
 
     // TODO(b/270987164): Tracking Bug
     @JvmField
-    val TRACKPAD_GESTURE_BACK = unreleasedFlag(1205, "trackpad_gesture_back", teamfood = true)
+    val TRACKPAD_GESTURE_FEATURES = releasedFlag(1205, "trackpad_gesture_features")
 
     // TODO(b/263826204): Tracking Bug
     @JvmField
@@ -551,6 +562,10 @@
     val WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM =
         unreleasedFlag(1209, "persist.wm.debug.predictive_back_qs_dialog_anim", teamfood = true)
 
+    // TODO(b/273800936): Tracking Bug
+    @JvmField
+    val TRACKPAD_GESTURE_COMMON = releasedFlag(1210, "trackpad_gesture_common")
+
     // 1300 - screenshots
     // TODO(b/254513155): Tracking Bug
     @JvmField
@@ -596,8 +611,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
@@ -669,8 +683,7 @@
 
     // 2600 - keyboard
     // TODO(b/259352579): Tracking Bug
-    @JvmField val SHORTCUT_LIST_SEARCH_LAYOUT =
-            unreleasedFlag(2600, "shortcut_list_search_layout", teamfood = true)
+    @JvmField val SHORTCUT_LIST_SEARCH_LAYOUT = releasedFlag(2600, "shortcut_list_search_layout")
 
     // TODO(b/259428678): Tracking Bug
     @JvmField
@@ -681,4 +694,9 @@
     @JvmField
     val LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION =
             unreleasedFlag(2602, "large_shade_granular_alpha_interpolation", teamfood = true)
+
+    // TODO(b/272805037): Tracking Bug
+    @JvmField
+    val ADVANCED_VPN_ENABLED = unreleasedFlag(2800, name = "AdvancedVpn__enable_feature",
+            namespace = "vpn", teamfood = false)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/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/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt
index eae40d6..d745a19 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt
@@ -46,6 +46,7 @@
     var legacyAlternateBouncer: LegacyAlternateBouncer? = null
     var legacyAlternateBouncerVisibleTime: Long = NOT_VISIBLE
 
+    var receivedDownTouch = false
     val isVisible: Flow<Boolean> = bouncerRepository.alternateBouncerVisible
 
     /**
@@ -79,6 +80,7 @@
      * @return true if the alternate bouncer was newly hidden, else false.
      */
     fun hide(): Boolean {
+        receivedDownTouch = false
         return if (isModernAlternateBouncerEnabled) {
             val wasAlternateBouncerVisible = isVisibleState()
             bouncerRepository.setAlternateVisible(false)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 7064827..8448b80 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -414,6 +414,10 @@
             KeyguardPickerFlag(
                 name = Contract.FlagsTable.FLAG_NAME_MONOCHROMATIC_THEME,
                 value = featureFlags.isEnabled(Flags.MONOCHROMATIC_THEME)
+            ),
+            KeyguardPickerFlag(
+                name = Contract.FlagsTable.FLAG_NAME_WALLPAPER_PICKER_UI_FOR_AIWP,
+                value = featureFlags.isEnabled(Flags.WALLPAPER_PICKER_UI_FOR_AIWP)
             )
         )
     }
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..a3bf652 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
@@ -151,7 +151,7 @@
     private static final float REC_MEDIA_COVER_SCALE_FACTOR = 1.25f;
     private static final float MEDIA_SCRIM_START_ALPHA = 0.25f;
     private static final float MEDIA_REC_SCRIM_START_ALPHA = 0.15f;
-    private static final float MEDIA_PLAYER_SCRIM_END_ALPHA = 0.9f;
+    private static final float MEDIA_PLAYER_SCRIM_END_ALPHA = 1.0f;
     private static final float MEDIA_REC_SCRIM_END_ALPHA = 1.0f;
 
     private static final Intent SETTINGS_INTENT = new Intent(ACTION_MEDIA_CONTROLS_SETTINGS);
@@ -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/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
index b4724dd..1a85a0f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
@@ -89,6 +89,9 @@
                 R.id.turbulence_noise_view,
                 R.id.touch_ripple_view,
             )
+
+        // Sizing view id for recommendation card view.
+        val recSizingViewId = R.id.sizing_view
     }
 
     /** A listener when the current dimensions of the player change */
@@ -176,7 +179,21 @@
                         // Layout dimensions are possibly changing, so we need to update them. (at
                         // least on large screen devices)
                         lastOrientation = newOrientation
-                        loadLayoutForType(type)
+                        // Update the height of media controls for the expanded layout. it is needed
+                        // for large screen devices.
+                        if (type == TYPE.PLAYER) {
+                            backgroundIds.forEach { id ->
+                                expandedLayout.getConstraint(id).layout.mHeight =
+                                    context.resources.getDimensionPixelSize(
+                                        R.dimen.qs_media_session_height_expanded
+                                    )
+                            }
+                        } else {
+                            expandedLayout.getConstraint(recSizingViewId).layout.mHeight =
+                                context.resources.getDimensionPixelSize(
+                                    R.dimen.qs_media_session_height_expanded
+                                )
+                        }
                     }
                 }
             }
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/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/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
index 80ed08c..e7bb6dc 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -53,11 +53,14 @@
 private const val PX_PER_SEC = 1000
 private const val PX_PER_MS = 1
 
-internal const val MIN_DURATION_ACTIVE_ANIMATION = 300L
+internal const val MIN_DURATION_ACTIVE_BEFORE_INACTIVE_ANIMATION = 300L
+private const val MIN_DURATION_ACTIVE_AFTER_INACTIVE_ANIMATION = 130L
 private const val MIN_DURATION_CANCELLED_ANIMATION = 200L
 private const val MIN_DURATION_COMMITTED_ANIMATION = 120L
 private const val MIN_DURATION_INACTIVE_BEFORE_FLUNG_ANIMATION = 50L
-private const val MIN_DURATION_CONSIDERED_AS_FLING = 100L
+
+private const val MIN_DURATION_ENTRY_TO_ACTIVE_CONSIDERED_AS_FLING = 100L
+private const val MIN_DURATION_INACTIVE_TO_ACTIVE_CONSIDERED_AS_FLING = 400L
 
 private const val FAILSAFE_DELAY_MS = 350L
 private const val POP_ON_FLING_DELAY = 140L
@@ -145,12 +148,12 @@
     private var startY = 0f
     private var startIsLeft: Boolean? = null
 
-    private var gestureSinceActionDown = 0L
     private var gestureEntryTime = 0L
+    private var gestureInactiveTime = 0L
     private var gestureActiveTime = 0L
 
-    private val elapsedTimeSinceActionDown
-        get() = SystemClock.uptimeMillis() - gestureSinceActionDown
+    private val elapsedTimeSinceInactive
+        get() = SystemClock.uptimeMillis() - gestureInactiveTime
     private val elapsedTimeSinceEntry
         get() = SystemClock.uptimeMillis() - gestureEntryTime
 
@@ -158,6 +161,9 @@
     // so that we can unambiguously start showing the ENTRY animation
     private var hasPassedDragSlop = false
 
+    // Distance in pixels a drag can be considered for a fling event
+    private var minFlingDistance = 0
+
     private val failsafeRunnable = Runnable { onFailsafe() }
 
     internal enum class GestureState {
@@ -235,6 +241,7 @@
     private fun updateConfiguration() {
         params.update(resources)
         mView.updateArrowPaint(params.arrowThickness)
+        minFlingDistance = ViewConfiguration.get(context).scaledTouchSlop * 3
     }
 
     private val configurationListener = object : ConfigurationController.ConfigurationListener {
@@ -268,7 +275,6 @@
         velocityTracker!!.addMovement(event)
         when (event.actionMasked) {
             MotionEvent.ACTION_DOWN -> {
-                gestureSinceActionDown = SystemClock.uptimeMillis()
                 cancelAllPendingAnimations()
                 startX = event.x
                 startY = event.y
@@ -307,8 +313,22 @@
                         }
                     }
                     GestureState.ACTIVE -> {
-                        if (elapsedTimeSinceEntry < MIN_DURATION_CONSIDERED_AS_FLING) {
+                        if (previousState == GestureState.ENTRY &&
+                                elapsedTimeSinceEntry
+                                    < MIN_DURATION_ENTRY_TO_ACTIVE_CONSIDERED_AS_FLING
+                        ) {
                             updateArrowState(GestureState.FLUNG)
+                        } else if (previousState == GestureState.INACTIVE &&
+                                elapsedTimeSinceInactive
+                                    < MIN_DURATION_INACTIVE_TO_ACTIVE_CONSIDERED_AS_FLING
+                        ) {
+                            // A delay is added to allow the background to transition back to ACTIVE
+                            // since it was briefly in INACTIVE. Without this delay, setting it
+                            // immediately to COMMITTED would result in the committed animation
+                            // appearing like it was playing in INACTIVE.
+                            mainHandler.postDelayed(MIN_DURATION_ACTIVE_AFTER_INACTIVE_ANIMATION) {
+                                updateArrowState(GestureState.COMMITTED)
+                            }
                         } else {
                             updateArrowState(GestureState.COMMITTED)
                         }
@@ -376,7 +396,7 @@
                 val isPastDynamicDeactivationThreshold =
                         totalTouchDelta <= params.deactivationSwipeTriggerThreshold
                 val isMinDurationElapsed =
-                        elapsedTimeSinceActionDown > MIN_DURATION_ACTIVE_ANIMATION
+                        elapsedTimeSinceEntry > MIN_DURATION_ACTIVE_BEFORE_INACTIVE_ANIMATION
 
                 if (isMinDurationElapsed && (!isWithinYActivationThreshold ||
                                 isPastDynamicDeactivationThreshold)
@@ -470,8 +490,15 @@
             GestureState.GONE -> 0f
         }
 
+        val indicator = when (currentState) {
+            GestureState.ENTRY -> params.entryIndicator
+            GestureState.INACTIVE -> params.preThresholdIndicator
+            GestureState.ACTIVE -> params.activeIndicator
+            else -> params.preThresholdIndicator
+        }
+
         strokeAlphaProgress?.let { progress ->
-            params.arrowStrokeAlphaSpring.get(progress).takeIf { it.isNewState }?.let {
+            indicator.arrowDimens.alphaSpring?.get(progress)?.takeIf { it.isNewState }?.let {
                 mView.popArrowAlpha(0f, it.value)
             }
         }
@@ -537,7 +564,8 @@
                 backgroundHeightStretchAmount = params.heightInterpolator
                         .getInterpolation(progress),
                 backgroundAlphaStretchAmount = 1f,
-                arrowAlphaStretchAmount = params.arrowStrokeAlphaInterpolator.get(progress).value,
+                arrowAlphaStretchAmount = params.entryIndicator.arrowDimens
+                        .alphaInterpolator?.get(progress)?.value ?: 0f,
                 edgeCornerStretchAmount = params.edgeCornerInterpolator.getInterpolation(progress),
                 farCornerStretchAmount = params.farCornerInterpolator.getInterpolation(progress),
                 fullyStretchedDimens = params.preThresholdIndicator
@@ -567,7 +595,8 @@
                 backgroundHeightStretchAmount = params.heightInterpolator
                         .getInterpolation(progress),
                 backgroundAlphaStretchAmount = 1f,
-                arrowAlphaStretchAmount = params.arrowStrokeAlphaInterpolator.get(progress).value,
+                arrowAlphaStretchAmount = params.preThresholdIndicator.arrowDimens
+                        .alphaInterpolator?.get(progress)?.value ?: 0f,
                 edgeCornerStretchAmount = params.edgeCornerInterpolator.getInterpolation(progress),
                 farCornerStretchAmount = params.farCornerInterpolator.getInterpolation(progress),
                 fullyStretchedDimens = params.preThresholdIndicator
@@ -599,19 +628,15 @@
         windowManager.addView(mView, layoutParams)
     }
 
-    private fun isDragAwayFromEdge(velocityPxPerSecThreshold: Int = 0) = velocityTracker!!.run {
-        computeCurrentVelocity(PX_PER_SEC)
-        val velocity = xVelocity.takeIf { mView.isLeftPanel } ?: (xVelocity * -1)
-        velocity > velocityPxPerSecThreshold
-    }
-
     private fun isFlungAwayFromEdge(endX: Float, startX: Float = touchDeltaStartX): Boolean {
-        val minDistanceConsideredForFling = ViewConfiguration.get(context).scaledTouchSlop
         val flingDistance = if (mView.isLeftPanel) endX - startX else startX - endX
-        val isPastFlingVelocity = isDragAwayFromEdge(
-                velocityPxPerSecThreshold =
-                ViewConfiguration.get(context).scaledMinimumFlingVelocity)
-        return flingDistance > minDistanceConsideredForFling && isPastFlingVelocity
+        val flingVelocity = velocityTracker?.run {
+            computeCurrentVelocity(PX_PER_SEC)
+            xVelocity.takeIf { mView.isLeftPanel } ?: (xVelocity * -1)
+        } ?: 0f
+        val isPastFlingVelocityThreshold =
+                flingVelocity > ViewConfiguration.get(context).scaledMinimumFlingVelocity
+        return flingDistance > minFlingDistance && isPastFlingVelocityThreshold
     }
 
     private fun playHorizontalAnimationThen(onEnd: DelayedOnAnimationEndListener) {
@@ -664,7 +689,6 @@
                 mView.setSpring(
                         arrowLength = params.entryIndicator.arrowDimens.lengthSpring,
                         arrowHeight = params.entryIndicator.arrowDimens.heightSpring,
-                        arrowAlpha = params.entryIndicator.arrowDimens.alphaSpring,
                         scale = params.entryIndicator.scaleSpring,
                         verticalTranslation = params.entryIndicator.verticalTranslationSpring,
                         horizontalTranslation = params.entryIndicator.horizontalTranslationSpring,
@@ -725,6 +749,7 @@
                         arrowLength = params.committedIndicator.arrowDimens.lengthSpring,
                         arrowHeight = params.committedIndicator.arrowDimens.heightSpring,
                         scale = params.committedIndicator.scaleSpring,
+                        backgroundAlpha = params.committedIndicator.backgroundDimens.alphaSpring,
                         backgroundWidth = params.committedIndicator.backgroundDimens.widthSpring,
                         backgroundHeight = params.committedIndicator.backgroundDimens.heightSpring,
                         backgroundEdgeCornerRadius = params.committedIndicator.backgroundDimens
@@ -733,6 +758,10 @@
                                 .farCornerRadiusSpring,
                 )
             }
+            GestureState.CANCELLED -> {
+                mView.setSpring(
+                        backgroundAlpha = params.cancelledIndicator.backgroundDimens.alphaSpring)
+            }
             else -> {}
         }
 
@@ -864,6 +893,7 @@
             }
 
             GestureState.INACTIVE -> {
+                gestureInactiveTime = SystemClock.uptimeMillis()
 
                 // Typically entering INACTIVE means
                 // totalTouchDelta <= deactivationSwipeTriggerThreshold
@@ -900,9 +930,9 @@
                 val delay = max(0, MIN_DURATION_CANCELLED_ANIMATION - elapsedTimeSinceEntry)
                 playWithBackgroundWidthAnimation(onEndSetGoneStateListener, delay)
 
-                params.arrowStrokeAlphaSpring.get(0f).takeIf { it.isNewState }?.let {
-                    mView.popArrowAlpha(0f, it.value)
-                }
+                val springForceOnCancelled = params.cancelledIndicator
+                        .arrowDimens.alphaSpring?.get(0f)?.value
+                mView.popArrowAlpha(0f, springForceOnCancelled)
                 mainHandler.postDelayed(10L) { vibratorHelper.cancel() }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index b454c23..cfcc671 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -67,7 +67,6 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.model.SysUiState;
-import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.NavigationEdgeBackPlugin;
@@ -591,7 +590,8 @@
 
             // Add a nav bar panel window
             mIsNewBackAffordanceEnabled = mFeatureFlags.isEnabled(Flags.NEW_BACK_AFFORDANCE);
-            mIsTrackpadGestureBackEnabled = mFeatureFlags.isEnabled(Flags.TRACKPAD_GESTURE_BACK);
+            mIsTrackpadGestureBackEnabled = mFeatureFlags.isEnabled(
+                    Flags.TRACKPAD_GESTURE_FEATURES);
             resetEdgeBackPlugin();
             mPluginManager.addPluginListener(
                     this, NavigationEdgeBackPlugin.class, /*allowMultiple=*/ false);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
index d46333a..3dc6d2f 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
@@ -12,9 +12,10 @@
             val length: Float? = 0f,
             val height: Float? = 0f,
             val alpha: Float = 0f,
-            var alphaSpring: SpringForce? = null,
             val heightSpring: SpringForce? = null,
             val lengthSpring: SpringForce? = null,
+            var alphaSpring: Step<SpringForce>? = null,
+            var alphaInterpolator: Step<Float>? = null
     )
 
     data class BackgroundDimens(
@@ -61,11 +62,6 @@
         private set
     var arrowThickness: Float = 0f
         private set
-    lateinit var arrowStrokeAlphaSpring: Step<SpringForce>
-        private set
-    lateinit var arrowStrokeAlphaInterpolator: Step<Float>
-        private set
-
     // The closest to y
     var minArrowYPosition: Int = 0
         private set
@@ -81,13 +77,6 @@
     var swipeProgressThreshold: Float = 0f
         private set
 
-    // The minimum delta needed to change direction / stop triggering back
-    var minDeltaForSwitch: Int = 0
-        private set
-
-    var minDragToStartAnimation: Float = 0f
-        private set
-
     lateinit var entryWidthInterpolator: PathInterpolator
         private set
     lateinit var entryWidthTowardsEdgeInterpolator: PathInterpolator
@@ -133,23 +122,17 @@
         deactivationSwipeTriggerThreshold =
                 getDimen(R.dimen.navigation_edge_action_deactivation_drag_threshold)
         swipeProgressThreshold = getDimen(R.dimen.navigation_edge_action_progress_threshold)
-        minDeltaForSwitch = getPx(R.dimen.navigation_edge_minimum_x_delta_for_switch)
-        minDragToStartAnimation =
-                getDimen(R.dimen.navigation_edge_action_min_distance_to_start_animation)
 
         entryWidthInterpolator = PathInterpolator(.19f, 1.27f, .71f, .86f)
         entryWidthTowardsEdgeInterpolator = PathInterpolator(1f, -3f, 1f, 1.2f)
-        activeWidthInterpolator = PathInterpolator(.32f, 0f, .16f, .94f)
+        activeWidthInterpolator = PathInterpolator(.7f, .06f, .34f, .97f)
         arrowAngleInterpolator = entryWidthInterpolator
         translationInterpolator = PathInterpolator(0.2f, 1.0f, 1.0f, 1.0f)
         farCornerInterpolator = PathInterpolator(.03f, .19f, .14f, 1.09f)
         edgeCornerInterpolator = PathInterpolator(0f, 1.11f, .85f, .84f)
         heightInterpolator = PathInterpolator(1f, .05f, .9f, -0.29f)
 
-        val showArrowOnProgressValue = .23f
-        val showArrowOnProgressValueFactor = 1.05f
-
-        val entryActiveHorizontalTranslationSpring = createSpring(800f, 0.8f)
+        val entryActiveHorizontalTranslationSpring = createSpring(800f, 0.76f)
         val activeCommittedArrowLengthSpring = createSpring(1500f, 0.29f)
         val activeCommittedArrowHeightSpring = createSpring(1500f, 0.29f)
         val flungCommittedEdgeCornerSpring = createSpring(10000f, 1f)
@@ -157,6 +140,8 @@
         val flungCommittedWidthSpring = createSpring(10000f, 1f)
         val flungCommittedHeightSpring = createSpring(10000f, 1f)
 
+        val entryIndicatorAlphaThreshold = .23f
+        val entryIndicatorAlphaFactor = 1.05f
         entryIndicator = BackIndicatorDimens(
                 horizontalTranslation = getDimen(R.dimen.navigation_edge_entry_margin),
                 scale = getDimenFloat(R.dimen.navigation_edge_entry_scale),
@@ -168,9 +153,20 @@
                         length = getDimen(R.dimen.navigation_edge_entry_arrow_length),
                         height = getDimen(R.dimen.navigation_edge_entry_arrow_height),
                         alpha = 0f,
-                        alphaSpring = createSpring(200f, 1f),
                         lengthSpring = createSpring(600f, 0.4f),
                         heightSpring = createSpring(600f, 0.4f),
+                        alphaSpring = Step(
+                                threshold = entryIndicatorAlphaThreshold,
+                                factor = entryIndicatorAlphaFactor,
+                                postThreshold = createSpring(200f, 1f),
+                                preThreshold = createSpring(2000f, 0.6f)
+                        ),
+                        alphaInterpolator = Step(
+                                threshold = entryIndicatorAlphaThreshold,
+                                factor = entryIndicatorAlphaFactor,
+                                postThreshold = 1f,
+                                preThreshold = 0f
+                        )
                 ),
                 backgroundDimens = BackgroundDimens(
                         alpha = 1f,
@@ -186,6 +182,20 @@
                 )
         )
 
+        val preThresholdAndActiveIndicatorAlphaThreshold = .355f
+        val preThresholdAndActiveIndicatorAlphaFactor = 1.05f
+        val preThresholdAndActiveAlphaSpring = Step(
+                threshold = preThresholdAndActiveIndicatorAlphaThreshold,
+                factor = preThresholdAndActiveIndicatorAlphaFactor,
+                postThreshold = createSpring(180f, 0.9f),
+                preThreshold = createSpring(2000f, 0.6f)
+        )
+        val preThresholdAndActiveAlphaSpringInterpolator = Step(
+                threshold = preThresholdAndActiveIndicatorAlphaThreshold,
+                factor = preThresholdAndActiveIndicatorAlphaFactor,
+                postThreshold = 1f,
+                preThreshold = 0f
+        )
         activeIndicator = BackIndicatorDimens(
                 horizontalTranslation = getDimen(R.dimen.navigation_edge_active_margin),
                 scale = getDimenFloat(R.dimen.navigation_edge_active_scale),
@@ -197,6 +207,8 @@
                         alpha = 1f,
                         lengthSpring = activeCommittedArrowLengthSpring,
                         heightSpring = activeCommittedArrowHeightSpring,
+                        alphaSpring = preThresholdAndActiveAlphaSpring,
+                        alphaInterpolator = preThresholdAndActiveAlphaSpringInterpolator
                 ),
                 backgroundDimens = BackgroundDimens(
                         alpha = 1f,
@@ -204,7 +216,7 @@
                         height = getDimen(R.dimen.navigation_edge_active_background_height),
                         edgeCornerRadius = getDimen(R.dimen.navigation_edge_active_edge_corners),
                         farCornerRadius = getDimen(R.dimen.navigation_edge_active_far_corners),
-                        widthSpring = createSpring(375f, 0.675f),
+                        widthSpring = createSpring(650f, 0.75f),
                         heightSpring = createSpring(10000f, 1f),
                         edgeCornerRadiusSpring = createSpring(600f, 0.36f),
                         farCornerRadiusSpring = createSpring(2500f, 0.855f),
@@ -223,6 +235,8 @@
                         alpha = 1f,
                         lengthSpring = createSpring(100f, 0.6f),
                         heightSpring = createSpring(100f, 0.6f),
+                        alphaSpring = preThresholdAndActiveAlphaSpring,
+                        alphaInterpolator = preThresholdAndActiveAlphaSpringInterpolator
                 ),
                 backgroundDimens = BackgroundDimens(
                         alpha = 1f,
@@ -255,6 +269,7 @@
                         heightSpring = flungCommittedHeightSpring,
                         edgeCornerRadiusSpring = flungCommittedEdgeCornerSpring,
                         farCornerRadiusSpring = flungCommittedFarCornerSpring,
+                        alphaSpring = createSpring(1100f, 1f),
                 ),
                 scale = 0.85f,
                 scaleSpring = createSpring(1150f, 1f),
@@ -276,7 +291,11 @@
         )
 
         cancelledIndicator = entryIndicator.copy(
-                backgroundDimens = entryIndicator.backgroundDimens.copy(width = 0f)
+                backgroundDimens = entryIndicator.backgroundDimens.copy(
+                        width = 0f,
+                        alpha = 0f,
+                        alphaSpring = createSpring(450f, 1f)
+                )
         )
 
         fullyStretchedIndicator = BackIndicatorDimens(
@@ -306,22 +325,6 @@
                         farCornerRadiusSpring = null,
                 )
         )
-
-        arrowStrokeAlphaInterpolator = Step(
-                threshold = showArrowOnProgressValue,
-                factor = showArrowOnProgressValueFactor,
-                postThreshold = 1f,
-                preThreshold = 0f
-        )
-
-        entryIndicator.arrowDimens.alphaSpring?.let { alphaSpring ->
-            arrowStrokeAlphaSpring = Step(
-                    threshold = showArrowOnProgressValue,
-                    factor = showArrowOnProgressValueFactor,
-                    postThreshold = alphaSpring,
-                    preThreshold = SpringForce().setStiffness(2000f).setDampingRatio(1f)
-            )
-        }
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index ac22b7c..e74d78d 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -27,6 +27,7 @@
 import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
 import android.content.pm.PackageManager
 import android.os.Build
+import android.os.UserHandle
 import android.os.UserManager
 import android.util.Log
 import androidx.annotation.VisibleForTesting
@@ -100,6 +101,14 @@
     fun showNoteTask(
         entryPoint: NoteTaskEntryPoint,
     ) {
+        showNoteTaskAsUser(entryPoint, userTracker.userHandle)
+    }
+
+    /** A variant of [showNoteTask] which launches note task in the given [user]. */
+    fun showNoteTaskAsUser(
+        entryPoint: NoteTaskEntryPoint,
+        user: UserHandle,
+    ) {
         if (!isEnabled) return
 
         val bubbles = optionalBubbles.getOrNull() ?: return
@@ -113,7 +122,7 @@
         // note task when the screen is locked.
         if (
             isKeyguardLocked &&
-                devicePolicyManager.areKeyguardShortcutsDisabled(userId = userTracker.userId)
+                devicePolicyManager.areKeyguardShortcutsDisabled(userId = user.identifier)
         ) {
             logDebug { "Enterprise policy disallows launching note app when the screen is locked." }
             return
@@ -126,16 +135,15 @@
         // TODO(b/266686199): We should handle when app not available. For now, we log.
         val intent = createNoteIntent(info)
         try {
-            logDebug { "onShowNoteTask - start: $info" }
+            logDebug { "onShowNoteTask - start: $info on user#${user.identifier}" }
             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" }
                 }
                 is NoteTaskLaunchMode.Activity -> {
-                    context.startActivityAsUser(intent, userTracker.userHandle)
+                    context.startActivityAsUser(intent, user)
                     eventLogger.logNoteTaskOpened(info)
                     logDebug { "onShowNoteTask - opened as activity: $info" }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java
index 95f1419..fbf1a0e 100644
--- a/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java
@@ -73,7 +73,7 @@
         return new PluginInstance.Factory(
                 PluginModule.class.getClassLoader(),
                 new PluginInstance.InstanceFactory<>(),
-                new PluginInstance.VersionChecker(),
+                new PluginInstance.VersionCheckerImpl(),
                 privilegedPlugins,
                 isDebug);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index baa812c..584d27f8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -635,7 +635,8 @@
                 && mLastKeyguardAndExpanded == onKeyguardAndExpanded
                 && mLastViewHeight == currentHeight
                 && mLastHeaderTranslation == headerTranslation
-                && mSquishinessFraction == squishinessFraction) {
+                && mSquishinessFraction == squishinessFraction
+                && mLastPanelFraction == panelExpansionFraction) {
             return;
         }
         mLastHeaderTranslation = headerTranslation;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
index a71e6dd..9ece72d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
@@ -26,6 +26,7 @@
 import com.android.systemui.plugins.qs.QSFactory;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.qs.QSTileView;
+import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
 import com.android.systemui.util.leak.GarbageMonitor;
 
 import java.util.ArrayList;
@@ -33,7 +34,7 @@
 import java.util.Collection;
 import java.util.List;
 
-public interface QSHost {
+public interface QSHost extends PanelInteractor {
     String TILES_SETTING = Settings.Secure.QS_TILES;
     int POSITION_AT_END = -1;
 
@@ -57,9 +58,6 @@
     }
 
     void warn(String message, Throwable t);
-    void collapsePanels();
-    void forceCollapsePanels();
-    void openPanels();
     Context getContext();
     Context getUserContext();
     int getUserId();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt
new file mode 100644
index 0000000..958fa71
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.qs.dagger
+
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QSTileHost
+import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
+import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractorImpl
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+
+@Module
+interface QSHostModule {
+
+    @Binds fun provideQsHost(controllerImpl: QSTileHost): QSHost
+
+    @Module
+    companion object {
+        @Provides
+        @JvmStatic
+        fun providePanelInteractor(
+            featureFlags: FeatureFlags,
+            qsHost: QSHost,
+            panelInteractorImpl: PanelInteractorImpl
+        ): PanelInteractor {
+            return if (featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) {
+                panelInteractorImpl
+            } else {
+                qsHost
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
index 431d6e8..cfe9313 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
@@ -27,7 +27,6 @@
 import com.android.systemui.media.dagger.MediaModule;
 import com.android.systemui.qs.AutoAddTracker;
 import com.android.systemui.qs.QSHost;
-import com.android.systemui.qs.QSTileHost;
 import com.android.systemui.qs.ReduceBrightColorsController;
 import com.android.systemui.qs.external.QSExternalModule;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -45,7 +44,6 @@
 
 import javax.inject.Named;
 
-import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
 import dagger.multibindings.Multibinds;
@@ -54,7 +52,13 @@
  * Module for QS dependencies
  */
 @Module(subcomponents = {QSFragmentComponent.class},
-        includes = {MediaModule.class, QSExternalModule.class, QSFlagsModule.class})
+        includes = {
+                MediaModule.class,
+                QSExternalModule.class,
+                QSFlagsModule.class,
+                QSHostModule.class
+        }
+)
 public interface QSModule {
 
     /** A map of internal QS tiles. Ensures that this can be injected even if
@@ -100,8 +104,4 @@
         manager.init();
         return manager;
     }
-
-    /** */
-    @Binds
-    QSHost provideQsHost(QSTileHost controllerImpl);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
index adc7165..2083cc7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
@@ -40,6 +40,7 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -74,6 +75,7 @@
     private final CommandQueue mCommandQueue;
     private final UserTracker mUserTracker;
     private final StatusBarIconController mStatusBarIconController;
+    private final PanelInteractor mPanelInteractor;
 
     private int mMaxBound = DEFAULT_MAX_BOUND;
 
@@ -85,7 +87,8 @@
             UserTracker userTracker,
             KeyguardStateController keyguardStateController,
             CommandQueue commandQueue,
-            StatusBarIconController statusBarIconController) {
+            StatusBarIconController statusBarIconController,
+            PanelInteractor panelInteractor) {
         mHost = host;
         mKeyguardStateController = keyguardStateController;
         mContext = mHost.getContext();
@@ -96,6 +99,7 @@
         mCommandQueue = commandQueue;
         mStatusBarIconController = statusBarIconController;
         mCommandQueue.addCallback(mRequestListeningCallback);
+        mPanelInteractor = panelInteractor;
     }
 
     public Context getContext() {
@@ -255,7 +259,7 @@
         if (customTile != null) {
             verifyCaller(customTile);
             customTile.onDialogShown();
-            mHost.forceCollapsePanels();
+            mPanelInteractor.forceCollapsePanels();
             Objects.requireNonNull(mServices.get(customTile)).setShowingDialog(true);
         }
     }
@@ -275,7 +279,7 @@
         CustomTile customTile = getTileForToken(token);
         if (customTile != null) {
             verifyCaller(customTile);
-            mHost.forceCollapsePanels();
+            mPanelInteractor.forceCollapsePanels();
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractor.kt
new file mode 100644
index 0000000..260caa7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractor.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.qs.pipeline.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.phone.CentralSurfaces
+import java.util.Optional
+import javax.inject.Inject
+
+/** Encapsulates business logic for interacting with the QS panel. */
+interface PanelInteractor {
+
+    /** Collapse the shade */
+    fun collapsePanels()
+
+    /** Collapse the shade forcefully, skipping some animations. */
+    fun forceCollapsePanels()
+
+    /** Open the Quick Settings panel */
+    fun openPanels()
+}
+
+@SysUISingleton
+class PanelInteractorImpl
+@Inject
+constructor(
+    private val centralSurfaces: Optional<CentralSurfaces>,
+) : PanelInteractor {
+    override fun collapsePanels() {
+        centralSurfaces.ifPresent { it.postAnimateCollapsePanels() }
+    }
+
+    override fun forceCollapsePanels() {
+        centralSurfaces.ifPresent { it.postAnimateForceCollapsePanels() }
+    }
+
+    override fun openPanels() {
+        centralSurfaces.ifPresent { it.postAnimateOpenPanels() }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
index a915ddb..3f514344 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
@@ -17,6 +17,7 @@
 
 import android.content.Intent
 import android.os.Handler
+import android.os.HandlerExecutor
 import android.os.Looper
 import android.provider.Settings
 import android.view.View
@@ -38,6 +39,7 @@
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.util.settings.SecureSettings
 import com.android.systemui.util.settings.SystemSettings
 import javax.inject.Inject
 
@@ -54,6 +56,7 @@
     qsLogger: QSLogger,
     private val dialogLaunchAnimator: DialogLaunchAnimator,
     private val systemSettings: SystemSettings,
+    private val secureSettings: SecureSettings,
     private val featureFlags: FeatureFlags
 ) :
     QSTileImpl<QSTile.State?>(
@@ -78,7 +81,13 @@
 
     override fun handleClick(view: View?) {
         mUiHandler.post {
-            val dialog: SystemUIDialog = FontScalingDialog(mContext, systemSettings)
+            val dialog: SystemUIDialog =
+                FontScalingDialog(
+                    mContext,
+                    systemSettings,
+                    secureSettings,
+                    HandlerExecutor(mHandler)
+                )
             if (view != null) {
                 dialogLaunchAnimator.showFromView(
                     dialog,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
index 89d402a3..27f5826 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
@@ -38,6 +38,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.LocationController;
@@ -52,6 +53,7 @@
 
     private final LocationController mController;
     private final KeyguardStateController mKeyguard;
+    private final PanelInteractor mPanelInteractor;
     private final Callback mCallback = new Callback();
 
     @Inject
@@ -65,12 +67,14 @@
             ActivityStarter activityStarter,
             QSLogger qsLogger,
             LocationController locationController,
-            KeyguardStateController keyguardStateController
+            KeyguardStateController keyguardStateController,
+            PanelInteractor panelInteractor
     ) {
         super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mController = locationController;
         mKeyguard = keyguardStateController;
+        mPanelInteractor = panelInteractor;
         mController.observe(this, mCallback);
         mKeyguard.observe(this, mCallback);
     }
@@ -90,7 +94,7 @@
         if (mKeyguard.isMethodSecure() && mKeyguard.isShowing()) {
             mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
                 final boolean wasEnabled = mState.value;
-                mHost.openPanels();
+                mPanelInteractor.openPanels();
                 mController.setLocationEnabled(!wasEnabled);
             });
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
index 07b50c9..65592a7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -42,6 +42,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.screenrecord.RecordingController;
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
@@ -66,6 +67,7 @@
     private final Callback mCallback = new Callback();
     private final DialogLaunchAnimator mDialogLaunchAnimator;
     private final FeatureFlags mFlags;
+    private final PanelInteractor mPanelInteractor;
 
     private long mMillisUntilFinished = 0;
 
@@ -83,7 +85,8 @@
             RecordingController controller,
             KeyguardDismissUtil keyguardDismissUtil,
             KeyguardStateController keyguardStateController,
-            DialogLaunchAnimator dialogLaunchAnimator
+            DialogLaunchAnimator dialogLaunchAnimator,
+            PanelInteractor panelInteractor
     ) {
         super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
@@ -93,6 +96,7 @@
         mKeyguardDismissUtil = keyguardDismissUtil;
         mKeyguardStateController = keyguardStateController;
         mDialogLaunchAnimator = dialogLaunchAnimator;
+        mPanelInteractor = panelInteractor;
     }
 
     @Override
@@ -171,7 +175,7 @@
             // disable the exit animation which looks weird when it happens at the same time as the
             // shade collapsing.
             mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations();
-            getHost().collapsePanels();
+            mPanelInteractor.collapsePanels();
         };
 
         final Dialog dialog = mController.createScreenRecordDialog(mContext, mFlags,
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 8a3ecc6..0748bcb 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -21,7 +21,6 @@
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_UP;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
-
 import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SUPPORTS_WINDOW_CORNERS;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
@@ -713,6 +712,7 @@
     }
 
     public void startConnectionToCurrentUser() {
+        Log.v(TAG_OPS, "startConnectionToCurrentUser: connection is restarted");
         if (mHandler.getLooper() != Looper.myLooper()) {
             mHandler.post(mConnectionRunnable);
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/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/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 06426b3..1c3e011 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -849,7 +849,7 @@
         mLayoutInflater = layoutInflater;
         mFeatureFlags = featureFlags;
         mAnimateBack = mFeatureFlags.isEnabled(Flags.WM_SHADE_ANIMATE_BACK_GESTURE);
-        mTrackpadGestureBack = mFeatureFlags.isEnabled(Flags.TRACKPAD_GESTURE_BACK);
+        mTrackpadGestureBack = mFeatureFlags.isEnabled(Flags.TRACKPAD_GESTURE_FEATURES);
         mFalsingCollector = falsingCollector;
         mPowerManager = powerManager;
         mWakeUpCoordinator = coordinator;
@@ -2805,6 +2805,7 @@
     public void setIsLaunchAnimationRunning(boolean running) {
         boolean wasRunning = mIsLaunchAnimationRunning;
         mIsLaunchAnimationRunning = running;
+        mCentralSurfaces.updateIsKeyguard();
         if (wasRunning != mIsLaunchAnimationRunning) {
             mShadeExpansionStateManager.notifyLaunchingActivityChanged(running);
         }
@@ -3581,15 +3582,12 @@
     }
 
     private void endMotionEvent(MotionEvent event, float x, float y, boolean forceCancel) {
-        // don't fling while in keyguard to avoid jump in shade expand animation
-        boolean fullyExpandedInKeyguard = mBarState == KEYGUARD && mExpandedFraction >= 1.0;
         mTrackingPointer = -1;
         mAmbientState.setSwipingUp(false);
-        if (!fullyExpandedInKeyguard && ((mTracking && mTouchSlopExceeded)
-                || Math.abs(x - mInitialExpandX) > mTouchSlop
+        if ((mTracking && mTouchSlopExceeded) || Math.abs(x - mInitialExpandX) > mTouchSlop
                 || Math.abs(y - mInitialExpandY) > mTouchSlop
                 || (!isFullyExpanded() && !isFullyCollapsed())
-                || event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel)) {
+                || event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) {
             mVelocityTracker.computeCurrentVelocity(1000);
             float vel = mVelocityTracker.getYVelocity();
             float vectorVel = (float) Math.hypot(
@@ -3637,9 +3635,9 @@
             if (mUpdateFlingOnLayout) {
                 mUpdateFlingVelocity = vel;
             }
-        } else if (fullyExpandedInKeyguard || (!mCentralSurfaces.isBouncerShowing()
+        } else if (!mCentralSurfaces.isBouncerShowing()
                 && !mAlternateBouncerInteractor.isVisibleState()
-                && !mKeyguardStateController.isKeyguardGoingAway())) {
+                && !mKeyguardStateController.isKeyguardGoingAway()) {
             onEmptySpaceClick();
             onTrackingStopped(true);
         }
@@ -3782,10 +3780,10 @@
                     mHeightAnimator.end();
                 }
             }
-            mQsController.setShadeExpandedHeight(mExpandedHeight);
-            mExpansionDragDownAmountPx = h;
             mExpandedFraction = Math.min(1f,
                     maxPanelHeight == 0 ? 0 : mExpandedHeight / maxPanelHeight);
+            mQsController.setShadeExpansion(mExpandedHeight, mExpandedFraction);
+            mExpansionDragDownAmountPx = h;
             mAmbientState.setExpansionFraction(mExpandedFraction);
             onHeightUpdated(mExpandedHeight);
             updatePanelExpansionAndVisibility();
@@ -3865,6 +3863,10 @@
         return mClosing || mIsLaunchAnimationRunning;
     }
 
+    public boolean isLaunchAnimationRunning() {
+        return mIsLaunchAnimationRunning;
+    }
+
     public boolean isTracking() {
         return mTracking;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 156e4fd..e7759df 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -301,9 +301,11 @@
     }
 
     private void applyKeyguardFlags(NotificationShadeWindowState state) {
-        final boolean keyguardOrAod = state.keyguardShowing
+        // Keyguard is visible if it's showing or if it's fading away (in which case we're animating
+        // it out, but the wallpaper should remain visible as a backdrop for the animation);
+        final boolean keyguardOrAodVisible = (state.keyguardShowing || state.keyguardFadingAway)
                 || (state.dozing && mDozeParameters.getAlwaysOn());
-        if ((keyguardOrAod && !state.mediaBackdropShowing && !state.lightRevealScrimOpaque)
+        if ((keyguardOrAodVisible && !state.mediaBackdropShowing && !state.lightRevealScrimOpaque)
                 || mKeyguardViewMediator.isAnimatingBetweenKeyguardAndSurfaceBehind()) {
             // Show the wallpaper if we're on keyguard/AOD and the wallpaper is not occluded by a
             // solid backdrop. Also, show it if we are currently animating between the
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index e2f31e8..5f6f158 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_COMMON;
 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,23 +30,27 @@
 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;
 import com.android.keyguard.LockIconViewController;
 import com.android.keyguard.dagger.KeyguardBouncerComponent;
 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;
 import com.android.systemui.keyguard.shared.model.TransitionState;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
 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 +65,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}.
@@ -86,12 +92,12 @@
     private final AmbientState mAmbientState;
     private final PulsingGestureListener mPulsingGestureListener;
     private final NotificationInsetsController mNotificationInsetsController;
-    private final AlternateBouncerInteractor mAlternateBouncerInteractor;
-    private final UdfpsOverlayInteractor mUdfpsOverlayInteractor;
+    private final boolean mIsTrackpadCommonEnabled;
     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 +117,7 @@
                 mIsOcclusionTransitionRunning =
                     step.getTransitionState() == TransitionState.RUNNING;
             };
+    private final SystemClock mClock;
 
     @Inject
     public NotificationShadeWindowViewController(
@@ -134,11 +141,11 @@
             PulsingGestureListener pulsingGestureListener,
             KeyguardBouncerViewModel keyguardBouncerViewModel,
             KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory,
-            AlternateBouncerInteractor alternateBouncerInteractor,
-            UdfpsOverlayInteractor udfpsOverlayInteractor,
             KeyguardTransitionInteractor keyguardTransitionInteractor,
-            PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel
-    ) {
+            PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
+            FeatureFlags featureFlags,
+            Provider<MultiShadeInteractor> multiShadeInteractorProvider,
+            SystemClock clock) {
         mLockscreenShadeTransitionController = transitionController;
         mFalsingCollector = falsingCollector;
         mStatusBarStateController = statusBarStateController;
@@ -157,8 +164,7 @@
         mAmbientState = ambientState;
         mPulsingGestureListener = pulsingGestureListener;
         mNotificationInsetsController = notificationInsetsController;
-        mAlternateBouncerInteractor = alternateBouncerInteractor;
-        mUdfpsOverlayInteractor = udfpsOverlayInteractor;
+        mIsTrackpadCommonEnabled = featureFlags.isEnabled(TRACKPAD_GESTURE_COMMON);
 
         // This view is not part of the newly inflated expanded status bar.
         mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container);
@@ -170,6 +176,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 +235,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;
@@ -243,6 +261,9 @@
 
                 mFalsingCollector.onTouchEvent(ev);
                 mPulsingWakeupGestureHandler.onTouchEvent(ev);
+                if (mStatusBarKeyguardViewManager.dispatchTouchEvent(ev)) {
+                    return true;
+                }
                 if (mBrightnessMirror != null
                         && mBrightnessMirror.getVisibility() == View.VISIBLE) {
                     // Disallow new pointers while the brightness mirror is visible. This is so that
@@ -262,7 +283,7 @@
                 mLockIconViewController.onTouchEvent(
                         ev,
                         () -> mService.wakeUpIfDozing(
-                                SystemClock.uptimeMillis(),
+                                mClock.uptimeMillis(),
                                 mView,
                                 "LOCK_ICON_TOUCH",
                                 PowerManager.WAKE_REASON_GESTURE)
@@ -317,9 +338,10 @@
                     return true;
                 }
 
-                if (mAlternateBouncerInteractor.isVisibleState()) {
-                    // If using UDFPS, don't intercept touches that are within its overlay bounds
-                    return mUdfpsOverlayInteractor.canInterceptTouchInUdfpsBounds(ev);
+                if (mStatusBarKeyguardViewManager.shouldInterceptTouchEvent(ev)) {
+                    // Don't allow touches to proceed to underlying views if alternate
+                    // bouncer is showing
+                    return true;
                 }
 
                 if (mLockIconViewController.onInterceptTouchEvent(ev)) {
@@ -355,10 +377,8 @@
                     handled = !mService.isPulsing();
                 }
 
-                if (mAlternateBouncerInteractor.isVisibleState()) {
-                    // eat the touch
-                    mStatusBarKeyguardViewManager.onTouch(ev);
-                    handled = true;
+                if (mStatusBarKeyguardViewManager.onTouch(ev)) {
+                    return true;
                 }
 
                 if ((mDragDownHelper.isDragDownEnabled() && !handled)
@@ -446,10 +466,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 (mIsTrackpadCommonEnabled) {
+                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/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index df8ae50..9f46707 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -351,7 +351,6 @@
         mFeatureFlags = featureFlags;
         mInteractionJankMonitor = interactionJankMonitor;
 
-        mShadeExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged);
         mLockscreenShadeTransitionController.addCallback(new LockscreenShadeTransitionCallback());
     }
 
@@ -878,8 +877,9 @@
         mCollapsedOnDown = collapsedOnDown;
     }
 
-    void setShadeExpandedHeight(float shadeExpandedHeight) {
-        mShadeExpandedHeight = shadeExpandedHeight;
+    void setShadeExpansion(float expandedHeight, float expandedFraction) {
+        mShadeExpandedHeight = expandedHeight;
+        mShadeExpandedFraction = expandedFraction;
     }
 
     @VisibleForTesting
@@ -1749,11 +1749,6 @@
         return false;
     }
 
-    @VisibleForTesting
-    void onPanelExpansionChanged(ShadeExpansionChangeEvent event) {
-        mShadeExpandedFraction = event.getFraction();
-    }
-
     /**
      * Animate QS closing by flinging it.
      * If QS is expanded, it will collapse into QQS and stop.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
index 01e042b..c920e1e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
@@ -657,25 +657,6 @@
                                                 R.string.input_switch_input_language_previous),
                                         KeyEvent.KEYCODE_SPACE,
                                         KeyEvent.META_META_ON | KeyEvent.META_SHIFT_ON),
-                                        null))),
-                /* Access emoji: Meta + . */
-                new ShortcutMultiMappingInfo(
-                        context.getString(R.string.input_access_emoji),
-                        null,
-                        Arrays.asList(
-                                new ShortcutKeyGroup(new KeyboardShortcutInfo(
-                                        context.getString(R.string.input_access_emoji),
-                                        KeyEvent.KEYCODE_PERIOD,
-                                        KeyEvent.META_META_ON),
-                                        null))),
-                /* Access voice typing: Meta + V */
-                new ShortcutMultiMappingInfo(
-                        context.getString(R.string.input_access_voice_typing),
-                        null,
-                        Arrays.asList(
-                                new ShortcutKeyGroup(new KeyboardShortcutInfo(
-                                        context.getString(R.string.input_access_voice_typing),
-                                        KeyEvent.KEYCODE_V, KeyEvent.META_META_ON),
                                         null)))
         );
         return new KeyboardShortcutMultiMappingGroup(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 51c5183..cac4251 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -16,7 +16,6 @@
 package com.android.systemui.statusbar;
 
 import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
-
 import static com.android.systemui.DejankUtils.whitelistIpcs;
 
 import android.app.KeyguardManager;
@@ -145,7 +144,10 @@
                     break;
                 case Intent.ACTION_USER_UNLOCKED:
                     // Start the overview connection to the launcher service
-                    mOverviewProxyServiceLazy.get().startConnectionToCurrentUser();
+                    // Connect if user hasn't connected yet
+                    if (mOverviewProxyServiceLazy.get().getProxy() == null) {
+                        mOverviewProxyServiceLazy.get().startConnectionToCurrentUser();
+                    }
                     break;
                 case NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION:
                     final IntentSender intentSender = intent.getParcelableExtra(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/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/collection/coordinator/GroupCountCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinator.kt
index 82b1268..02bf3b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinator.kt
@@ -1,3 +1,19 @@
+/*
+ * 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.
+ */
+
 package com.android.systemui.statusbar.notification.collection.coordinator
 
 import android.util.ArrayMap
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 33cbf06..a529da5 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
@@ -68,6 +68,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.util.ContrastColorUtil;
 import com.android.internal.widget.CachingIconView;
 import com.android.internal.widget.CallLayout;
@@ -1671,7 +1672,8 @@
             MetricsLogger metricsLogger,
             SmartReplyConstants smartReplyConstants,
             SmartReplyController smartReplyController,
-            FeatureFlags featureFlags) {
+            FeatureFlags featureFlags,
+            IStatusBarService statusBarService) {
         mEntry = entry;
         mAppName = appName;
         if (mMenuRow == null) {
@@ -1699,7 +1701,8 @@
                     mPeopleNotificationIdentifier,
                     rivSubcomponentFactory,
                     smartReplyConstants,
-                    smartReplyController);
+                    smartReplyController,
+                    statusBarService);
         }
         mOnUserInteractionCallback = onUserInteractionCallback;
         mBubblesManagerOptional = bubblesManagerOptional;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index e2a3111..5ca0866 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -29,6 +29,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.logging.MetricsLogger;
+import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
@@ -100,6 +101,7 @@
     private final SmartReplyController mSmartReplyController;
     private final ExpandableNotificationRowDragController mDragController;
     private final NotificationDismissibilityProvider mDismissibilityProvider;
+    private final IStatusBarService mStatusBarService;
     private final ExpandableNotificationRow.ExpandableNotificationRowLogger mLoggerCallback =
             new ExpandableNotificationRow.ExpandableNotificationRowLogger() {
                 @Override
@@ -157,7 +159,8 @@
             PeopleNotificationIdentifier peopleNotificationIdentifier,
             Optional<BubblesManager> bubblesManagerOptional,
             ExpandableNotificationRowDragController dragController,
-            NotificationDismissibilityProvider dismissibilityProvider) {
+            NotificationDismissibilityProvider dismissibilityProvider,
+            IStatusBarService statusBarService) {
         mView = view;
         mListContainer = listContainer;
         mRemoteInputViewSubcomponentFactory = rivSubcomponentFactory;
@@ -189,6 +192,7 @@
         mSmartReplyConstants = smartReplyConstants;
         mSmartReplyController = smartReplyController;
         mDismissibilityProvider = dismissibilityProvider;
+        mStatusBarService = statusBarService;
     }
 
     /**
@@ -220,7 +224,8 @@
                 mMetricsLogger,
                 mSmartReplyConstants,
                 mSmartReplyController,
-                mFeatureFlags
+                mFeatureFlags,
+                mStatusBarService
         );
         mView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
         if (mAllowLongPress) {
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..78392f7 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
@@ -21,10 +21,13 @@
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.content.Context;
+import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
+import android.os.RemoteException;
 import android.provider.Settings;
+import android.service.notification.StatusBarNotification;
 import android.util.ArrayMap;
 import android.util.AttributeSet;
 import android.util.IndentingPrintWriter;
@@ -39,6 +42,7 @@
 import android.widget.LinearLayout;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.R;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.statusbar.RemoteInputController;
@@ -129,6 +133,7 @@
     private Runnable mExpandedVisibleListener;
     private PeopleNotificationIdentifier mPeopleIdentifier;
     private RemoteInputViewSubcomponent.Factory mRemoteInputSubcomponentFactory;
+    private IStatusBarService mStatusBarService;
 
     /**
      * List of listeners for when content views become inactive (i.e. not the showing view).
@@ -196,11 +201,13 @@
             PeopleNotificationIdentifier peopleNotificationIdentifier,
             RemoteInputViewSubcomponent.Factory rivSubcomponentFactory,
             SmartReplyConstants smartReplyConstants,
-            SmartReplyController smartReplyController) {
+            SmartReplyController smartReplyController,
+            IStatusBarService statusBarService) {
         mPeopleIdentifier = peopleNotificationIdentifier;
         mRemoteInputSubcomponentFactory = rivSubcomponentFactory;
         mSmartReplyConstants = smartReplyConstants;
         mSmartReplyController = smartReplyController;
+        mStatusBarService = statusBarService;
     }
 
     public void reinflate() {
@@ -2055,6 +2062,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 */
@@ -2176,4 +2200,36 @@
     protected void setHeadsUpWrapper(NotificationViewWrapper headsUpWrapper) {
         mHeadsUpWrapper = headsUpWrapper;
     }
+
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        try {
+            super.dispatchDraw(canvas);
+        } catch (Exception e) {
+            // Catch draw exceptions that may be caused by RemoteViews
+            Log.e(TAG, "Drawing view failed: " + e);
+            cancelNotification(e);
+        }
+    }
+
+    private void cancelNotification(Exception exception) {
+        try {
+            setVisibility(GONE);
+            final StatusBarNotification sbn = mNotificationEntry.getSbn();
+            if (mStatusBarService != null) {
+                // report notification inflation errors back up
+                // to notification delegates
+                mStatusBarService.onNotificationError(
+                        sbn.getPackageName(),
+                        sbn.getTag(),
+                        sbn.getId(),
+                        sbn.getUid(),
+                        sbn.getInitialPid(),
+                        exception.getMessage(),
+                        sbn.getUser().getIdentifier());
+            }
+        } catch (RemoteException ex) {
+            Log.e(TAG, "cancelNotification failed: " + ex);
+        }
+    }
 }
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 2c088fa..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);
     }
 
     /**
@@ -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/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index e8058b8..769edf7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -831,18 +831,16 @@
 
     private boolean isInVisibleLocation(NotificationEntry entry) {
         ExpandableNotificationRow row = entry.getRow();
-        ExpandableViewState childViewState = row.getViewState();
-
-        if (childViewState == null) {
+        if (row == null) {
             return false;
         }
+
+        ExpandableViewState childViewState = row.getViewState();
         if ((childViewState.location & ExpandableViewState.VISIBLE_LOCATIONS) == 0) {
             return false;
         }
-        if (row.getVisibility() != View.VISIBLE) {
-            return false;
-        }
-        return true;
+
+        return row.getVisibility() == View.VISIBLE;
     }
 
     public boolean isViewAffectedBySwipe(ExpandableView expandableView) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 5438a59..5e3c1c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -1052,6 +1052,8 @@
                 // The light reveal scrim should always be fully revealed by the time the keyguard
                 // is done going away. Double check that this is true.
                 if (!mKeyguardStateController.isKeyguardGoingAway()) {
+                    updateIsKeyguard();
+
                     if (mLightRevealScrim.getRevealAmount() != 1f) {
                         Log.e(TAG, "Keyguard is done going away, but someone left the light reveal "
                                 + "scrim at reveal amount: " + mLightRevealScrim.getRevealAmount());
@@ -2943,6 +2945,10 @@
                 showKeyguardImpl();
             }
         } else {
+            final boolean isLaunchingOrGoingAway =
+                    mNotificationPanelViewController.isLaunchAnimationRunning()
+                            || mKeyguardStateController.isKeyguardGoingAway();
+
             // During folding a foldable device this might be called as a result of
             // 'onScreenTurnedOff' call for the inner display.
             // In this case:
@@ -2954,7 +2960,14 @@
             if (!mScreenOffAnimationController.isKeyguardHideDelayed()
                     // If we're animating occluded, there's an activity launching over the keyguard
                     // UI. Wait to hide it until after the animation concludes.
-                    && !mKeyguardViewMediator.isOccludeAnimationPlaying()) {
+                    && !mKeyguardViewMediator.isOccludeAnimationPlaying()
+                    // If we're occluded, but playing an animation (launch or going away animations)
+                    // the keyguard is visible behind the animation.
+                    && !(mKeyguardStateController.isOccluded() && isLaunchingOrGoingAway)) {
+                    // If we're going away and occluded, it means we are launching over the
+                    // unsecured keyguard, which will subsequently go away. Wait to hide it until
+                    // after the animation concludes to avoid the lockscreen UI changing into the
+                    // shade UI behind the launch animation.
                 return hideKeyguardImpl(forceStateChange);
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 0bded73..46603df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -809,6 +809,11 @@
 
     public void setOccludeAnimationPlaying(boolean occludeAnimationPlaying) {
         mOccludeAnimationPlaying = occludeAnimationPlaying;
+
+        for (ScrimState state : ScrimState.values()) {
+            state.setOccludeAnimationPlaying(occludeAnimationPlaying);
+        }
+
         applyAndDispatchState();
     }
 
@@ -853,7 +858,6 @@
         if (mState == ScrimState.UNLOCKED || mState == ScrimState.DREAMING) {
             final boolean occluding =
                     mOccludeAnimationPlaying || mState.mLaunchingAffordanceWithPreview;
-
             // Darken scrim as it's pulled down while unlocked. If we're unlocked but playing the
             // screen off/occlusion animations, ignore expansion changes while those animations
             // play.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index 0e9d3ce..7b20283 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -243,7 +243,12 @@
                     : CentralSurfaces.FADE_KEYGUARD_DURATION;
 
             boolean fromAod = previousState == AOD || previousState == PULSING;
-            mAnimateChange = !mLaunchingAffordanceWithPreview && !fromAod;
+            // If launch/occlude animations were playing, they already animated the scrim
+            // alpha to 0f as part of the animation. If we animate it now, we'll set it back
+            // to 1f and animate it back to 0f, causing an unwanted scrim flash.
+            mAnimateChange = !mLaunchingAffordanceWithPreview
+                    && !mOccludeAnimationPlaying
+                    && !fromAod;
 
             mFrontTint = Color.TRANSPARENT;
             mBehindTint = Color.BLACK;
@@ -308,6 +313,7 @@
     boolean mWallpaperSupportsAmbientMode;
     boolean mHasBackdrop;
     boolean mLaunchingAffordanceWithPreview;
+    boolean mOccludeAnimationPlaying;
     boolean mWakeLockScreenSensorActive;
     boolean mKeyguardFadingAway;
     long mKeyguardFadingAwayDuration;
@@ -411,6 +417,10 @@
         mLaunchingAffordanceWithPreview = launchingAffordanceWithPreview;
     }
 
+    public void setOccludeAnimationPlaying(boolean occludeAnimationPlaying) {
+        mOccludeAnimationPlaying = occludeAnimationPlaying;
+    }
+
     public boolean isLowPowerState() {
         return false;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 69b683b..06d0758 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -54,6 +54,7 @@
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.keyguard.KeyguardViewController;
 import com.android.keyguard.ViewMediatorCallback;
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dreams.DreamOverlayStateController;
@@ -282,6 +283,8 @@
     final Set<KeyguardViewManagerCallback> mCallbacks = new HashSet<>();
     private boolean mIsModernAlternateBouncerEnabled;
     private boolean mIsBackAnimationEnabled;
+    private final boolean mUdfpsNewTouchDetectionEnabled;
+    private final UdfpsOverlayInteractor mUdfpsOverlayInteractor;
 
     private OnDismissAction mAfterKeyguardGoneAction;
     private Runnable mKeyguardGoneCancelAction;
@@ -336,7 +339,9 @@
             PrimaryBouncerCallbackInteractor primaryBouncerCallbackInteractor,
             PrimaryBouncerInteractor primaryBouncerInteractor,
             BouncerView primaryBouncerView,
-            AlternateBouncerInteractor alternateBouncerInteractor) {
+            AlternateBouncerInteractor alternateBouncerInteractor,
+            UdfpsOverlayInteractor udfpsOverlayInteractor
+    ) {
         mContext = context;
         mViewMediatorCallback = callback;
         mLockPatternUtils = lockPatternUtils;
@@ -362,6 +367,8 @@
         mAlternateBouncerInteractor = alternateBouncerInteractor;
         mIsBackAnimationEnabled =
                 featureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM);
+        mUdfpsNewTouchDetectionEnabled = featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION);
+        mUdfpsOverlayInteractor = udfpsOverlayInteractor;
     }
 
     @Override
@@ -1443,16 +1450,48 @@
     }
 
     /**
+     * An opportunity for the AlternateBouncer to handle the touch instead of sending
+     * the touch to NPVC child views.
+     * @return true if the alternate bouncer should consime the touch and prevent it from
+     * going to its child views
+     */
+    public boolean dispatchTouchEvent(MotionEvent event) {
+        if (shouldInterceptTouchEvent(event)
+                && !mUdfpsOverlayInteractor.isTouchWithinUdfpsArea(event)) {
+            onTouch(event);
+        }
+        return shouldInterceptTouchEvent(event);
+    }
+
+    /**
+     * Whether the touch should be intercepted by the AlternateBouncer before going to the
+     * notification shade's child views.
+     */
+    public boolean shouldInterceptTouchEvent(MotionEvent event) {
+        return mAlternateBouncerInteractor.isVisibleState();
+    }
+
+    /**
      * For any touches on the NPVC, show the primary bouncer if the alternate bouncer is currently
      * showing.
      */
     public boolean onTouch(MotionEvent event) {
-        boolean handledTouch = false;
-        if (event.getAction() == MotionEvent.ACTION_UP
-                && mAlternateBouncerInteractor.isVisibleState()
-                && mAlternateBouncerInteractor.hasAlternateBouncerShownWithMinTime()) {
-            showPrimaryBouncer(true);
-            handledTouch = true;
+        boolean handleTouch = shouldInterceptTouchEvent(event);
+        if (handleTouch) {
+            final boolean actionDown = event.getActionMasked() == MotionEvent.ACTION_DOWN;
+            final boolean actionDownThenUp = mAlternateBouncerInteractor.getReceivedDownTouch()
+                    && event.getActionMasked() == MotionEvent.ACTION_UP;
+            final boolean udfpsOverlayWillForwardEventsOutsideNotificationShade =
+                    mUdfpsNewTouchDetectionEnabled && mKeyguardUpdateManager.isUdfpsEnrolled();
+            final boolean actionOutsideShouldDismissAlternateBouncer =
+                    event.getActionMasked() == MotionEvent.ACTION_OUTSIDE
+                    && !udfpsOverlayWillForwardEventsOutsideNotificationShade;
+            if (actionDown) {
+                mAlternateBouncerInteractor.setReceivedDownTouch(true);
+            } else if ((actionDownThenUp || actionOutsideShouldDismissAlternateBouncer)
+                    && mAlternateBouncerInteractor.hasAlternateBouncerShownWithMinTime()) {
+                showPrimaryBouncer(true);
+            }
         }
 
         // Forward NPVC touches to callbacks in case they want to respond to touches
@@ -1460,7 +1499,7 @@
             callback.onTouch(event);
         }
 
-        return handledTouch;
+        return handleTouch;
     }
 
     /** Update keyguard position based on a tapped X coordinate. */
diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
index a0a0372..209ea41 100644
--- a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
@@ -59,6 +59,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.concurrency.MessageRouter;
@@ -412,6 +413,7 @@
         private final GarbageMonitor gm;
         private ProcessMemInfo pmi;
         private boolean dumpInProgress;
+        private final PanelInteractor mPanelInteractor;
 
         @Inject
         public MemoryTile(
@@ -423,11 +425,13 @@
                 StatusBarStateController statusBarStateController,
                 ActivityStarter activityStarter,
                 QSLogger qsLogger,
-                GarbageMonitor monitor
+                GarbageMonitor monitor,
+                PanelInteractor panelInteractor
         ) {
             super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                     statusBarStateController, activityStarter, qsLogger);
             gm = monitor;
+            mPanelInteractor = panelInteractor;
         }
 
         @Override
@@ -457,7 +461,7 @@
                     mHandler.post(() -> {
                         dumpInProgress = false;
                         refreshState();
-                        getHost().collapsePanels();
+                        mPanelInteractor.collapsePanels();
                         mActivityStarter.postStartActivityDismissingKeyguard(shareIntent, 0);
                     });
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java
index a062e7b..492f231 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java
@@ -77,8 +77,11 @@
     private final FalsingManager mFalsingManager;
     private final UiEventLogger mUiEventLogger;
 
-    @VisibleForTesting String mSelectedCardId;
-    @VisibleForTesting boolean mIsDismissed;
+
+    @VisibleForTesting
+    String mSelectedCardId;
+    @VisibleForTesting
+    boolean mIsDismissed;
 
     public WalletScreenController(
             Context context,
@@ -124,9 +127,20 @@
         }
         Log.i(TAG, "Successfully retrieved wallet cards.");
         List<WalletCard> walletCards = response.getWalletCards();
-        List<WalletCardViewInfo> data = new ArrayList<>(walletCards.size());
+
+        boolean allUnknown = true;
         for (WalletCard card : walletCards) {
-            data.add(new QAWalletCardViewInfo(mContext, card));
+            if (card.getCardType() != WalletCard.CARD_TYPE_UNKNOWN) {
+                allUnknown = false;
+                break;
+            }
+        }
+
+        List<WalletCardViewInfo> paymentCardData = new ArrayList<>();
+        for (WalletCard card : walletCards) {
+            if (allUnknown || card.getCardType() == WalletCard.CARD_TYPE_PAYMENT) {
+                paymentCardData.add(new QAWalletCardViewInfo(mContext, card));
+            }
         }
 
         // Get on main thread for UI updates.
@@ -134,18 +148,18 @@
             if (mIsDismissed) {
                 return;
             }
-            if (data.isEmpty()) {
+            if (paymentCardData.isEmpty()) {
                 showEmptyStateView();
             } else {
                 int selectedIndex = response.getSelectedIndex();
-                if (selectedIndex >= data.size()) {
+                if (selectedIndex >= paymentCardData.size()) {
                     Log.w(TAG, "Invalid selected card index, showing empty state.");
                     showEmptyStateView();
                 } else {
                     boolean isUdfpsEnabled = mKeyguardUpdateMonitor.isUdfpsEnrolled()
                             && mKeyguardUpdateMonitor.isFingerprintDetectionRunning();
                     mWalletView.showCardCarousel(
-                            data,
+                            paymentCardData,
                             selectedIndex,
                             !mKeyguardStateController.isUnlocked(),
                             isUdfpsEnabled);
@@ -213,7 +227,6 @@
     }
 
 
-
     @Override
     public void onCardClicked(@NonNull WalletCardViewInfo cardInfo) {
         if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
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/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
new file mode 100644
index 0000000..eb86c05
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
@@ -0,0 +1,131 @@
+/*
+ * 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.keyguard
+
+import android.telephony.PinResult
+import android.telephony.TelephonyManager
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.LayoutInflater
+import androidx.test.filters.SmallTest
+import com.android.internal.util.LatencyTracker
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.util.mockito.any
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class KeyguardSimPinViewControllerTest : SysuiTestCase() {
+    private lateinit var simPinView: KeyguardSimPinView
+    private lateinit var underTest: KeyguardSimPinViewController
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    @Mock private lateinit var securityMode: KeyguardSecurityModel.SecurityMode
+    @Mock private lateinit var lockPatternUtils: LockPatternUtils
+    @Mock private lateinit var keyguardSecurityCallback: KeyguardSecurityCallback
+    @Mock private lateinit var messageAreaControllerFactory: KeyguardMessageAreaController.Factory
+    @Mock private lateinit var latencyTracker: LatencyTracker
+    @Mock private lateinit var liftToActivateListener: LiftToActivateListener
+    @Mock private lateinit var telephonyManager: TelephonyManager
+    @Mock private lateinit var falsingCollector: FalsingCollector
+    @Mock private lateinit var emergencyButtonController: EmergencyButtonController
+    @Mock
+    private lateinit var keyguardMessageAreaController:
+        KeyguardMessageAreaController<BouncerKeyguardMessageArea>
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        `when`(messageAreaControllerFactory.create(Mockito.any(KeyguardMessageArea::class.java)))
+            .thenReturn(keyguardMessageAreaController)
+        `when`(telephonyManager.createForSubscriptionId(anyInt())).thenReturn(telephonyManager)
+        `when`(telephonyManager.supplyIccLockPin(anyString()))
+            .thenReturn(mock(PinResult::class.java))
+        simPinView =
+            LayoutInflater.from(context).inflate(R.layout.keyguard_sim_pin_view, null)
+                as KeyguardSimPinView
+        underTest =
+            KeyguardSimPinViewController(
+                simPinView,
+                keyguardUpdateMonitor,
+                securityMode,
+                lockPatternUtils,
+                keyguardSecurityCallback,
+                messageAreaControllerFactory,
+                latencyTracker,
+                liftToActivateListener,
+                telephonyManager,
+                falsingCollector,
+                emergencyButtonController
+            )
+        underTest.init()
+    }
+
+    @Test
+    fun onViewAttached() {
+        underTest.onViewAttached()
+    }
+
+    @Test
+    fun onViewDetached() {
+        underTest.onViewDetached()
+    }
+
+    @Test
+    fun onResume() {
+        underTest.onResume(KeyguardSecurityView.VIEW_REVEALED)
+        verify(keyguardUpdateMonitor)
+            .registerCallback(any(KeyguardUpdateMonitorCallback::class.java))
+    }
+
+    @Test
+    fun onPause() {
+        underTest.onPause()
+        verify(keyguardUpdateMonitor).removeCallback(any(KeyguardUpdateMonitorCallback::class.java))
+    }
+
+    @Test
+    fun startAppearAnimation() {
+        underTest.startAppearAnimation()
+        verify(keyguardMessageAreaController)
+            .setMessage(context.resources.getString(R.string.keyguard_enter_your_pin), false)
+    }
+
+    @Test
+    fun startDisappearAnimation() {
+        underTest.startDisappearAnimation {}
+    }
+
+    @Test
+    fun resetState() {
+        underTest.resetState()
+        verify(keyguardMessageAreaController).setMessage("")
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
new file mode 100644
index 0000000..2dcca55
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
@@ -0,0 +1,132 @@
+/*
+ * 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.keyguard
+
+import android.telephony.PinResult
+import android.telephony.TelephonyManager
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.LayoutInflater
+import androidx.test.filters.SmallTest
+import com.android.internal.util.LatencyTracker
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.util.mockito.any
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class KeyguardSimPukViewControllerTest : SysuiTestCase() {
+    private lateinit var simPukView: KeyguardSimPukView
+    private lateinit var underTest: KeyguardSimPukViewController
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    @Mock private lateinit var securityMode: KeyguardSecurityModel.SecurityMode
+    @Mock private lateinit var lockPatternUtils: LockPatternUtils
+    @Mock private lateinit var keyguardSecurityCallback: KeyguardSecurityCallback
+    @Mock private lateinit var messageAreaControllerFactory: KeyguardMessageAreaController.Factory
+    @Mock private lateinit var latencyTracker: LatencyTracker
+    @Mock private lateinit var liftToActivateListener: LiftToActivateListener
+    @Mock private lateinit var telephonyManager: TelephonyManager
+    @Mock private lateinit var falsingCollector: FalsingCollector
+    @Mock private lateinit var emergencyButtonController: EmergencyButtonController
+    @Mock
+    private lateinit var keyguardMessageAreaController:
+        KeyguardMessageAreaController<BouncerKeyguardMessageArea>
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        Mockito.`when`(
+                messageAreaControllerFactory.create(Mockito.any(KeyguardMessageArea::class.java))
+            )
+            .thenReturn(keyguardMessageAreaController)
+        Mockito.`when`(telephonyManager.createForSubscriptionId(Mockito.anyInt()))
+            .thenReturn(telephonyManager)
+        Mockito.`when`(telephonyManager.supplyIccLockPuk(anyString(), anyString()))
+            .thenReturn(Mockito.mock(PinResult::class.java))
+        simPukView =
+            LayoutInflater.from(context).inflate(R.layout.keyguard_sim_puk_view, null)
+                as KeyguardSimPukView
+        underTest =
+            KeyguardSimPukViewController(
+                simPukView,
+                keyguardUpdateMonitor,
+                securityMode,
+                lockPatternUtils,
+                keyguardSecurityCallback,
+                messageAreaControllerFactory,
+                latencyTracker,
+                liftToActivateListener,
+                telephonyManager,
+                falsingCollector,
+                emergencyButtonController
+            )
+        underTest.init()
+    }
+
+    @Test
+    fun onViewAttached() {
+        underTest.onViewAttached()
+        Mockito.verify(keyguardUpdateMonitor)
+            .registerCallback(any(KeyguardUpdateMonitorCallback::class.java))
+    }
+
+    @Test
+    fun onViewDetached() {
+        underTest.onViewDetached()
+        Mockito.verify(keyguardUpdateMonitor)
+            .removeCallback(any(KeyguardUpdateMonitorCallback::class.java))
+    }
+
+    @Test
+    fun onResume() {
+        underTest.onResume(KeyguardSecurityView.VIEW_REVEALED)
+    }
+
+    @Test
+    fun onPause() {
+        underTest.onPause()
+    }
+
+    @Test
+    fun startAppearAnimation() {
+        underTest.startAppearAnimation()
+        Mockito.verify(keyguardMessageAreaController)
+            .setMessage(context.resources.getString(R.string.keyguard_enter_your_pin), false)
+    }
+
+    @Test
+    fun startDisappearAnimation() {
+        underTest.startDisappearAnimation {}
+    }
+
+    @Test
+    fun resetState() {
+        underTest.resetState()
+        Mockito.verify(keyguardMessageAreaController)
+            .setMessage(context.resources.getString(R.string.kg_puk_enter_puk_hint))
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt
index ca6f426..eb82956 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt
@@ -25,12 +25,19 @@
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView
+import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.settings.SecureSettings
 import com.android.systemui.util.settings.SystemSettings
+import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.MockitoAnnotations
+
+private const val ON: Int = 1
+private const val OFF: Int = 0
 
 /** Tests for [FontScalingDialog]. */
 @SmallTest
@@ -39,6 +46,8 @@
 class FontScalingDialogTest : SysuiTestCase() {
     private lateinit var fontScalingDialog: FontScalingDialog
     private lateinit var systemSettings: SystemSettings
+    private lateinit var secureSettings: SecureSettings
+    private lateinit var backgroundExecutor: FakeExecutor
     private val fontSizeValueArray: Array<String> =
         mContext
             .getResources()
@@ -46,9 +55,13 @@
 
     @Before
     fun setUp() {
+        MockitoAnnotations.initMocks(this)
         val mainHandler = Handler(TestableLooper.get(this).getLooper())
         systemSettings = FakeSettings()
-        fontScalingDialog = FontScalingDialog(mContext, systemSettings as FakeSettings)
+        secureSettings = FakeSettings()
+        backgroundExecutor = FakeExecutor(FakeSystemClock())
+        fontScalingDialog =
+            FontScalingDialog(mContext, systemSettings, secureSettings, backgroundExecutor)
     }
 
     @Test
@@ -76,6 +89,7 @@
         seekBarWithIconButtonsView.setProgress(0)
 
         iconEndFrame.performClick()
+        backgroundExecutor.runAllReady()
 
         val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, /* def = */ 1.0f)
         assertThat(seekBar.getProgress()).isEqualTo(1)
@@ -96,6 +110,7 @@
         seekBarWithIconButtonsView.setProgress(fontSizeValueArray.size - 1)
 
         iconStartFrame.performClick()
+        backgroundExecutor.runAllReady()
 
         val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, /* def = */ 1.0f)
         assertThat(seekBar.getProgress()).isEqualTo(fontSizeValueArray.size - 2)
@@ -104,4 +119,26 @@
 
         fontScalingDialog.dismiss()
     }
+
+    @Test
+    fun progressChanged_keyWasNotSetBefore_fontScalingHasBeenChangedIsOn() {
+        fontScalingDialog.show()
+
+        val seekBarWithIconButtonsView: SeekBarWithIconButtonsView =
+            fontScalingDialog.findViewById(R.id.font_scaling_slider)!!
+        secureSettings.putInt(Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED, OFF)
+
+        // Default seekbar progress for font size is 1, set it to another progress 0
+        seekBarWithIconButtonsView.setProgress(0)
+        backgroundExecutor.runAllReady()
+
+        val currentSettings =
+            secureSettings.getInt(
+                Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED,
+                /* def = */ OFF
+            )
+        assertThat(currentSettings).isEqualTo(ON)
+
+        fontScalingDialog.dismiss()
+    }
 }
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/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 9a73898..445cc87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -53,6 +53,7 @@
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.hardware.fingerprint.IUdfpsOverlayControllerCallback;
+import android.hardware.input.InputManager;
 import android.os.Handler;
 import android.os.PowerManager;
 import android.os.RemoteException;
@@ -63,6 +64,7 @@
 import android.view.MotionEvent;
 import android.view.Surface;
 import android.view.View;
+import android.view.ViewRootImpl;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
 
@@ -231,6 +233,10 @@
     private FingerprintSensorPropertiesInternal mOpticalProps;
     private FingerprintSensorPropertiesInternal mUltrasonicProps;
     private UdfpsUtils mUdfpsUtils;
+    @Mock
+    private InputManager mInputManager;
+    @Mock
+    private ViewRootImpl mViewRootImpl;
 
     @Before
     public void setUp() {
@@ -248,6 +254,7 @@
         when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
         when(mSessionTracker.getSessionId(anyInt())).thenReturn(
                 (new InstanceIdSequence(1 << 20)).newInstanceId());
+        when(mUdfpsView.getViewRootImpl()).thenReturn(mViewRootImpl);
 
         final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
         componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */,
@@ -304,7 +311,7 @@
                 mUnlockedScreenOffAnimationController, mSystemUIDialogManager, mLatencyTracker,
                 mActivityLaunchAnimator, alternateTouchProvider, mBiometricExecutor,
                 mPrimaryBouncerInteractor, mSinglePointerTouchProcessor, mSessionTracker,
-                mAlternateBouncerInteractor, mSecureSettings, mUdfpsUtils);
+                mAlternateBouncerInteractor, mSecureSettings, mInputManager, mUdfpsUtils);
         verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
         mOverlayController = mOverlayCaptor.getValue();
         verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
@@ -1262,6 +1269,81 @@
     }
 
     @Test
+    public void onTouch_withNewTouchDetection_pilferPointer() throws RemoteException {
+        final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L,
+                0L);
+        final TouchProcessorResult processorResultDown = new TouchProcessorResult.ProcessedTouch(
+                InteractionEvent.DOWN, 1 /* pointerId */, touchData);
+
+        // Enable new touch detection.
+        when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true);
+
+        // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider.
+        initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */);
+
+        // Configure UdfpsView to accept the ACTION_DOWN event
+        when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
+
+        // GIVEN that the overlay is showing and a11y touch exploration NOT enabled
+        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
+                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+        mFgExecutor.runAllReady();
+
+        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+
+        // WHEN ACTION_DOWN is received
+        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+                processorResultDown);
+        MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
+        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
+        mBiometricExecutor.runAllReady();
+        downEvent.recycle();
+
+        // THEN the touch is pilfered, expected twice (valid overlap and touch on sensor)
+        verify(mInputManager, times(2)).pilferPointers(any());
+    }
+
+    @Test
+    public void onTouch_withNewTouchDetection_doNotPilferPointer() throws RemoteException {
+        final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L,
+                0L);
+        final TouchProcessorResult processorResultUnchanged =
+                new TouchProcessorResult.ProcessedTouch(InteractionEvent.UNCHANGED,
+                        1 /* pointerId */, touchData);
+
+        // Enable new touch detection.
+        when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true);
+
+        // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider.
+        initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */);
+
+        // Configure UdfpsView to not accept the ACTION_DOWN event
+        when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(false);
+
+        // GIVEN that the overlay is showing and a11y touch exploration NOT enabled
+        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
+                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+        mFgExecutor.runAllReady();
+
+        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+
+        // WHEN ACTION_DOWN is received and touch is not within sensor
+        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+                processorResultUnchanged);
+        MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
+        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
+        mBiometricExecutor.runAllReady();
+        downEvent.recycle();
+
+        // THEN the touch is NOT pilfered
+        verify(mInputManager, times(0)).pilferPointers(any());
+    }
+
+    @Test
     public void onAodInterrupt_onAcquiredGood_fingerNoLongerDown() throws RemoteException {
         // GIVEN UDFPS overlay is showing
         mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
@@ -1285,6 +1367,5 @@
 
         // THEN is fingerDown should be FALSE
         assertFalse(mUdfpsController.isFingerDown());
-
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
index 87d5ae6..9431d86 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
@@ -77,16 +77,14 @@
 
             runCurrent()
 
-            // Then touch should not be intercepted
-            val canInterceptTrue = underTest.canInterceptTouchInUdfpsBounds(downEv)
-            assertThat(canInterceptTrue).isFalse()
+            // Then touch is within udfps area
+            assertThat(underTest.isTouchWithinUdfpsArea(downEv)).isTrue()
 
             // When touch is outside of bounds
             whenever(overlayBounds.contains(downEv.x.toInt(), downEv.y.toInt())).thenReturn(false)
 
-            // Then touch should be intercepted
-            val canInterceptFalse = underTest.canInterceptTouchInUdfpsBounds(downEv)
-            assertThat(canInterceptFalse).isTrue()
+            // Then touch is not within udfps area
+            assertThat(underTest.isTouchWithinUdfpsArea(downEv)).isFalse()
         }
 
     @Test
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/dreams/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
index 3312c43..aad49f9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
@@ -35,7 +35,6 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.view.LaunchableImageView;
 import com.android.systemui.condition.SelfExecutingMonitor;
@@ -89,9 +88,6 @@
     private ArgumentCaptor<ControlsListingController.ControlsListingCallback> mCallbackCaptor;
 
     @Mock
-    private View mView;
-
-    @Mock
     private LaunchableImageView mHomeControlsView;
 
     @Mock
@@ -115,7 +111,6 @@
         when(mControlsComponent.getControlsListingController()).thenReturn(
                 Optional.of(mControlsListingController));
         when(mControlsComponent.getVisibility()).thenReturn(AVAILABLE);
-        when(mView.findViewById(R.id.home_controls_chip)).thenReturn(mHomeControlsView);
 
         mMonitor = SelfExecutingMonitor.createInstance();
     }
@@ -223,7 +218,7 @@
     public void testClick_logsUiEvent() {
         final DreamHomeControlsComplication.DreamHomeControlsChipViewController viewController =
                 new DreamHomeControlsComplication.DreamHomeControlsChipViewController(
-                        mView,
+                        mHomeControlsView,
                         mActivityStarter,
                         mContext,
                         mControlsComponent,
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/controls/ui/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
index 0fac3db..4565762 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
@@ -21,7 +21,6 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.View
-import androidx.constraintlayout.widget.ConstraintSet
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
@@ -61,8 +60,6 @@
     @Mock private lateinit var mediaSubTitleWidgetState: WidgetState
     @Mock private lateinit var mediaContainerWidgetState: WidgetState
     @Mock private lateinit var mediaFlags: MediaFlags
-    @Mock private lateinit var expandedLayout: ConstraintSet
-    @Mock private lateinit var collapsedLayout: ConstraintSet
 
     val delta = 0.1F
 
@@ -82,16 +79,47 @@
     }
 
     @Test
-    fun testOrientationChanged_layoutsAreLoaded() {
-        mediaViewController.expandedLayout = expandedLayout
-        mediaViewController.collapsedLayout = collapsedLayout
-
+    fun testOrientationChanged_heightOfPlayerIsUpdated() {
         val newConfig = Configuration()
+
+        mediaViewController.attach(player, MediaViewController.TYPE.PLAYER)
+        // Change the height to see the effect of orientation change.
+        MediaViewController.backgroundIds.forEach { id ->
+            mediaViewController.expandedLayout.getConstraint(id).layout.mHeight = 10
+        }
         newConfig.orientation = ORIENTATION_LANDSCAPE
         configurationController.onConfigurationChanged(newConfig)
 
-        verify(expandedLayout).load(context, R.xml.media_session_expanded)
-        verify(collapsedLayout).load(context, R.xml.media_session_collapsed)
+        MediaViewController.backgroundIds.forEach { id ->
+            assertTrue(
+                mediaViewController.expandedLayout.getConstraint(id).layout.mHeight ==
+                    context.resources.getDimensionPixelSize(
+                        R.dimen.qs_media_session_height_expanded
+                    )
+            )
+        }
+    }
+
+    @Test
+    fun testOrientationChanged_heightOfRecCardIsUpdated() {
+        val newConfig = Configuration()
+
+        mediaViewController.attach(recommendation, MediaViewController.TYPE.RECOMMENDATION)
+        // Change the height to see the effect of orientation change.
+        mediaViewController.expandedLayout
+            .getConstraint(MediaViewController.recSizingViewId)
+            .layout
+            .mHeight = 10
+        newConfig.orientation = ORIENTATION_LANDSCAPE
+        configurationController.onConfigurationChanged(newConfig)
+
+        assertTrue(
+            mediaViewController.expandedLayout
+                .getConstraint(MediaViewController.recSizingViewId)
+                .layout
+                .mHeight ==
+                context.resources.getDimensionPixelSize(R.dimen.qs_media_session_height_expanded)
+        )
     }
 
     @Test
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/navigationbar/gestural/BackPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
index bc31a0e..8e32f81 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
@@ -127,7 +127,7 @@
                 mBackPanelController.params.deactivationSwipeTriggerThreshold
         )
         clearInvocations(backCallback)
-        Thread.sleep(MIN_DURATION_ACTIVE_ANIMATION)
+        Thread.sleep(MIN_DURATION_ACTIVE_BEFORE_INACTIVE_ANIMATION)
         // Move in the opposite direction to cross the deactivation threshold and cancel back
         continueTouch(START_X)
 
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..0a8cd26 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -216,6 +216,41 @@
     }
 
     @Test
+    fun showNoteTaskWithUser_keyguardIsLocked_shouldStartActivityWithExpectedUserAndLogUiEvent() {
+        val user10 = UserHandle.of(/* userId= */ 10)
+        val expectedInfo =
+            noteTaskInfo.copy(
+                entryPoint = NoteTaskEntryPoint.TAIL_BUTTON,
+                isKeyguardLocked = true,
+            )
+        whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked)
+        whenever(resolver.resolveInfo(any(), any())).thenReturn(expectedInfo)
+
+        createNoteTaskController()
+            .showNoteTaskAsUser(
+                entryPoint = expectedInfo.entryPoint!!,
+                user = user10,
+            )
+
+        val intentCaptor = argumentCaptor<Intent>()
+        val userCaptor = argumentCaptor<UserHandle>()
+        verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor))
+        intentCaptor.value.let { intent ->
+            assertThat(intent.action).isEqualTo(Intent.ACTION_CREATE_NOTE)
+            assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
+            assertThat(intent.flags and FLAG_ACTIVITY_NEW_TASK).isEqualTo(FLAG_ACTIVITY_NEW_TASK)
+            assertThat(intent.flags and FLAG_ACTIVITY_MULTIPLE_TASK)
+                .isEqualTo(FLAG_ACTIVITY_MULTIPLE_TASK)
+            assertThat(intent.flags and FLAG_ACTIVITY_NEW_DOCUMENT)
+                .isEqualTo(FLAG_ACTIVITY_NEW_DOCUMENT)
+            assertThat(intent.getBooleanExtra(Intent.EXTRA_USE_STYLUS_MODE, false)).isTrue()
+        }
+        assertThat(userCaptor.value).isEqualTo(user10)
+        verify(eventLogger).logNoteTaskOpened(expectedInfo)
+        verifyZeroInteractions(bubbles)
+    }
+
+    @Test
     fun showNoteTask_keyguardIsUnlocked_shouldStartBubblesWithoutLoggingUiEvent() {
         val expectedInfo =
             noteTaskInfo.copy(
@@ -232,7 +267,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 +401,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 +424,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/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
index 64e9a3e..7e052bf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
@@ -29,6 +29,7 @@
 import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Intent;
+import android.os.Binder;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -41,6 +42,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -91,6 +93,8 @@
     private TileLifecycleManager mTileLifecycleManager;
     @Mock
     private QSHost mQSHost;
+    @Mock
+    private PanelInteractor mPanelInteractor;
 
     @Before
     public void setUp() throws Exception {
@@ -107,7 +111,8 @@
         Provider<Handler> provider = () -> new Handler(mTestableLooper.getLooper());
 
         mTileService = new TestTileServices(mQSHost, provider, mBroadcastDispatcher,
-                mUserTracker, mKeyguardStateController, mCommandQueue, mStatusBarIconController);
+                mUserTracker, mKeyguardStateController, mCommandQueue, mStatusBarIconController,
+                mPanelInteractor);
     }
 
     @After
@@ -222,13 +227,37 @@
         verify(tile, never()).startActivityAndCollapse(pi);
     }
 
+    @Test
+    public void testOnStartActivityCollapsesPanel() {
+        CustomTile tile = mock(CustomTile.class);
+        ComponentName componentName = mock(ComponentName.class);
+        when(tile.getComponent()).thenReturn(componentName);
+        when(componentName.getPackageName()).thenReturn(this.getContext().getPackageName());
+        TileServiceManager manager = mTileService.getTileWrapper(tile);
+
+        mTileService.onStartActivity(manager.getToken());
+        verify(mPanelInteractor).forceCollapsePanels();
+    }
+
+    @Test
+    public void testOnShowDialogCollapsesPanel() {
+        CustomTile tile = mock(CustomTile.class);
+        ComponentName componentName = mock(ComponentName.class);
+        when(tile.getComponent()).thenReturn(componentName);
+        when(componentName.getPackageName()).thenReturn(this.getContext().getPackageName());
+        TileServiceManager manager = mTileService.getTileWrapper(tile);
+
+        mTileService.onShowDialog(manager.getToken());
+        verify(mPanelInteractor).forceCollapsePanels();
+    }
+
     private class TestTileServices extends TileServices {
         TestTileServices(QSHost host, Provider<Handler> handlerProvider,
                 BroadcastDispatcher broadcastDispatcher, UserTracker userTracker,
                 KeyguardStateController keyguardStateController, CommandQueue commandQueue,
-                StatusBarIconController statusBarIconController) {
+                StatusBarIconController statusBarIconController, PanelInteractor panelInteractor) {
             super(host, handlerProvider, broadcastDispatcher, userTracker, keyguardStateController,
-                    commandQueue, statusBarIconController);
+                    commandQueue, statusBarIconController, panelInteractor);
         }
 
         @Override
@@ -237,6 +266,8 @@
             TileServiceManager manager = mock(TileServiceManager.class);
             mManagers.add(manager);
             when(manager.isLifecycleStarted()).thenReturn(true);
+            Binder b = new Binder();
+            when(manager.getToken()).thenReturn(b);
             return manager;
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt
new file mode 100644
index 0000000..45783ab
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.qs.pipeline.domain.interactor
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.phone.CentralSurfaces
+import java.util.Optional
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class PanelInteractorImplTest : SysuiTestCase() {
+
+    @Mock private lateinit var centralSurfaces: CentralSurfaces
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+    }
+
+    @Test
+    fun openPanels_callsCentralSurfaces() {
+        val underTest = PanelInteractorImpl(Optional.of(centralSurfaces))
+
+        underTest.openPanels()
+
+        verify(centralSurfaces).postAnimateOpenPanels()
+    }
+
+    @Test
+    fun collapsePanels_callsCentralSurfaces() {
+        val underTest = PanelInteractorImpl(Optional.of(centralSurfaces))
+
+        underTest.collapsePanels()
+
+        verify(centralSurfaces).postAnimateCollapsePanels()
+    }
+
+    @Test
+    fun forceCollapsePanels_callsCentralSurfaces() {
+        val underTest = PanelInteractorImpl(Optional.of(centralSurfaces))
+
+        underTest.forceCollapsePanels()
+
+        verify(centralSurfaces).postAnimateForceCollapsePanels()
+    }
+
+    @Test
+    fun whenOptionalEmpty_doesnThrow() {
+        val underTest = PanelInteractorImpl(Optional.empty())
+
+        underTest.openPanels()
+        underTest.collapsePanels()
+        underTest.forceCollapsePanels()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt
index bd99cd4..eeebd4f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt
@@ -84,6 +84,7 @@
                 qsLogger,
                 dialogLaunchAnimator,
                 FakeSettings(),
+                FakeSettings(),
                 featureFlags
             )
         fontScalingTile.initialize()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt
index 33921c7..3642e87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt
@@ -31,9 +31,12 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.statusbar.policy.LocationController
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
 import org.junit.Before
@@ -41,6 +44,7 @@
 import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.Mockito.`when`
+import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
 @RunWith(AndroidTestingRunner::class)
@@ -65,6 +69,8 @@
     private lateinit var locationController: LocationController
     @Mock
     private lateinit var keyguardStateController: KeyguardStateController
+    @Mock
+    private lateinit var panelInteractor: PanelInteractor
 
     private val uiEventLogger = UiEventLoggerFake()
     private lateinit var testableLooper: TestableLooper
@@ -86,7 +92,9 @@
             activityStarter,
             qsLogger,
             locationController,
-            keyguardStateController)
+            keyguardStateController,
+            panelInteractor,
+        )
     }
 
     @After
@@ -116,4 +124,18 @@
         assertThat(state.icon)
             .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_location_icon_on))
     }
+
+    @Test
+    fun testClickWhenLockedWillCallOpenPanels() {
+        `when`(keyguardStateController.isMethodSecure).thenReturn(true)
+        `when`(keyguardStateController.isShowing).thenReturn(true)
+
+        tile.handleClick(null)
+
+        val captor = argumentCaptor<Runnable>()
+        verify(activityStarter).postQSRunnableDismissingKeyguard(capture(captor))
+        captor.value.run()
+
+        verify(panelInteractor).openPanels()
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
index 5aef758..d9ed1a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
@@ -45,6 +45,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.screenrecord.RecordingController;
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
@@ -83,6 +84,8 @@
     private KeyguardStateController mKeyguardStateController;
     @Mock
     private DialogLaunchAnimator mDialogLaunchAnimator;
+    @Mock
+    private PanelInteractor mPanelInteractor;
 
     private TestableLooper mTestableLooper;
     private ScreenRecordTile mTile;
@@ -108,7 +111,8 @@
                 mController,
                 mKeyguardDismissUtil,
                 mKeyguardStateController,
-                mDialogLaunchAnimator
+                mDialogLaunchAnimator,
+                mPanelInteractor
         );
 
         mTile.initialize();
@@ -146,7 +150,7 @@
         assertNotNull(onStartRecordingClicked.getValue());
         onStartRecordingClicked.getValue().run();
         verify(mDialogLaunchAnimator).disableAllCurrentDialogsExitAnimations();
-        verify(mHost).collapsePanels();
+        verify(mPanelInteractor).collapsePanels();
     }
 
     // Test that the tile is active and labeled correctly when the controller is starting
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 526dc8d..dd79297 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -223,6 +223,16 @@
     }
 
     @Test
+    public void attach_fadingAway_wallpaperVisible() {
+        clearInvocations(mWindowManager);
+        mNotificationShadeWindowController.attach();
+        mNotificationShadeWindowController.setKeyguardFadingAway(true);
+
+        verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
+        assertThat((mLayoutParameters.getValue().flags & FLAG_SHOW_WALLPAPER) != 0).isTrue();
+    }
+
+    @Test
     public void setBackgroundBlurRadius_expandedWithBlurs() {
         mNotificationShadeWindowController.setBackgroundBlurRadius(10);
         verify(mNotificationShadeWindowView).setVisibility(eq(View.VISIBLE));
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..629208e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -26,15 +26,18 @@
 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.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 +51,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
@@ -60,13 +67,15 @@
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
 
+@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
@@ -86,8 +95,6 @@
     @Mock private lateinit var phoneStatusBarViewController: PhoneStatusBarViewController
     @Mock private lateinit var pulsingGestureListener: PulsingGestureListener
     @Mock private lateinit var notificationInsetsController: NotificationInsetsController
-    @Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
-    @Mock private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor
     @Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
     @Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent
     @Mock lateinit var keyguardSecurityContainerController: KeyguardSecurityContainerController
@@ -100,6 +107,8 @@
 
     private lateinit var underTest: NotificationShadeWindowViewController
 
+    private lateinit var testScope: TestScope
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
@@ -112,6 +121,14 @@
             .thenReturn(keyguardSecurityContainerController)
         whenever(keyguardTransitionInteractor.lockscreenToDreamingTransition)
             .thenReturn(emptyFlow<TransitionStep>())
+
+        val featureFlags = FakeFeatureFlags()
+        featureFlags.set(Flags.TRACKPAD_GESTURE_COMMON, true)
+        featureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, false)
+        featureFlags.set(Flags.DUAL_SHADE, false)
+
+        val inputProxy = MultiShadeInputProxy()
+        testScope = TestScope()
         underTest =
             NotificationShadeWindowViewController(
                 lockscreenShadeTransitionController,
@@ -134,10 +151,21 @@
                 pulsingGestureListener,
                 keyguardBouncerViewModel,
                 keyguardBouncerComponentFactory,
-                alternateBouncerInteractor,
-                udfpsOverlayInteractor,
                 keyguardTransitionInteractor,
                 primaryBouncerToGoneTransitionViewModel,
+                featureFlags,
+                {
+                    MultiShadeInteractor(
+                        applicationScope = testScope.backgroundScope,
+                        repository =
+                            MultiShadeRepository(
+                                applicationContext = context,
+                                inputProxy = inputProxy,
+                            ),
+                        inputProxy = inputProxy,
+                    )
+                },
+                FakeSystemClock(),
             )
         underTest.setupExpandedStatusBar()
 
@@ -150,147 +178,160 @@
     // 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_statusBarKeyguardViewManagerShouldIntercept() {
+        // down event should be intercepted by keyguardViewManager
+        whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(DOWN_EVENT))
+                .thenReturn(true)
 
         // Then touch should not be intercepted
-        val shouldIntercept = interactionEventHandler.shouldInterceptTouchEvent(downEv)
-        assertThat(shouldIntercept).isFalse()
+        val shouldIntercept = interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)
+        assertThat(shouldIntercept).isTrue()
     }
 
     @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..b4b5ec1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -0,0 +1,244 @@
+/*
+ * 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.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.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 keyguardTransitionInteractor: KeyguardTransitionInteractor
+    @Mock
+    private lateinit var primaryBouncerToGoneTransitionViewModel:
+        PrimaryBouncerToGoneTransitionViewModel
+    @Captor
+    private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler>
+
+    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_COMMON, true)
+        featureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, 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,
+                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(statusBarKeyguardViewManager.shouldInterceptTouchEvent(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(statusBarKeyguardViewManager.shouldInterceptTouchEvent(any()))
+                .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(statusBarKeyguardViewManager.onTouch(any())).thenReturn(true)
+            whenever(dragDownHelper.onInterceptTouchEvent(any())).thenReturn(false)
+
+            // THEN we should handle the touch
+            assertThat(interactionEventHandler.handleTouchEvent(mock())).isTrue()
+        }
+
+    private fun captureInteractionEventHandler() {
+        verify(underTest).setInteractionEventHandler(interactionEventHandlerCaptor.capture())
+        interactionEventHandler = interactionEventHandlerCaptor.value
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
index 15b8423..d8ffe39 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
@@ -275,9 +275,7 @@
 
     @Test
     public void testPanelStaysOpenWhenClosingQs() {
-        mShadeExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 1,
-                /* expanded= */ true, /* tracking= */ false, /* dragDownPxAmount= */ 0);
-        mQsController.setShadeExpandedHeight(1);
+        mQsController.setShadeExpansion(/* shadeExpandedHeight= */ 1, /* expandedFraction=*/ 1);
 
         float shadeExpandedHeight = mQsController.getShadeExpandedHeight();
         mQsController.animateCloseQs(false);
@@ -289,7 +287,7 @@
     public void interceptTouchEvent_withinQs_shadeExpanded_startsQsTracking() {
         mQsController.setQs(mQs);
 
-        mQsController.setShadeExpandedHeight(1f);
+        mQsController.setShadeExpansion(/* shadeExpandedHeight= */ 1, /* expandedFraction=*/ 1);
         mQsController.onIntercept(
                 createMotionEvent(0, 0, ACTION_DOWN));
         mQsController.onIntercept(
@@ -303,7 +301,7 @@
         enableSplitShade(true);
         mQsController.setQs(mQs);
 
-        mQsController.setShadeExpandedHeight(1f);
+        mQsController.setShadeExpansion(/* shadeExpandedHeight= */ 1, /* expandedFraction=*/ 1);
         mQsController.onIntercept(
                 createMotionEvent(0, 0, ACTION_DOWN));
         mQsController.onIntercept(
@@ -342,13 +340,8 @@
     public void handleTouch_downActionInQsArea() {
         mQsController.setQs(mQs);
         mQsController.setBarState(SHADE);
-        mQsController.onPanelExpansionChanged(
-                new ShadeExpansionChangeEvent(
-                        0.5f,
-                        true,
-                        true,
-                        0
-                ));
+        mQsController.setShadeExpansion(/* shadeExpandedHeight= */ 1, /* expandedFraction=*/ 0.5f);
+
         MotionEvent event =
                 createMotionEvent(QS_FRAME_WIDTH / 2, QS_FRAME_BOTTOM / 2, ACTION_DOWN);
         mQsController.handleTouch(event, false, false);
@@ -385,7 +378,7 @@
     @Test
     public void handleTouch_isConflictingExpansionGestureSet() {
         assertThat(mQsController.isConflictingExpansionGesture()).isFalse();
-        mShadeExpansionStateManager.onPanelExpansionChanged(1f, true, false, 0f);
+        mQsController.setShadeExpansion(/* shadeExpandedHeight= */ 1, /* expandedFraction=*/ 1);
         mQsController.handleTouch(MotionEvent.obtain(0L /* downTime */,
                 0L /* eventTime */, ACTION_DOWN, 0f /* x */, 0f /* y */,
                 0 /* metaState */), false, false);
@@ -394,7 +387,7 @@
 
     @Test
     public void handleTouch_isConflictingExpansionGestureSet_cancel() {
-        mShadeExpansionStateManager.onPanelExpansionChanged(1f, true, false, 0f);
+        mQsController.setShadeExpansion(/* shadeExpandedHeight= */ 1, /* expandedFraction=*/ 1);
         mQsController.handleTouch(createMotionEvent(0, 0, ACTION_DOWN), false, false);
         assertThat(mQsController.isConflictingExpansionGesture()).isTrue();
         mQsController.handleTouch(createMotionEvent(0, 0, ACTION_UP), true, true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index 1fdb364..374aae1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -27,13 +27,16 @@
 import com.android.systemui.plugins.ClockProviderPlugin
 import com.android.systemui.plugins.ClockSettings
 import com.android.systemui.plugins.PluginListener
+import com.android.systemui.plugins.PluginLifecycleManager
 import com.android.systemui.plugins.PluginManager
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
 import junit.framework.Assert.assertEquals
 import junit.framework.Assert.fail
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestCoroutineScheduler
 import kotlinx.coroutines.test.TestScope
 import org.junit.Before
 import org.junit.Rule
@@ -49,6 +52,7 @@
 class ClockRegistryTest : SysuiTestCase() {
 
     @JvmField @Rule val mockito = MockitoJUnit.rule()
+    private lateinit var scheduler: TestCoroutineScheduler
     private lateinit var dispatcher: CoroutineDispatcher
     private lateinit var scope: TestScope
 
@@ -58,37 +62,38 @@
     @Mock private lateinit var mockDefaultClock: ClockController
     @Mock private lateinit var mockThumbnail: Drawable
     @Mock private lateinit var mockContentResolver: ContentResolver
+    @Mock private lateinit var mockPluginLifecycle: PluginLifecycleManager<ClockProviderPlugin>
     private lateinit var fakeDefaultProvider: FakeClockPlugin
     private lateinit var pluginListener: PluginListener<ClockProviderPlugin>
     private lateinit var registry: ClockRegistry
 
     companion object {
-        private fun failFactory(): ClockController {
-            fail("Unexpected call to createClock")
+        private fun failFactory(clockId: ClockId): ClockController {
+            fail("Unexpected call to createClock: $clockId")
             return null!!
         }
 
-        private fun failThumbnail(): Drawable? {
-            fail("Unexpected call to getThumbnail")
+        private fun failThumbnail(clockId: ClockId): Drawable? {
+            fail("Unexpected call to getThumbnail: $clockId")
             return null
         }
     }
 
     private class FakeClockPlugin : ClockProviderPlugin {
         private val metadata = mutableListOf<ClockMetadata>()
-        private val createCallbacks = mutableMapOf<ClockId, () -> ClockController>()
-        private val thumbnailCallbacks = mutableMapOf<ClockId, () -> Drawable?>()
+        private val createCallbacks = mutableMapOf<ClockId, (ClockId) -> ClockController>()
+        private val thumbnailCallbacks = mutableMapOf<ClockId, (ClockId) -> Drawable?>()
 
         override fun getClocks() = metadata
         override fun createClock(settings: ClockSettings): ClockController =
-            createCallbacks[settings.clockId!!]!!()
-        override fun getClockThumbnail(id: ClockId): Drawable? = thumbnailCallbacks[id]!!()
+            createCallbacks[settings.clockId!!]!!(settings.clockId!!)
+        override fun getClockThumbnail(id: ClockId): Drawable? = thumbnailCallbacks[id]!!(id)
 
         fun addClock(
             id: ClockId,
             name: String,
-            create: () -> ClockController = ::failFactory,
-            getThumbnail: () -> Drawable? = ::failThumbnail
+            create: (ClockId) -> ClockController = ::failFactory,
+            getThumbnail: (ClockId) -> Drawable? = ::failThumbnail
         ): FakeClockPlugin {
             metadata.add(ClockMetadata(id, name))
             createCallbacks[id] = create
@@ -99,7 +104,8 @@
 
     @Before
     fun setUp() {
-        dispatcher = StandardTestDispatcher()
+        scheduler = TestCoroutineScheduler()
+        dispatcher = StandardTestDispatcher(scheduler)
         scope = TestScope(dispatcher)
 
         fakeDefaultProvider = FakeClockPlugin()
@@ -116,6 +122,8 @@
             isEnabled = true,
             handleAllUsers = true,
             defaultClockProvider = fakeDefaultProvider,
+            keepAllLoaded = true,
+            subTag = "Test",
         ) {
             override fun querySettings() { }
             override fun applySettings(value: ClockSettings?) {
@@ -142,8 +150,8 @@
             .addClock("clock_3", "clock 3")
             .addClock("clock_4", "clock 4")
 
-        pluginListener.onPluginConnected(plugin1, mockContext)
-        pluginListener.onPluginConnected(plugin2, mockContext)
+        pluginListener.onPluginLoaded(plugin1, mockContext, mockPluginLifecycle)
+        pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle)
         val list = registry.getClocks()
         assertEquals(
             list,
@@ -165,16 +173,18 @@
 
     @Test
     fun clockIdConflict_ErrorWithoutCrash() {
+        val mockPluginLifecycle1 = mock<PluginLifecycleManager<ClockProviderPlugin>>()
         val plugin1 = FakeClockPlugin()
             .addClock("clock_1", "clock 1", { mockClock }, { mockThumbnail })
             .addClock("clock_2", "clock 2", { mockClock }, { mockThumbnail })
 
+        val mockPluginLifecycle2 = mock<PluginLifecycleManager<ClockProviderPlugin>>()
         val plugin2 = FakeClockPlugin()
             .addClock("clock_1", "clock 1")
             .addClock("clock_2", "clock 2")
 
-        pluginListener.onPluginConnected(plugin1, mockContext)
-        pluginListener.onPluginConnected(plugin2, mockContext)
+        pluginListener.onPluginLoaded(plugin1, mockContext, mockPluginLifecycle1)
+        pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle2)
         val list = registry.getClocks()
         assertEquals(
             list,
@@ -202,8 +212,8 @@
             .addClock("clock_4", "clock 4")
 
         registry.applySettings(ClockSettings("clock_3", null))
-        pluginListener.onPluginConnected(plugin1, mockContext)
-        pluginListener.onPluginConnected(plugin2, mockContext)
+        pluginListener.onPluginLoaded(plugin1, mockContext, mockPluginLifecycle)
+        pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle)
 
         val clock = registry.createCurrentClock()
         assertEquals(mockClock, clock)
@@ -220,9 +230,9 @@
             .addClock("clock_4", "clock 4")
 
         registry.applySettings(ClockSettings("clock_3", null))
-        pluginListener.onPluginConnected(plugin1, mockContext)
-        pluginListener.onPluginConnected(plugin2, mockContext)
-        pluginListener.onPluginDisconnected(plugin2)
+        pluginListener.onPluginLoaded(plugin1, mockContext, mockPluginLifecycle)
+        pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle)
+        pluginListener.onPluginUnloaded(plugin2, mockPluginLifecycle)
 
         val clock = registry.createCurrentClock()
         assertEquals(clock, mockDefaultClock)
@@ -230,15 +240,16 @@
 
     @Test
     fun pluginRemoved_clockAndListChanged() {
+        val mockPluginLifecycle1 = mock<PluginLifecycleManager<ClockProviderPlugin>>()
         val plugin1 = FakeClockPlugin()
             .addClock("clock_1", "clock 1")
             .addClock("clock_2", "clock 2")
 
+        val mockPluginLifecycle2 = mock<PluginLifecycleManager<ClockProviderPlugin>>()
         val plugin2 = FakeClockPlugin()
             .addClock("clock_3", "clock 3", { mockClock })
             .addClock("clock_4", "clock 4")
 
-
         var changeCallCount = 0
         var listChangeCallCount = 0
         registry.registerClockChangeListener(object : ClockRegistry.ClockChangeListener {
@@ -247,23 +258,38 @@
         })
 
         registry.applySettings(ClockSettings("clock_3", null))
-        assertEquals(0, changeCallCount)
+        scheduler.runCurrent()
+        assertEquals(1, changeCallCount)
         assertEquals(0, listChangeCallCount)
 
-        pluginListener.onPluginConnected(plugin1, mockContext)
-        assertEquals(0, changeCallCount)
+        pluginListener.onPluginLoaded(plugin1, mockContext, mockPluginLifecycle1)
+        scheduler.runCurrent()
+        assertEquals(1, changeCallCount)
         assertEquals(1, listChangeCallCount)
 
-        pluginListener.onPluginConnected(plugin2, mockContext)
-        assertEquals(1, changeCallCount)
+        pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle2)
+        scheduler.runCurrent()
+        assertEquals(2, changeCallCount)
         assertEquals(2, listChangeCallCount)
 
-        pluginListener.onPluginDisconnected(plugin1)
-        assertEquals(1, changeCallCount)
+        pluginListener.onPluginUnloaded(plugin1, mockPluginLifecycle1)
+        scheduler.runCurrent()
+        assertEquals(2, changeCallCount)
+        assertEquals(2, listChangeCallCount)
+
+        pluginListener.onPluginUnloaded(plugin2, mockPluginLifecycle2)
+        scheduler.runCurrent()
+        assertEquals(3, changeCallCount)
+        assertEquals(2, listChangeCallCount)
+
+        pluginListener.onPluginDetached(mockPluginLifecycle1)
+        scheduler.runCurrent()
+        assertEquals(3, changeCallCount)
         assertEquals(3, listChangeCallCount)
 
-        pluginListener.onPluginDisconnected(plugin2)
-        assertEquals(2, changeCallCount)
+        pluginListener.onPluginDetached(mockPluginLifecycle2)
+        scheduler.runCurrent()
+        assertEquals(3, changeCallCount)
         assertEquals(4, listChangeCallCount)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java
index 05280fa..c39b29f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java
@@ -79,11 +79,11 @@
     private PluginInstance<TestPlugin> mPluginInstance;
     private PluginInstance.Factory mPluginInstanceFactory = new PluginInstance.Factory(
             this.getClass().getClassLoader(),
-            new PluginInstance.InstanceFactory<>(), new PluginInstance.VersionChecker(),
+            new PluginInstance.InstanceFactory<>(), new PluginInstance.VersionCheckerImpl(),
             Collections.emptyList(), false) {
         @Override
         public <T extends Plugin> PluginInstance<T> create(Context context, ApplicationInfo appInfo,
-                ComponentName componentName, Class<T> pluginClass) {
+                ComponentName componentName, Class<T> pluginClass, PluginListener<T> listener) {
             return (PluginInstance<T>) mPluginInstance;
         }
     };
@@ -128,7 +128,7 @@
         createPlugin();
 
         // Verify startup lifecycle
-        verify(mPluginInstance).onCreate(mContext, mMockListener);
+        verify(mPluginInstance).onCreate();
     }
 
     @Test
@@ -140,7 +140,7 @@
         mFakeExecutor.runAllReady();
 
         // Verify shutdown lifecycle
-        verify(mPluginInstance).onDestroy(mMockListener);
+        verify(mPluginInstance).onDestroy();
     }
 
     @Test
@@ -152,9 +152,9 @@
         mFakeExecutor.runAllReady();
 
         // Verify the old one was destroyed.
-        verify(mPluginInstance).onDestroy(mMockListener);
+        verify(mPluginInstance).onDestroy();
         verify(mPluginInstance, Mockito.times(2))
-                .onCreate(mContext, mMockListener);
+                .onCreate();
     }
 
     @Test
@@ -188,7 +188,7 @@
         mFakeExecutor.runAllReady();
 
         // Verify startup lifecycle
-        verify(mPluginInstance).onCreate(mContext, mMockListener);
+        verify(mPluginInstance).onCreate();
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
index bb9a1e9..d5e904c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
@@ -16,11 +16,9 @@
 
 package com.android.systemui.shared.plugins;
 
+import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNotNull;
-
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+import static junit.framework.Assert.assertNull;
 
 import android.content.ComponentName;
 import android.content.Context;
@@ -31,6 +29,7 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.Plugin;
+import com.android.systemui.plugins.PluginLifecycleManager;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.annotations.ProvidesInterface;
 import com.android.systemui.plugins.annotations.Requires;
@@ -38,46 +37,64 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
 
+import java.lang.ref.WeakReference;
 import java.util.Collections;
+import java.util.concurrent.atomic.AtomicInteger;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class PluginInstanceTest extends SysuiTestCase {
 
     private static final String PRIVILEGED_PACKAGE = "com.android.systemui.plugins";
-
-    @Mock
-    private TestPluginImpl mMockPlugin;
-    @Mock
-    private PluginListener<TestPlugin> mMockListener;
-    @Mock
-    private VersionInfo mVersionInfo;
-    ComponentName mTestPluginComponentName =
+    private static final ComponentName TEST_PLUGIN_COMPONENT_NAME =
             new ComponentName(PRIVILEGED_PACKAGE, TestPluginImpl.class.getName());
+
+    private FakeListener mPluginListener;
+    private VersionInfo mVersionInfo;
+    private VersionInfo.InvalidVersionException mVersionException;
+    private PluginInstance.VersionChecker mVersionChecker;
+
+    private RefCounter mCounter;
     private PluginInstance<TestPlugin> mPluginInstance;
     private PluginInstance.Factory mPluginInstanceFactory;
-
     private ApplicationInfo mAppInfo;
-    private Context mPluginContext;
-    @Mock
-    private PluginInstance.VersionChecker mVersionChecker;
+
+    // Because we're testing memory in this file, we must be careful not to assert the target
+    // objects, or capture them via mockito if we expect the garbage collector to later free them.
+    // Both JUnit and Mockito will save references and prevent these objects from being cleaned up.
+    private WeakReference<TestPluginImpl> mPlugin;
+    private WeakReference<Context> mPluginContext;
 
     @Before
     public void setup() throws Exception {
-        MockitoAnnotations.initMocks(this);
+        mCounter = new RefCounter();
         mAppInfo = mContext.getApplicationInfo();
-        mAppInfo.packageName = mTestPluginComponentName.getPackageName();
-        when(mVersionChecker.checkVersion(any(), any(), any())).thenReturn(mVersionInfo);
+        mAppInfo.packageName = TEST_PLUGIN_COMPONENT_NAME.getPackageName();
+        mPluginListener = new FakeListener();
+        mVersionInfo = new VersionInfo();
+        mVersionChecker = new PluginInstance.VersionChecker() {
+            @Override
+            public <T extends Plugin> VersionInfo checkVersion(
+                    Class<T> instanceClass,
+                    Class<T> pluginClass,
+                    Plugin plugin
+            ) {
+                if (mVersionException != null) {
+                    throw mVersionException;
+                }
+                return mVersionInfo;
+            }
+        };
 
         mPluginInstanceFactory = new PluginInstance.Factory(
                 this.getClass().getClassLoader(),
                 new PluginInstance.InstanceFactory<TestPlugin>() {
                     @Override
                     TestPlugin create(Class cls) {
-                        return mMockPlugin;
+                        TestPluginImpl plugin = new TestPluginImpl(mCounter);
+                        mPlugin = new WeakReference<>(plugin);
+                        return plugin;
                     }
                 },
                 mVersionChecker,
@@ -85,8 +102,9 @@
                 false);
 
         mPluginInstance = mPluginInstanceFactory.create(
-                mContext, mAppInfo, mTestPluginComponentName, TestPlugin.class);
-        mPluginContext = mPluginInstance.getPluginContext();
+                mContext, mAppInfo, TEST_PLUGIN_COMPONENT_NAME,
+                TestPlugin.class, mPluginListener);
+        mPluginContext = new WeakReference<>(mPluginInstance.getPluginContext());
     }
 
     @Test
@@ -96,29 +114,51 @@
 
     @Test(expected = VersionInfo.InvalidVersionException.class)
     public void testIncorrectVersion() throws Exception {
-
         ComponentName wrongVersionTestPluginComponentName =
                 new ComponentName(PRIVILEGED_PACKAGE, TestPlugin.class.getName());
 
-        when(mVersionChecker.checkVersion(any(), any(), any())).thenThrow(
-                new VersionInfo.InvalidVersionException("test", true));
+        mVersionException = new VersionInfo.InvalidVersionException("test", true);
 
         mPluginInstanceFactory.create(
-                mContext, mAppInfo, wrongVersionTestPluginComponentName, TestPlugin.class);
+                mContext, mAppInfo, wrongVersionTestPluginComponentName,
+                TestPlugin.class, mPluginListener);
     }
 
     @Test
     public void testOnCreate() {
-        mPluginInstance.onCreate(mContext, mMockListener);
-        verify(mMockPlugin).onCreate(mContext, mPluginContext);
-        verify(mMockListener).onPluginConnected(mMockPlugin, mPluginContext);
+        mPluginInstance.onCreate();
+        assertEquals(1, mPluginListener.mAttachedCount);
+        assertEquals(1, mPluginListener.mLoadCount);
+        assertEquals(mPlugin.get(), mPluginInstance.getPlugin());
+        assertInstances(1, 1);
     }
 
     @Test
     public void testOnDestroy() {
-        mPluginInstance.onDestroy(mMockListener);
-        verify(mMockListener).onPluginDisconnected(mMockPlugin);
-        verify(mMockPlugin).onDestroy();
+        mPluginInstance.onDestroy();
+        assertEquals(1, mPluginListener.mDetachedCount);
+        assertEquals(1, mPluginListener.mUnloadCount);
+        assertNull(mPluginInstance.getPlugin());
+        assertInstances(0, -1); // Destroyed but never created
+    }
+
+    @Test
+    public void testOnRepeatedlyLoadUnload_PluginFreed() {
+        mPluginInstance.onCreate();
+        mPluginInstance.loadPlugin();
+        assertInstances(1, 1);
+
+        mPluginInstance.unloadPlugin();
+        assertNull(mPluginInstance.getPlugin());
+        assertInstances(0, 0);
+
+        mPluginInstance.loadPlugin();
+        assertInstances(1, 1);
+
+        mPluginInstance.unloadPlugin();
+        mPluginInstance.onDestroy();
+        assertNull(mPluginInstance.getPlugin());
+        assertInstances(0, 0);
     }
 
     // This target class doesn't matter, it just needs to have a Requires to hit the flow where
@@ -129,10 +169,103 @@
         String ACTION = "testAction";
     }
 
+    public void assertInstances(Integer allocated, Integer created) {
+        // Run the garbage collector to finalize and deallocate outstanding
+        // instances. Since the GC doesn't always appear to want to run
+        // completely when we ask, we ask it 10 times in a short loop.
+        for (int i = 0; i < 10; i++) {
+            System.runFinalization();
+            System.gc();
+        }
+
+        mCounter.assertInstances(allocated, created);
+    }
+
+    public static class RefCounter {
+        public final AtomicInteger mAllocatedInstances = new AtomicInteger();
+        public final AtomicInteger mCreatedInstances = new AtomicInteger();
+
+        public void assertInstances(Integer allocated, Integer created) {
+            if (allocated != null) {
+                assertEquals(allocated.intValue(), mAllocatedInstances.get());
+            }
+            if (created != null) {
+                assertEquals(created.intValue(), mCreatedInstances.get());
+            }
+        }
+    }
+
     @Requires(target = TestPlugin.class, version = TestPlugin.VERSION)
     public static class TestPluginImpl implements TestPlugin {
+        public final RefCounter mCounter;
+        public TestPluginImpl(RefCounter counter) {
+            mCounter = counter;
+            mCounter.mAllocatedInstances.getAndIncrement();
+        }
+
+        @Override
+        public void finalize() {
+            mCounter.mAllocatedInstances.getAndDecrement();
+        }
+
         @Override
         public void onCreate(Context sysuiContext, Context pluginContext) {
+            mCounter.mCreatedInstances.getAndIncrement();
+        }
+
+        @Override
+        public void onDestroy() {
+            mCounter.mCreatedInstances.getAndDecrement();
+        }
+    }
+
+    public class FakeListener implements PluginListener<TestPlugin> {
+        public int mAttachedCount = 0;
+        public int mDetachedCount = 0;
+        public int mLoadCount = 0;
+        public int mUnloadCount = 0;
+
+        @Override
+        public void onPluginAttached(PluginLifecycleManager<TestPlugin> manager) {
+            mAttachedCount++;
+            assertEquals(PluginInstanceTest.this.mPluginInstance, manager);
+        }
+
+        @Override
+        public void onPluginDetached(PluginLifecycleManager<TestPlugin> manager) {
+            mDetachedCount++;
+            assertEquals(PluginInstanceTest.this.mPluginInstance, manager);
+        }
+
+        @Override
+        public void onPluginLoaded(
+                TestPlugin plugin,
+                Context pluginContext,
+                PluginLifecycleManager<TestPlugin> manager
+        ) {
+            mLoadCount++;
+            TestPlugin expectedPlugin = PluginInstanceTest.this.mPlugin.get();
+            if (expectedPlugin != null) {
+                assertEquals(expectedPlugin, plugin);
+            }
+            Context expectedContext = PluginInstanceTest.this.mPluginContext.get();
+            if (expectedContext != null) {
+                assertEquals(expectedContext, pluginContext);
+            }
+            assertEquals(PluginInstanceTest.this.mPluginInstance, manager);
+        }
+
+        @Override
+        public void onPluginUnloaded(
+                TestPlugin plugin,
+                PluginLifecycleManager<TestPlugin> manager
+        ) {
+            mUnloadCount++;
+            TestPlugin expectedPlugin = PluginInstanceTest.this.mPlugin.get();
+            if (expectedPlugin != null) {
+                assertEquals(expectedPlugin, plugin);
+            }
+            assertEquals(PluginInstanceTest.this.mPluginInstance, manager);
         }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/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/notification/row/ExpandableNotificationRowControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
index c92134b..60bc3a4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
@@ -21,6 +21,7 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
+import com.android.internal.statusbar.IStatusBarService
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.flags.FeatureFlags
@@ -93,6 +94,7 @@
     private val bubblesManager: BubblesManager = mock()
     private val dragController: ExpandableNotificationRowDragController = mock()
     private val dismissibilityProvider: NotificationDismissibilityProvider = mock()
+    private val statusBarService: IStatusBarService = mock()
 
     private lateinit var controller: ExpandableNotificationRowController
 
@@ -130,7 +132,8 @@
                 peopleNotificationIdentifier,
                 Optional.of(bubblesManager),
                 dragController,
-                dismissibilityProvider
+                dismissibilityProvider,
+                statusBarService
             )
         whenever(view.childrenContainer).thenReturn(childrenContainer)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
index 7b2051d..0b90ebe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
@@ -74,7 +74,7 @@
         doReturn(10).whenever(spyRow).intrinsicHeight
 
         with(view) {
-            initialize(mPeopleNotificationIdentifier, mock(), mock(), mock())
+            initialize(mPeopleNotificationIdentifier, mock(), mock(), mock(), mock())
             setContainingNotification(spyRow)
             setHeights(/* smallHeight= */ 10, /* headsUpMaxHeight= */ 20, /* maxHeight= */ 30)
             contractedChild = createViewWithHeight(10)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index f8a8e50..813bae8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -49,6 +49,7 @@
 import android.widget.RemoteViews;
 
 import com.android.internal.logging.MetricsLogger;
+import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.TestableDependency;
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.classifier.FalsingManagerFake;
@@ -582,7 +583,8 @@
                 mock(MetricsLogger.class),
                 mock(SmartReplyConstants.class),
                 mock(SmartReplyController.class),
-                mFeatureFlags);
+                mFeatureFlags,
+                mock(IStatusBarService.class));
 
         row.setAboveShelfChangedListener(aboveShelf -> { });
         mBindStage.getStageParams(entry).requireContentViews(extraInflationFlags);
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..1aba1fc 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();
 
@@ -1343,6 +1342,18 @@
     }
 
     @Test
+    public void keyguard_notHidden_ifGoingAwayAndOccluded() {
+        setKeyguardShowingAndOccluded(true /* showing */, false /* occluded */);
+
+        when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(true);
+        when(mKeyguardStateController.isOccluded()).thenReturn(true);
+
+        mCentralSurfaces.updateIsKeyguard(false);
+
+        verify(mStatusBarStateController, never()).setState(eq(SHADE), anyBoolean());
+    }
+
+    @Test
     public void frpLockedDevice_shadeDisabled() {
         when(mDeviceProvisionedController.isFrpActive()).thenReturn(true);
         when(mDozeServiceHost.isPulsing()).thenReturn(true);
@@ -1400,7 +1411,6 @@
         TestableNotificationInterruptStateProviderImpl(
                 ContentResolver contentResolver,
                 PowerManager powerManager,
-                IDreamManager dreamManager,
                 AmbientDisplayConfiguration ambientDisplayConfiguration,
                 StatusBarStateController controller,
                 KeyguardStateController keyguardStateController,
@@ -1415,7 +1425,6 @@
             super(
                     contentResolver,
                     powerManager,
-                    dreamManager,
                     ambientDisplayConfiguration,
                     batteryController,
                     controller,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 7a1270f..a9ed175 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -25,6 +25,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -1163,8 +1164,8 @@
     @Test
     public void testScrimFocus() {
         mScrimController.transitionTo(ScrimState.AOD);
-        Assert.assertFalse("Should not be focusable on AOD", mScrimBehind.isFocusable());
-        Assert.assertFalse("Should not be focusable on AOD", mScrimInFront.isFocusable());
+        assertFalse("Should not be focusable on AOD", mScrimBehind.isFocusable());
+        assertFalse("Should not be focusable on AOD", mScrimInFront.isFocusable());
 
         mScrimController.transitionTo(ScrimState.KEYGUARD);
         Assert.assertTrue("Should be focusable on keyguard", mScrimBehind.isFocusable());
@@ -1224,7 +1225,7 @@
     public void testAnimatesTransitionToAod() {
         when(mDozeParameters.shouldControlScreenOff()).thenReturn(false);
         ScrimState.AOD.prepare(ScrimState.KEYGUARD);
-        Assert.assertFalse("No animation when ColorFade kicks in",
+        assertFalse("No animation when ColorFade kicks in",
                 ScrimState.AOD.getAnimateChange());
 
         reset(mDozeParameters);
@@ -1236,9 +1237,9 @@
 
     @Test
     public void testViewsDontHaveFocusHighlight() {
-        Assert.assertFalse("Scrim shouldn't have focus highlight",
+        assertFalse("Scrim shouldn't have focus highlight",
                 mScrimInFront.getDefaultFocusHighlightEnabled());
-        Assert.assertFalse("Scrim shouldn't have focus highlight",
+        assertFalse("Scrim shouldn't have focus highlight",
                 mScrimBehind.getDefaultFocusHighlightEnabled());
     }
 
@@ -1738,7 +1739,7 @@
     @Test
     public void aodStateSetsFrontScrimToNotBlend() {
         mScrimController.transitionTo(ScrimState.AOD);
-        Assert.assertFalse("Front scrim should not blend with main color",
+        assertFalse("Front scrim should not blend with main color",
                 mScrimInFront.shouldBlendWithMainColor());
     }
 
@@ -1773,6 +1774,14 @@
         verify(mStatusBarKeyguardViewManager).onKeyguardFadedAway();
     }
 
+    @Test
+    public void testDoNotAnimateChangeIfOccludeAnimationPlaying() {
+        mScrimController.setOccludeAnimationPlaying(true);
+        mScrimController.transitionTo(ScrimState.UNLOCKED);
+
+        assertFalse(ScrimState.UNLOCKED.mAnimateChange);
+    }
+
     private void assertAlphaAfterExpansion(ScrimView scrim, float expectedAlpha, float expansion) {
         mScrimController.setRawPanelExpansionFraction(expansion);
         finishAnimationsImmediately();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 3146262..d9546877 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -31,6 +31,7 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -58,6 +59,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.flags.FeatureFlags;
@@ -126,12 +128,14 @@
     @Mock private PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor;
     @Mock private PrimaryBouncerInteractor mPrimaryBouncerInteractor;
     @Mock private AlternateBouncerInteractor mAlternateBouncerInteractor;
+    @Mock private UdfpsOverlayInteractor mUdfpsOverlayInteractor;
     @Mock private BouncerView mBouncerView;
     @Mock private BouncerViewDelegate mBouncerViewDelegate;
     @Mock private OnBackAnimationCallback mBouncerViewDelegateBackCallback;
     @Mock private NotificationShadeWindowView mNotificationShadeWindowView;
     @Mock private WindowInsetsController mWindowInsetsController;
     @Mock private TaskbarDelegate mTaskbarDelegate;
+    @Mock private StatusBarKeyguardViewManager.KeyguardViewManagerCallback mCallback;
 
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback
@@ -188,7 +192,8 @@
                         mPrimaryBouncerCallbackInteractor,
                         mPrimaryBouncerInteractor,
                         mBouncerView,
-                        mAlternateBouncerInteractor) {
+                        mAlternateBouncerInteractor,
+                        mUdfpsOverlayInteractor) {
                     @Override
                     public ViewRootImpl getViewRootImpl() {
                         return mViewRootImpl;
@@ -675,7 +680,8 @@
                         mPrimaryBouncerCallbackInteractor,
                         mPrimaryBouncerInteractor,
                         mBouncerView,
-                        mAlternateBouncerInteractor) {
+                        mAlternateBouncerInteractor,
+                        mUdfpsOverlayInteractor) {
                     @Override
                     public ViewRootImpl getViewRootImpl() {
                         return mViewRootImpl;
@@ -713,7 +719,115 @@
     }
 
     @Test
-    public void testAlternateBouncerToShowPrimaryBouncer_updatesScrimControllerOnce() {
+    public void handleDispatchTouchEvent_alternateBouncerNotVisible() {
+        mStatusBarKeyguardViewManager.addCallback(mCallback);
+
+        // GIVEN the alternate bouncer is visible
+        when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(false);
+
+        // THEN handleDispatchTouchEvent doesn't use the touches
+        assertFalse(mStatusBarKeyguardViewManager.dispatchTouchEvent(
+                MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+        ));
+        assertFalse(mStatusBarKeyguardViewManager.dispatchTouchEvent(
+                MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0)
+        ));
+        assertFalse(mStatusBarKeyguardViewManager.dispatchTouchEvent(
+                MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
+        ));
+
+        // THEN the touch is not acted upon
+        verify(mCallback, never()).onTouch(any());
+    }
+
+    @Test
+    public void handleDispatchTouchEvent_shouldInterceptTouchAndHandleTouch() {
+        mStatusBarKeyguardViewManager.addCallback(mCallback);
+
+        // GIVEN the alternate bouncer is visible
+        when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
+
+        // GIVEN all touches are NOT the udfps overlay
+        when(mUdfpsOverlayInteractor.isTouchWithinUdfpsArea(any())).thenReturn(false);
+
+        // THEN handleDispatchTouchEvent eats/intercepts the touches so motion events aren't sent
+        // to its child views (handleDispatchTouchEvent returns true)
+        assertTrue(mStatusBarKeyguardViewManager.dispatchTouchEvent(
+                MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+        ));
+        assertTrue(mStatusBarKeyguardViewManager.dispatchTouchEvent(
+                MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0)
+        ));
+        assertTrue(mStatusBarKeyguardViewManager.dispatchTouchEvent(
+                MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
+        ));
+
+        // THEN the touch is acted upon once for each dispatchTOuchEvent call
+        verify(mCallback, times(3)).onTouch(any());
+    }
+
+    @Test
+    public void handleDispatchTouchEvent_shouldInterceptTouchButNotHandleTouch() {
+        mStatusBarKeyguardViewManager.addCallback(mCallback);
+
+        // GIVEN the alternate bouncer is visible
+        when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
+
+        // GIVEN all touches are within the udfps overlay
+        when(mUdfpsOverlayInteractor.isTouchWithinUdfpsArea(any())).thenReturn(true);
+
+        // THEN handleDispatchTouchEvent eats/intercepts the touches so motion events aren't sent
+        // to its child views (handleDispatchTouchEvent returns true)
+        assertTrue(mStatusBarKeyguardViewManager.dispatchTouchEvent(
+                MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+        ));
+        assertTrue(mStatusBarKeyguardViewManager.dispatchTouchEvent(
+                MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0)
+        ));
+        assertTrue(mStatusBarKeyguardViewManager.dispatchTouchEvent(
+                MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
+        ));
+
+        // THEN the touch is NOT acted upon at the moment
+        verify(mCallback, never()).onTouch(any());
+    }
+
+    @Test
+    public void shouldInterceptTouch_alternateBouncerNotVisible() {
+        // GIVEN the alternate bouncer is not visible
+        when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(false);
+
+        // THEN no motion events are intercepted
+        assertFalse(mStatusBarKeyguardViewManager.shouldInterceptTouchEvent(
+                MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+        ));
+        assertFalse(mStatusBarKeyguardViewManager.shouldInterceptTouchEvent(
+                MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0)
+        ));
+        assertFalse(mStatusBarKeyguardViewManager.shouldInterceptTouchEvent(
+                MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
+        ));
+    }
+
+    @Test
+    public void shouldInterceptTouch_alternateBouncerVisible() {
+        // GIVEN the alternate bouncer is visible
+        when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
+
+        // THEN all motion events are intercepted
+        assertTrue(mStatusBarKeyguardViewManager.shouldInterceptTouchEvent(
+                MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+        ));
+        assertTrue(mStatusBarKeyguardViewManager.shouldInterceptTouchEvent(
+                MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0)
+        ));
+        assertTrue(mStatusBarKeyguardViewManager.shouldInterceptTouchEvent(
+                MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
+        ));
+    }
+
+    @Test
+    public void alternateBouncerToShowPrimaryBouncer_updatesScrimControllerOnce() {
         // GIVEN the alternate bouncer has shown and calls to hide()  will result in successfully
         // hiding it
         when(mAlternateBouncerInteractor.hide()).thenReturn(true);
@@ -729,30 +843,67 @@
     }
 
     @Test
-    public void testAlternateBouncerOnTouch_actionDown_doesNotHandleTouch() {
+    public void alternateBouncerOnTouch_actionDownThenUp_noMinTimeShown_noHideAltBouncer() {
+        reset(mAlternateBouncerInteractor);
+
         // GIVEN the alternate bouncer has shown for a minimum amount of time
-        when(mAlternateBouncerInteractor.hasAlternateBouncerShownWithMinTime()).thenReturn(true);
+        when(mAlternateBouncerInteractor.hasAlternateBouncerShownWithMinTime()).thenReturn(false);
         when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
+        when(mUdfpsOverlayInteractor.isTouchWithinUdfpsArea(any())).thenReturn(false);
 
-        // WHEN ACTION_DOWN touch event comes
-        boolean touchHandled = mStatusBarKeyguardViewManager.onTouch(
+        // WHEN ACTION_DOWN and ACTION_UP touch event comes
+        boolean touchHandledDown = mStatusBarKeyguardViewManager.onTouch(
                 MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0));
+        when(mAlternateBouncerInteractor.getReceivedDownTouch()).thenReturn(true);
+        boolean touchHandledUp = mStatusBarKeyguardViewManager.onTouch(
+                MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0));
 
-        // THEN the touch is not handled
-        assertFalse(touchHandled);
+        // THEN the touches are handled (doesn't let touches through to underlying views)
+        assertTrue(touchHandledDown);
+        assertTrue(touchHandledUp);
+
+        // THEN alternate bouncer does NOT attempt to hide since min showing time wasn't met
+        verify(mAlternateBouncerInteractor, never()).hide();
     }
 
     @Test
-    public void testAlternateBouncerOnTouch_actionUp_handlesTouch() {
+    public void alternateBouncerOnTouch_actionDownThenUp_handlesTouch_hidesAltBouncer() {
+        reset(mAlternateBouncerInteractor);
+
         // GIVEN the alternate bouncer has shown for a minimum amount of time
         when(mAlternateBouncerInteractor.hasAlternateBouncerShownWithMinTime()).thenReturn(true);
         when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
+        when(mUdfpsOverlayInteractor.isTouchWithinUdfpsArea(any())).thenReturn(false);
 
-        // WHEN ACTION_UP touch event comes
-        boolean touchHandled = mStatusBarKeyguardViewManager.onTouch(
+        // WHEN ACTION_DOWN and ACTION_UP touch event comes
+        boolean touchHandledDown = mStatusBarKeyguardViewManager.onTouch(
+                MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0));
+        when(mAlternateBouncerInteractor.getReceivedDownTouch()).thenReturn(true);
+        boolean touchHandledUp = mStatusBarKeyguardViewManager.onTouch(
                 MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0));
 
-        // THEN the touch is handled
-        assertTrue(touchHandled);
+        // THEN the touches are handled
+        assertTrue(touchHandledDown);
+        assertTrue(touchHandledUp);
+
+        // THEN alternate bouncer attempts to hide
+        verify(mAlternateBouncerInteractor).hide();
+    }
+
+    @Test
+    public void alternateBouncerOnTouch_actionUp_doesNotHideAlternateBouncer() {
+        reset(mAlternateBouncerInteractor);
+
+        // GIVEN the alternate bouncer has shown for a minimum amount of time
+        when(mAlternateBouncerInteractor.hasAlternateBouncerShownWithMinTime()).thenReturn(true);
+        when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
+        when(mUdfpsOverlayInteractor.isTouchWithinUdfpsArea(any())).thenReturn(false);
+
+        // WHEN only ACTION_UP touch event comes
+        mStatusBarKeyguardViewManager.onTouch(
+                MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0));
+
+        // THEN the alternateBouncer doesn't hide
+        verify(mAlternateBouncerInteractor, never()).hide();
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
index 481d453..c8f28bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
@@ -55,6 +55,9 @@
 public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase {
 
     private static final String[] DEFAULT_SETTINGS = new String[]{"0:1", "2:0:1", "1:2"};
+    private static final int[] DEFAULT_FOLDED_STATES = new int[]{0};
+    private static final int[] DEFAULT_HALF_FOLDED_STATES = new int[]{2};
+    private static final int[] DEFAULT_UNFOLDED_STATES = new int[]{1};
 
     @Mock private DeviceStateManager mDeviceStateManager;
     @Mock private DeviceStateRotationLockSettingControllerLogger mLogger;
@@ -73,6 +76,9 @@
         MockitoAnnotations.initMocks(/* testClass= */ this);
         TestableResources resources = mContext.getOrCreateTestableResources();
         resources.addOverride(R.array.config_perDeviceStateRotationLockDefaults, DEFAULT_SETTINGS);
+        resources.addOverride(R.array.config_foldedDeviceStates, DEFAULT_FOLDED_STATES);
+        resources.addOverride(R.array.config_halfFoldedDeviceStates, DEFAULT_HALF_FOLDED_STATES);
+        resources.addOverride(R.array.config_openDeviceStates, DEFAULT_UNFOLDED_STATES);
 
         ArgumentCaptor<DeviceStateManager.DeviceStateCallback> deviceStateCallbackArgumentCaptor =
                 ArgumentCaptor.forClass(DeviceStateManager.DeviceStateCallback.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java
index b1950ea..692af6a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java
@@ -22,6 +22,9 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -63,6 +66,7 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.Collections;
+import java.util.List;
 
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -99,6 +103,8 @@
     ArgumentCaptor<PendingIntent> mIntentCaptor;
     @Captor
     ArgumentCaptor<QuickAccessWalletClient.OnWalletCardsRetrievedCallback> mCallbackCaptor;
+    @Captor
+    ArgumentCaptor<List<WalletCardViewInfo>> mPaymentCardDataCaptor;
     private WalletScreenController mController;
     private TestableLooper mTestableLooper;
 
@@ -107,7 +113,7 @@
         MockitoAnnotations.initMocks(this);
         mTestableLooper = TestableLooper.get(this);
         when(mUserTracker.getUserContext()).thenReturn(mContext);
-        mWalletView = new WalletView(mContext);
+        mWalletView = spy(new WalletView(mContext));
         mWalletView.getCardCarousel().setExpectedViewWidth(CARD_CAROUSEL_WIDTH);
         when(mWalletClient.getLogo()).thenReturn(mWalletLogo);
         when(mWalletClient.getShortcutLongLabel()).thenReturn(SHORTCUT_LONG_LABEL);
@@ -430,6 +436,41 @@
         assertEquals(GONE, mWalletView.getVisibility());
     }
 
+    @Test
+    public void onWalletCardsRetrieved_cardDataAllUnknown_showsAllCards() {
+        List<WalletCard> walletCardList = List.of(
+                createWalletCardWithType(mContext, WalletCard.CARD_TYPE_UNKNOWN),
+                createWalletCardWithType(mContext, WalletCard.CARD_TYPE_UNKNOWN),
+                createWalletCardWithType(mContext, WalletCard.CARD_TYPE_UNKNOWN));
+        GetWalletCardsResponse response = new GetWalletCardsResponse(walletCardList, 0);
+        mController.onWalletCardsRetrieved(response);
+        mTestableLooper.processAllMessages();
+
+        verify(mWalletView).showCardCarousel(mPaymentCardDataCaptor.capture(), anyInt(),
+                anyBoolean(),
+                anyBoolean());
+        List<WalletCardViewInfo> paymentCardData = mPaymentCardDataCaptor.getValue();
+        assertEquals(paymentCardData.size(), walletCardList.size());
+    }
+
+    @Test
+    public void onWalletCardsRetrieved_cardDataDifferentTypes_onlyShowsPayment() {
+        List<WalletCard> walletCardList = List.of(createWalletCardWithType(mContext,
+                        WalletCard.CARD_TYPE_UNKNOWN),
+                createWalletCardWithType(mContext, WalletCard.CARD_TYPE_PAYMENT),
+                createWalletCardWithType(mContext, WalletCard.CARD_TYPE_NON_PAYMENT)
+                );
+        GetWalletCardsResponse response = new GetWalletCardsResponse(walletCardList, 0);
+        mController.onWalletCardsRetrieved(response);
+        mTestableLooper.processAllMessages();
+
+        verify(mWalletView).showCardCarousel(mPaymentCardDataCaptor.capture(), anyInt(),
+                anyBoolean(),
+                anyBoolean());
+        List<WalletCardViewInfo> paymentCardData = mPaymentCardDataCaptor.getValue();
+        assertEquals(paymentCardData.size(), 1);
+    }
+
     private WalletCard createNonActiveWalletCard(Context context) {
         PendingIntent pendingIntent =
                 PendingIntent.getActivity(context, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE);
@@ -457,6 +498,15 @@
                 .build();
     }
 
+    private WalletCard createWalletCardWithType(Context context, int cardType) {
+        PendingIntent pendingIntent =
+                PendingIntent.getActivity(context, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE);
+        return new WalletCard.Builder(CARD_ID_1, cardType, createIcon(), "•••• 1234", pendingIntent)
+                .setCardIcon(createIcon())
+                .setCardLabel("Hold to reader")
+                .build();
+    }
+
     private WalletCard createCrazyWalletCard(Context context, boolean hasLabel) {
         PendingIntent pendingIntent =
                 PendingIntent.getActivity(context, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE);
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..ee4e00b 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),
@@ -1242,6 +1245,24 @@
         // Show the menu
         stackView.showManageMenu(true);
         assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */);
+        assertTrue(stackView.isManageMenuSettingsVisible());
+    }
+
+    @Test
+    public void testShowManageMenuChangesSysuiState_appBubble() {
+        mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0);
+        assertTrue(mBubbleController.hasBubbles());
+
+        // Expand the stack
+        BubbleStackView stackView = mBubbleController.getStackView();
+        mBubbleData.setExpanded(true);
+        assertStackExpanded();
+        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+
+        // Show the menu
+        stackView.showManageMenu(true);
+        assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */);
+        assertFalse(stackView.isManageMenuSettingsVisible());
     }
 
     @Test
@@ -1650,7 +1671,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 +1681,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 +1696,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 +1870,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/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index d422f9a..0edb8f2 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -2263,6 +2263,15 @@
             }
             if (userState.mEnabledServices.contains(componentName)
                     && !mUiAutomationManager.suppressingAccessibilityServicesLocked()) {
+                // Skip the enabling service disallowed by device admin policy.
+                if (!isAccessibilityTargetAllowed(componentName.getPackageName(),
+                        installedService.getResolveInfo().serviceInfo.applicationInfo.uid,
+                        userState.mUserId)) {
+                    Slog.d(LOG_TAG, "Skipping enabling service disallowed by device admin policy: "
+                            + componentName);
+                    disableAccessibilityServiceLocked(componentName, userState.mUserId);
+                    continue;
+                }
                 if (service == null) {
                     service = new AccessibilityServiceConnection(userState, mContext, componentName,
                             installedService, sIdCounter++, mMainHandler, mLock, mSecurityPolicy,
@@ -3875,32 +3884,29 @@
         }
     }
 
-    @Override
-    @RequiresPermission(anyOf = {
-            android.Manifest.permission.MANAGE_USERS,
-            android.Manifest.permission.QUERY_ADMIN_POLICY})
     public boolean isAccessibilityTargetAllowed(String packageName, int uid, int userId) {
-        final DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
-        final List<String> permittedServices = dpm.getPermittedAccessibilityServices(userId);
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            final DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+            final List<String> permittedServices = dpm.getPermittedAccessibilityServices(userId);
 
-        // permittedServices null means all accessibility services are allowed.
-        boolean allowed = permittedServices == null || permittedServices.contains(packageName);
-        if (allowed) {
-            final AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class);
-            final int mode = appOps.noteOpNoThrow(
-                    AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS,
-                    uid, packageName, /* attributionTag= */ null, /* message= */ null);
-            final boolean ecmEnabled = mContext.getResources().getBoolean(
-                    R.bool.config_enhancedConfirmationModeEnabled);
-            return !ecmEnabled || mode == AppOpsManager.MODE_ALLOWED;
+            // permittedServices null means all accessibility services are allowed.
+            boolean allowed = permittedServices == null || permittedServices.contains(packageName);
+            if (allowed) {
+                final AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class);
+                final int mode = appOps.noteOpNoThrow(
+                        AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS,
+                        uid, packageName, /* attributionTag= */ null, /* message= */ null);
+                final boolean ecmEnabled = mContext.getResources().getBoolean(
+                        R.bool.config_enhancedConfirmationModeEnabled);
+                return !ecmEnabled || mode == AppOpsManager.MODE_ALLOWED;
+            }
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(identity);
         }
-        return false;
     }
 
-    @Override
-    @RequiresPermission(anyOf = {
-            android.Manifest.permission.MANAGE_USERS,
-            android.Manifest.permission.QUERY_ADMIN_POLICY})
     public boolean sendRestrictedDialogIntent(String packageName, int uid, int userId) {
         // The accessibility service is allowed. Don't show the restricted dialog.
         if (isAccessibilityTargetAllowed(packageName, uid, userId)) {
diff --git a/services/core/java/android/app/usage/UsageStatsManagerInternal.java b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
index 70eeb7f..fc56511 100644
--- a/services/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -203,6 +203,16 @@
     public abstract void setActiveAdminApps(Set<String> adminApps, int userId);
 
     /**
+     * Called by DevicePolicyManagerService to inform about the protected packages for a user.
+     * User control will be disabled for protected packages.
+     *
+     * @param packageNames the set of protected packages for {@code userId}.
+     * @param userId the userId to which the protected packages belong.
+     */
+    public abstract void setAdminProtectedPackages(@Nullable Set<String> packageNames,
+            @UserIdInt int userId);
+
+    /**
      * Called by DevicePolicyManagerService during boot to inform that admin data is loaded and
      * pushed to UsageStatsService.
      */
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..55069b7 100644
--- a/services/core/java/com/android/server/DropBoxManagerService.java
+++ b/services/core/java/com/android/server/DropBoxManagerService.java
@@ -311,16 +311,12 @@
             extrasMerger.setMergeStrategy(DropBoxManager.EXTRA_DROPPED_COUNT,
                     BundleMerger.STRATEGY_NUMBER_INCREMENT_FIRST_AND_ADD);
 
-            final String tag = intent.getStringExtra(DropBoxManager.EXTRA_TAG);
-            final IntentFilter matchingFilter = new IntentFilter(
-                    DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED);
-            matchingFilter.addExtra(DropBoxManager.EXTRA_TAG, tag);
-
             return BroadcastOptions.makeBasic()
                     .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MERGED)
-                    .setDeliveryGroupMatchingFilter(matchingFilter)
+                    .setDeliveryGroupMatchingKey(DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED,
+                            intent.getStringExtra(DropBoxManager.EXTRA_TAG))
                     .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 9bde039..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.
@@ -6609,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--) {
@@ -6646,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));
                 }
             }
 
@@ -6743,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..b4e75e1 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -9351,7 +9351,9 @@
 
                 String logcatSetting = Settings.Global.ERROR_LOGCAT_PREFIX + dropboxTag;
                 String maxBytesSetting = Settings.Global.MAX_ERROR_BYTES_PREFIX + dropboxTag;
-                int lines = Settings.Global.getInt(mContext.getContentResolver(), logcatSetting, 0);
+                int lines = Build.IS_USER
+                        ? 0
+                        : Settings.Global.getInt(mContext.getContentResolver(), logcatSetting, 0);
                 int dropboxMaxSize = Settings.Global.getInt(
                         mContext.getContentResolver(), maxBytesSetting, DROPBOX_DEFAULT_MAX_SIZE);
                 int maxDataFileSize = dropboxMaxSize - sb.length()
@@ -14507,18 +14509,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/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 94d08bf..72e17d8 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -1066,16 +1066,26 @@
     }
 
     @NeverCompile
-    int runCompact(PrintWriter pw) {
+    int runCompact(PrintWriter pw) throws RemoteException {
         ProcessRecord app;
         String op = getNextArgRequired();
         boolean isFullCompact = op.equals("full");
         boolean isSomeCompact = op.equals("some");
         if (isFullCompact || isSomeCompact) {
             String processName = getNextArgRequired();
-            String uid = getNextArgRequired();
             synchronized (mInternal.mProcLock) {
-                app = mInternal.getProcessRecordLocked(processName, Integer.parseInt(uid));
+                // Default to current user
+                int userId = mInterface.getCurrentUserId();
+                String userOpt = getNextOption();
+                if (userOpt != null && "--user".equals(userOpt)) {
+                    int inputUserId = UserHandle.parseUserArg(getNextArgRequired());
+                    if (inputUserId != UserHandle.USER_CURRENT) {
+                        userId = inputUserId;
+                    }
+                }
+                final int uid =
+                        mInternal.getPackageManagerInternal().getPackageUid(processName, 0, userId);
+                app = mInternal.getProcessRecordLocked(processName, uid);
             }
             pw.println("Process record found pid: " + app.mPid);
             if (isFullCompact) {
@@ -1101,6 +1111,28 @@
                 mInternal.mOomAdjuster.mCachedAppOptimizer.compactAllSystem();
             }
             pw.println("Finished system compaction");
+        } else if (op.equals("native")) {
+            op = getNextArgRequired();
+            isFullCompact = op.equals("full");
+            isSomeCompact = op.equals("some");
+            int pid;
+            String pidStr = getNextArgRequired();
+            try {
+                pid = Integer.parseInt(pidStr);
+            } catch (Exception e) {
+                getErrPrintWriter().println("Error: failed to parse '" + pidStr + "' as a PID");
+                return -1;
+            }
+            if (isFullCompact) {
+                mInternal.mOomAdjuster.mCachedAppOptimizer.compactNative(
+                        CachedAppOptimizer.CompactProfile.FULL, pid);
+            } else if (isSomeCompact) {
+                mInternal.mOomAdjuster.mCachedAppOptimizer.compactNative(
+                        CachedAppOptimizer.CompactProfile.SOME, pid);
+            } else {
+                getErrPrintWriter().println("Error: unknown compaction type '" + op + "'");
+                return -1;
+            }
         } else {
             getErrPrintWriter().println("Error: unknown compact command '" + op + "'");
             return -1;
@@ -4018,11 +4050,17 @@
             pw.println("      --allow-background-activity-starts: The receiver may start activities");
             pw.println("          even if in the background.");
             pw.println("      --async: Send without waiting for the completion of the receiver.");
-            pw.println("  compact [some|full|system] <process_name> <Package UID>");
-            pw.println("      Force process compaction.");
+            pw.println("  compact [some|full] <process_name> [--user <USER_ID>]");
+            pw.println("      Perform a single process compaction.");
             pw.println("      some: execute file compaction.");
             pw.println("      full: execute anon + file compaction.");
             pw.println("      system: system compaction.");
+            pw.println("  compact system");
+            pw.println("      Perform a full system compaction.");
+            pw.println("  compact native [some|full] <pid>");
+            pw.println("      Perform a native compaction for process with <pid>.");
+            pw.println("      some: execute file compaction.");
+            pw.println("      full: execute anon + file compaction.");
             pw.println("  instrument [-r] [-e <NAME> <VALUE>] [-p <FILE>] [-w]");
             pw.println("          [--user <USER_ID> | current]");
             pw.println("          [--no-hidden-api-checks [--no-test-api-access]]");
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index d09ca5c..7c84b72 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -843,7 +843,10 @@
 
                     final long sessionStart = mBatteryUsageStatsStore
                             .getLastBatteryUsageStatsBeforeResetAtomPullTimestamp();
-                    final long sessionEnd = mStats.getStartClockTime();
+                    final long sessionEnd;
+                    synchronized (mStats) {
+                        sessionEnd = mStats.getStartClockTime();
+                    }
                     final BatteryUsageStatsQuery queryBeforeReset =
                             new BatteryUsageStatsQuery.Builder()
                                     .setMaxStatsAgeMs(0)
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/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index f4685f0..8675bfd 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -66,8 +66,6 @@
     // Flags stored in the DeviceConfig API.
     @VisibleForTesting static final String KEY_USE_COMPACTION = "use_compaction";
     @VisibleForTesting static final String KEY_USE_FREEZER = "use_freezer";
-    @VisibleForTesting static final String KEY_COMPACT_ACTION_1 = "compact_action_1";
-    @VisibleForTesting static final String KEY_COMPACT_ACTION_2 = "compact_action_2";
     @VisibleForTesting static final String KEY_COMPACT_THROTTLE_1 = "compact_throttle_1";
     @VisibleForTesting static final String KEY_COMPACT_THROTTLE_2 = "compact_throttle_2";
     @VisibleForTesting static final String KEY_COMPACT_THROTTLE_3 = "compact_throttle_3";
@@ -99,15 +97,6 @@
     private static final int RSS_ANON_INDEX = 2;
     private static final int RSS_SWAP_INDEX = 3;
 
-    // Phenotype sends int configurations and we map them to the strings we'll use on device,
-    // preventing a weird string value entering the kernel.
-    private static final int COMPACT_ACTION_NONE = 0;
-    private static final int COMPACT_ACTION_FILE = 1;
-    private static final int COMPACT_ACTION_ANON = 2;
-    private static final int COMPACT_ACTION_ALL = 3;
-
-    private static final String COMPACT_ACTION_STRING[] = {"", "file", "anon", "all"};
-
     // Keeps these flags in sync with services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
     private static final int COMPACT_ACTION_FILE_FLAG = 1;
     private static final int COMPACT_ACTION_ANON_FLAG = 2;
@@ -117,11 +106,11 @@
 
     private static final int FREEZE_BINDER_TIMEOUT_MS = 100;
 
+    @VisibleForTesting static final boolean ENABLE_FILE_COMPACT = false;
+
     // Defaults for phenotype flags.
     @VisibleForTesting static final Boolean DEFAULT_USE_COMPACTION = true;
     @VisibleForTesting static final Boolean DEFAULT_USE_FREEZER = true;
-    @VisibleForTesting static final int DEFAULT_COMPACT_ACTION_2 = COMPACT_ACTION_ALL;
-    @VisibleForTesting static final int DEFAULT_COMPACT_ACTION_1 = COMPACT_ACTION_FILE;
     @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_1 = 5_000;
     @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_2 = 10_000;
     @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_3 = 500;
@@ -156,22 +145,15 @@
     @VisibleForTesting
     interface ProcessDependencies {
         long[] getRss(int pid);
-        void performCompaction(CompactAction action, int pid) throws IOException;
+        void performCompaction(CompactProfile action, int pid) throws IOException;
     }
 
     // This indicates the compaction we want to perform
     public enum CompactProfile {
-        SOME, // File compaction
-        FULL // File+anon compaction
-    }
-
-    // Low level actions that can be performed for compaction
-    // currently determined by the compaction profile
-    public enum CompactAction {
         NONE, // No compaction
-        FILE, // File+anon compaction
-        ANON,
-        ALL
+        SOME, // File compaction
+        ANON, // Anon compaction
+        FULL // File+anon compaction
     }
 
     // This indicates the process OOM memory state that initiated the compaction request
@@ -187,6 +169,7 @@
     static final int COMPACT_SYSTEM_MSG = 2;
     static final int SET_FROZEN_PROCESS_MSG = 3;
     static final int REPORT_UNFREEZE_MSG = 4;
+    static final int COMPACT_NATIVE_MSG = 5;
 
     // When free swap falls below this percentage threshold any full (file + anon)
     // compactions will be downgraded to file only compactions to reduce pressure
@@ -240,9 +223,6 @@
                         for (String name : properties.getKeyset()) {
                             if (KEY_USE_COMPACTION.equals(name)) {
                                 updateUseCompaction();
-                            } else if (KEY_COMPACT_ACTION_1.equals(name)
-                                    || KEY_COMPACT_ACTION_2.equals(name)) {
-                                updateCompactionActions();
                             } else if (KEY_COMPACT_THROTTLE_1.equals(name)
                                     || KEY_COMPACT_THROTTLE_2.equals(name)
                                     || KEY_COMPACT_THROTTLE_3.equals(name)
@@ -314,12 +294,6 @@
 
     // Configured by phenotype. Updates from the server take effect immediately.
     @GuardedBy("mPhenotypeFlagLock")
-    @VisibleForTesting
-    volatile CompactAction mCompactActionSome = compactActionIntToAction(DEFAULT_COMPACT_ACTION_1);
-    @GuardedBy("mPhenotypeFlagLock")
-    @VisibleForTesting
-    volatile CompactAction mCompactActionFull = compactActionIntToAction(DEFAULT_COMPACT_ACTION_2);
-    @GuardedBy("mPhenotypeFlagLock")
     @VisibleForTesting volatile long mCompactThrottleSomeSome = DEFAULT_COMPACT_THROTTLE_1;
     @GuardedBy("mPhenotypeFlagLock")
     @VisibleForTesting volatile long mCompactThrottleSomeFull = DEFAULT_COMPACT_THROTTLE_2;
@@ -542,7 +516,6 @@
                 CACHED_APP_FREEZER_ENABLED_URI, false, mSettingsObserver);
         synchronized (mPhenotypeFlagLock) {
             updateUseCompaction();
-            updateCompactionActions();
             updateCompactionThrottles();
             updateCompactStatsdSampleRate();
             updateFreezerStatsdSampleRate();
@@ -587,8 +560,6 @@
         pw.println("CachedAppOptimizer settings");
         synchronized (mPhenotypeFlagLock) {
             pw.println("  " + KEY_USE_COMPACTION + "=" + mUseCompaction);
-            pw.println("  " + KEY_COMPACT_ACTION_1 + "=" + mCompactActionSome);
-            pw.println("  " + KEY_COMPACT_ACTION_2 + "=" + mCompactActionFull);
             pw.println("  " + KEY_COMPACT_THROTTLE_1 + "=" + mCompactThrottleSomeSome);
             pw.println("  " + KEY_COMPACT_THROTTLE_2 + "=" + mCompactThrottleSomeFull);
             pw.println("  " + KEY_COMPACT_THROTTLE_3 + "=" + mCompactThrottleFullSome);
@@ -761,19 +732,9 @@
         return false;
     }
 
-    private CompactAction resolveCompactActionForProfile(CompactProfile profile) {
-        CompactAction action;
-        switch (profile) {
-            case SOME:
-                action = CompactAction.FILE;
-                break;
-            case FULL:
-                action = CompactAction.ALL;
-                break;
-            default:
-                action = CompactAction.NONE;
-        }
-        return action;
+    void compactNative(CompactProfile compactProfile, int pid) {
+        mCompactionHandler.sendMessage(mCompactionHandler.obtainMessage(
+                COMPACT_NATIVE_MSG, pid, compactProfile.ordinal()));
     }
 
     private AggregatedProcessCompactionStats getPerProcessAggregatedCompactStat(
@@ -1051,18 +1012,6 @@
     }
 
     @GuardedBy("mPhenotypeFlagLock")
-    private void updateCompactionActions() {
-        int compactAction1 = DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                KEY_COMPACT_ACTION_1, DEFAULT_COMPACT_ACTION_1);
-
-        int compactAction2 = DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                KEY_COMPACT_ACTION_2, DEFAULT_COMPACT_ACTION_2);
-
-        mCompactActionSome = compactActionIntToAction(compactAction1);
-        mCompactActionFull = compactActionIntToAction(compactAction2);
-    }
-
-    @GuardedBy("mPhenotypeFlagLock")
     private void updateCompactionThrottles() {
         boolean useThrottleDefaults = false;
         // TODO: improve efficiency by calling DeviceConfig only once for all flags.
@@ -1235,14 +1184,6 @@
         return true;
     }
 
-    static CompactAction compactActionIntToAction(int action) {
-        if (action < 0 || action >= CompactAction.values().length) {
-            return CompactAction.NONE;
-        }
-
-        return CompactAction.values()[action];
-    }
-
     // This will ensure app will be out of the freezer for at least mFreezerDebounceTimeout.
     @GuardedBy("mAm")
     void unfreezeTemporarily(ProcessRecord app, @OomAdjuster.OomAdjReason int reason) {
@@ -1475,8 +1416,10 @@
 
         if (oldAdj <= ProcessList.PERCEPTIBLE_APP_ADJ
                 && (newAdj == ProcessList.PREVIOUS_APP_ADJ || newAdj == ProcessList.HOME_APP_ADJ)) {
-            // Perform a minor compaction when a perceptible app becomes the prev/home app
-            compactApp(app, CompactProfile.SOME, CompactSource.APP, false);
+            if (ENABLE_FILE_COMPACT) {
+                // Perform a minor compaction when a perceptible app becomes the prev/home app
+                compactApp(app, CompactProfile.SOME, CompactSource.APP, false);
+            }
         } else if (oldAdj < ProcessList.CACHED_APP_MIN_ADJ
                 && newAdj >= ProcessList.CACHED_APP_MIN_ADJ
                 && newAdj <= ProcessList.CACHED_APP_MAX_ADJ) {
@@ -1486,23 +1429,37 @@
     }
 
     /**
-     * Applies a compaction downgrade when swap is low.
+     * Computes the final compaction profile to be used which depends on compaction
+     * features enabled and swap usage.
      */
-    CompactProfile downgradeCompactionIfRequired(CompactProfile profile) {
-        // Downgrade compaction under swap memory pressure
+    CompactProfile resolveCompactionProfile(CompactProfile profile) {
         if (profile == CompactProfile.FULL) {
             double swapFreePercent = getFreeSwapPercent();
+            // Downgrade compaction under swap memory pressure
             if (swapFreePercent < COMPACT_DOWNGRADE_FREE_SWAP_THRESHOLD) {
                 profile = CompactProfile.SOME;
+
                 ++mTotalCompactionDowngrades;
                 if (DEBUG_COMPACTION) {
                     Slog.d(TAG_AM,
-                            "Downgraded compaction to file only due to low swap."
+                            "Downgraded compaction to "+ profile +" due to low swap."
                                     + " Swap Free% " + swapFreePercent);
                 }
             }
         }
 
+        if (!ENABLE_FILE_COMPACT) {
+            if (profile == CompactProfile.SOME) {
+                profile = CompactProfile.NONE;
+            } else if (profile == CompactProfile.FULL) {
+                profile = CompactProfile.ANON;
+            }
+            if (DEBUG_COMPACTION) {
+                Slog.d(TAG_AM,
+                        "Final compaction profile "+ profile +" due to file compact disabled");
+            }
+        }
+
         return profile;
     }
 
@@ -1733,7 +1690,6 @@
                     ProcessRecord proc;
                     final ProcessCachedOptimizerRecord opt;
                     int pid;
-                    CompactAction resolvedAction;
                     final String name;
                     CompactProfile lastCompactProfile;
                     long lastCompactTime;
@@ -1811,17 +1767,24 @@
                     }
 
                     CompactProfile resolvedProfile =
-                            downgradeCompactionIfRequired(requestedProfile);
-                    resolvedAction = resolveCompactActionForProfile(resolvedProfile);
+                            resolveCompactionProfile(requestedProfile);
+                    if (resolvedProfile == CompactProfile.NONE) {
+                        // No point on issuing compaction call as we don't want to compact.
+                        if (DEBUG_COMPACTION) {
+                            Slog.d(TAG_AM, "Resolved no compaction for "+ name +
+                                    " requested profile="+requestedProfile);
+                        }
+                        return;
+                    }
 
                     try {
                         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
-                                "Compact " + resolvedAction.name() + ": " + name
+                                "Compact " + resolvedProfile.name() + ": " + name
                                         + " lastOomAdjReason: " + oomAdjReason
                                         + " source: " + compactSource.name());
                         long zramUsedKbBefore = getUsedZramMemory();
                         long startCpuTime = threadCpuTimeNs();
-                        mProcessDependencies.performCompaction(resolvedAction, pid);
+                        mProcessDependencies.performCompaction(resolvedProfile, pid);
                         long endCpuTime = threadCpuTimeNs();
                         long[] rssAfter = mProcessDependencies.getRss(pid);
                         long end = SystemClock.uptimeMillis();
@@ -1877,7 +1840,7 @@
                                 return;
                         }
                         EventLog.writeEvent(EventLogTags.AM_COMPACT, pid, name,
-                                resolvedAction.name(), rssBefore[RSS_TOTAL_INDEX],
+                                resolvedProfile.name(), rssBefore[RSS_TOTAL_INDEX],
                                 rssBefore[RSS_FILE_INDEX], rssBefore[RSS_ANON_INDEX],
                                 rssBefore[RSS_SWAP_INDEX], deltaTotalRss, deltaFileRss,
                                 deltaAnonRss, deltaSwapRss, time, lastCompactProfile.name(),
@@ -1907,6 +1870,21 @@
                     Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                     break;
                 }
+                case COMPACT_NATIVE_MSG: {
+                    int pid = msg.arg1;
+                    CompactProfile compactProfile = CompactProfile.values()[msg.arg2];
+                    Slog.d(TAG_AM,
+                            "Performing native compaction for pid=" + pid
+                                    + " type=" + compactProfile.name());
+                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "compactSystem");
+                    try {
+                        mProcessDependencies.performCompaction(compactProfile, pid);
+                    } catch (Exception e) {
+                        Slog.d(TAG_AM, "Failed compacting native pid= " + pid);
+                    }
+                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+                    break;
+                }
             }
         }
     }
@@ -2170,14 +2148,14 @@
 
         // Compact process.
         @Override
-        public void performCompaction(CompactAction action, int pid) throws IOException {
+        public void performCompaction(CompactProfile profile, int pid) throws IOException {
             mPidCompacting = pid;
-            if (action == CompactAction.ALL) {
-                    compactProcess(pid, COMPACT_ACTION_FILE_FLAG | COMPACT_ACTION_ANON_FLAG);
-            } else if (action == CompactAction.FILE) {
-                    compactProcess(pid, COMPACT_ACTION_FILE_FLAG);
-            } else if (action == CompactAction.ANON) {
-                    compactProcess(pid, COMPACT_ACTION_ANON_FLAG);
+            if (profile == CompactProfile.FULL) {
+                compactProcess(pid, COMPACT_ACTION_FILE_FLAG | COMPACT_ACTION_ANON_FLAG);
+            } else if (profile == CompactProfile.SOME) {
+                compactProcess(pid, COMPACT_ACTION_FILE_FLAG);
+            } else if (profile == CompactProfile.ANON) {
+                compactProcess(pid, COMPACT_ACTION_ANON_FLAG);
             }
             mPidCompacting = -1;
         }
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/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index 58a47d7..83caf0f 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -459,7 +459,10 @@
                         // don't dump native PIDs for background ANRs unless
                         // it is the process of interest
                         String[] nativeProcs = null;
-                        if (isSilentAnr || onlyDumpSelf) {
+                        boolean isSystemApp = mApp.info.isSystemApp() || mApp.info.isSystemExt();
+                        // Do not collect system daemons dumps as this is not likely to be useful
+                        // for non-system apps.
+                        if (!isSystemApp || isSilentAnr || onlyDumpSelf) {
                             for (int i = 0; i < NATIVE_STACKS_OF_INTEREST.length; i++) {
                                 if (NATIVE_STACKS_OF_INTEREST[i].equals(mApp.processName)) {
                                     nativeProcs = new String[] { mApp.processName };
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/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index aae1d38..6758581 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -597,7 +597,13 @@
             if (wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED
                     && AudioSystem.DEVICE_OUT_ALL_USB_SET.contains(
                             wdcs.mAttributes.getInternalType())) {
-                mDeviceBroker.dispatchPreferredMixerAttributesChangedCausedByDeviceRemoved(info);
+                if (info != null) {
+                    mDeviceBroker.dispatchPreferredMixerAttributesChangedCausedByDeviceRemoved(
+                            info);
+                } else {
+                    Log.e(TAG, "Didn't find AudioDeviceInfo to notify preferred mixer "
+                            + "attributes change for type=" + wdcs.mAttributes.getType());
+                }
             }
             sendDeviceConnectionIntent(type, wdcs.mState,
                     wdcs.mAttributes.getAddress(), wdcs.mAttributes.getName());
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 490a33e..127a9d8b 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,11 @@
         mSpatializerHelper.reset(/* featureEnabled */ mHasSpatializerEffect);
         mSoundDoseHelper.reset();
 
+        // Restore rotation information.
+        if (mMonitorRotation) {
+            RotationHelper.forceUpdate();
+        }
+
         onIndicateSystemReady();
         // indicate the end of reconfiguration phase to audio HAL
         AudioSystem.setParameters("restarting=false");
@@ -7228,7 +7237,7 @@
         super.setDeviceVolumeBehavior_enforcePermission();
         // verify arguments
         Objects.requireNonNull(device);
-        AudioManager.enforceValidVolumeBehavior(deviceVolumeBehavior);
+        AudioManager.enforceSettableVolumeBehavior(deviceVolumeBehavior);
 
         sVolumeLogger.enqueue(new EventLogger.StringEvent("setDeviceVolumeBehavior: dev:"
                 + AudioSystem.getOutputDeviceName(device.getInternalType()) + " addr:"
@@ -8170,7 +8179,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 +8188,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();
         }
 
@@ -10463,16 +10473,16 @@
 
     @Override
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
-    public float getRs2Value() {
-        super.getRs2Value_enforcePermission();
-        return mSoundDoseHelper.getRs2Value();
+    public float getOutputRs2UpperBound() {
+        super.getOutputRs2UpperBound_enforcePermission();
+        return mSoundDoseHelper.getOutputRs2UpperBound();
     }
 
     @Override
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
-    public void setRs2Value(float rs2Value) {
-        super.setRs2Value_enforcePermission();
-        mSoundDoseHelper.setRs2Value(rs2Value);
+    public void setOutputRs2UpperBound(float rs2Value) {
+        super.setOutputRs2UpperBound_enforcePermission();
+        mSoundDoseHelper.setOutputRs2UpperBound(rs2Value);
     }
 
     @Override
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..d6a9d3a 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,7 +281,26 @@
                 Context.ALARM_SERVICE);
     }
 
-    float getRs2Value() {
+    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 getOutputRs2UpperBound() {
         if (!mEnableCsd) {
             return 0.f;
         }
@@ -317,14 +312,14 @@
         }
 
         try {
-            return soundDose.getOutputRs2();
+            return soundDose.getOutputRs2UpperBound();
         } catch (RemoteException e) {
             Log.e(TAG, "Exception while getting the RS2 exposure value", e);
             return 0.f;
         }
     }
 
-    void setRs2Value(float rs2Value) {
+    void setOutputRs2UpperBound(float rs2Value) {
         if (!mEnableCsd) {
             return;
         }
@@ -336,7 +331,7 @@
         }
 
         try {
-            soundDose.setOutputRs2(rs2Value);
+            soundDose.setOutputRs2UpperBound(rs2Value);
         } catch (RemoteException e) {
             Log.e(TAG, "Exception while setting the RS2 exposure value", e);
         }
@@ -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/biometrics/TEST_MAPPING b/services/core/java/com/android/server/biometrics/TEST_MAPPING
index 8b80674..daca00b 100644
--- a/services/core/java/com/android/server/biometrics/TEST_MAPPING
+++ b/services/core/java/com/android/server/biometrics/TEST_MAPPING
@@ -6,5 +6,24 @@
         {
             "name": "CtsBiometricsHostTestCases"
         }
-    ]
-}
\ No newline at end of file
+    ],
+    "ironwood-postsubmit": [
+     {
+      "name": "BiometricsE2eTests",
+      "options": [
+        {
+            "include-annotation": "android.platform.test.annotations.IwTest"
+        },
+        {
+            "exclude-annotation": "org.junit.Ignore"
+        },
+        {
+            "include-filter": "android.platform.test.scenario.biometrics"
+        },
+        {
+            "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+        }
+      ]
+    }
+   ]
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index dc00ffc..01ffc7e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -314,7 +314,8 @@
             final FingerprintSensorPropertiesInternal sensorProps =
                     provider.second.getSensorProperties(options.getSensorId());
             if (!isKeyguard && !Utils.isSettings(getContext(), opPackageName)
-                    && sensorProps != null && sensorProps.isAnyUdfpsType()) {
+                    && sensorProps != null && (sensorProps.isAnyUdfpsType()
+                    || sensorProps.isAnySidefpsType())) {
                 try {
                     return authenticateWithPrompt(operationId, sensorProps, callingUid,
                             callingUserId, receiver, opPackageName,
diff --git a/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java b/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java
index b66120d..6a01042 100644
--- a/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java
+++ b/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java
@@ -86,8 +86,7 @@
 
     @Override
     public ITuner openTuner(int moduleId, RadioManager.BandConfig bandConfig,
-            boolean withAudio, ITunerCallback callback, int targetSdkVersion)
-            throws RemoteException {
+            boolean withAudio, ITunerCallback callback) throws RemoteException {
         if (isDebugEnabled()) {
             Slogf.d(TAG, "Opening module %d", moduleId);
         }
@@ -95,7 +94,7 @@
         if (callback == null) {
             throw new IllegalArgumentException("Callback must not be null");
         }
-        return mHalAidl.openSession(moduleId, bandConfig, withAudio, callback, targetSdkVersion);
+        return mHalAidl.openSession(moduleId, bandConfig, withAudio, callback);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java b/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java
index 8a1ba19..408fba1 100644
--- a/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java
+++ b/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java
@@ -92,8 +92,7 @@
 
     @Override
     public ITuner openTuner(int moduleId, RadioManager.BandConfig bandConfig,
-            boolean withAudio, ITunerCallback callback, int targetSdkVersion)
-            throws RemoteException {
+            boolean withAudio, ITunerCallback callback) throws RemoteException {
         if (isDebugEnabled()) {
             Slog.d(TAG, "Opening module " + moduleId);
         }
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java b/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
index 772cd41..03acf72 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
@@ -199,8 +199,7 @@
      */
     @Nullable
     public ITuner openSession(int moduleId, @Nullable RadioManager.BandConfig legacyConfig,
-            boolean withAudio, ITunerCallback callback, int targetSdkVersion)
-            throws RemoteException {
+            boolean withAudio, ITunerCallback callback) throws RemoteException {
         if (DEBUG) {
             Slogf.d(TAG, "Open AIDL radio session");
         }
@@ -223,7 +222,7 @@
             }
         }
 
-        TunerSession tunerSession = radioModule.openSession(callback, targetSdkVersion);
+        TunerSession tunerSession = radioModule.openSession(callback);
         if (legacyConfig != null) {
             tunerSession.setConfiguration(legacyConfig);
         }
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java b/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java
index c0a238f..4f2bfd1 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java
@@ -17,6 +17,10 @@
 package com.android.server.broadcastradio.aidl;
 
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
 import android.hardware.broadcastradio.AmFmRegionConfig;
 import android.hardware.broadcastradio.Announcement;
 import android.hardware.broadcastradio.DabTableEntry;
@@ -57,16 +61,24 @@
  * {@link android.hardware.radio}
  */
 final class ConversionUtils {
-    // TODO(b/241118988): Add unit test for ConversionUtils class
     private static final String TAG = "BcRadioAidlSrv.convert";
 
+    /**
+     * With RADIO_U_VERSION_REQUIRED enabled, 44-bit DAB identifier
+     * {@link IdentifierType#DAB_SID_EXT} from broadcast radio HAL can be passed as
+     * {@link ProgramSelector#IDENTIFIER_TYPE_DAB_DMB_SID_EXT} to {@link RadioTuner}.
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public static final long RADIO_U_VERSION_REQUIRED = 261770108L;
+
     private ConversionUtils() {
         throw new UnsupportedOperationException("ConversionUtils class is noninstantiable");
     }
 
-    static boolean isAtLeastU(int targetSdkVersion) {
-        // TODO(b/261770108): Use version code for U.
-        return targetSdkVersion >= Build.VERSION_CODES.CUR_DEVELOPMENT;
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    static boolean isAtLeastU(int uid) {
+        return CompatChanges.isChangeEnabled(RADIO_U_VERSION_REQUIRED, uid);
     }
 
     static RuntimeException throwOnError(RuntimeException halException, String action) {
@@ -584,9 +596,8 @@
         return id.getType() == ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT;
     }
 
-    static boolean programSelectorMeetsSdkVersionRequirement(ProgramSelector sel,
-            int targetSdkVersion) {
-        if (isAtLeastU(targetSdkVersion)) {
+    static boolean programSelectorMeetsSdkVersionRequirement(ProgramSelector sel, int uid) {
+        if (isAtLeastU(uid)) {
             return true;
         }
         if (sel.getPrimaryId().getType() == ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT) {
@@ -601,12 +612,11 @@
         return true;
     }
 
-    static boolean programInfoMeetsSdkVersionRequirement(RadioManager.ProgramInfo info,
-            int targetSdkVersion) {
-        if (isAtLeastU(targetSdkVersion)) {
+    static boolean programInfoMeetsSdkVersionRequirement(RadioManager.ProgramInfo info, int uid) {
+        if (isAtLeastU(uid)) {
             return true;
         }
-        if (!programSelectorMeetsSdkVersionRequirement(info.getSelector(), targetSdkVersion)) {
+        if (!programSelectorMeetsSdkVersionRequirement(info.getSelector(), uid)) {
             return false;
         }
         if (isNewIdentifierInU(info.getLogicallyTunedTo())
@@ -622,16 +632,15 @@
         return true;
     }
 
-    static ProgramList.Chunk convertChunkToTargetSdkVersion(ProgramList.Chunk chunk,
-            int targetSdkVersion) {
-        if (isAtLeastU(targetSdkVersion)) {
+    static ProgramList.Chunk convertChunkToTargetSdkVersion(ProgramList.Chunk chunk, int uid) {
+        if (isAtLeastU(uid)) {
             return chunk;
         }
         Set<RadioManager.ProgramInfo> modified = new ArraySet<>();
         Iterator<RadioManager.ProgramInfo> modifiedIterator = chunk.getModified().iterator();
         while (modifiedIterator.hasNext()) {
             RadioManager.ProgramInfo info = modifiedIterator.next();
-            if (programInfoMeetsSdkVersionRequirement(info, targetSdkVersion)) {
+            if (programInfoMeetsSdkVersionRequirement(info, uid)) {
                 modified.add(info);
             }
         }
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
index f8c19ae..132fb8e 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
@@ -101,9 +101,9 @@
                         ConversionUtils.programSelectorFromHalProgramSelector(programSelector);
                 int tunerResult = ConversionUtils.halResultToTunerResult(result);
                 synchronized (mLock) {
-                    fanoutAidlCallbackLocked((cb, sdkVersion) -> {
+                    fanoutAidlCallbackLocked((cb, uid) -> {
                         if (csel != null && !ConversionUtils
-                                .programSelectorMeetsSdkVersionRequirement(csel, sdkVersion)) {
+                                .programSelectorMeetsSdkVersionRequirement(csel, uid)) {
                             Slogf.e(TAG, "onTuneFailed: cannot send program selector "
                                     + "requiring higher target SDK version");
                             return;
@@ -123,9 +123,9 @@
                         "Program info from AIDL HAL is invalid");
                 synchronized (mLock) {
                     mCurrentProgramInfo = currentProgramInfo;
-                    fanoutAidlCallbackLocked((cb, sdkVersion) -> {
+                    fanoutAidlCallbackLocked((cb, uid) -> {
                         if (!ConversionUtils.programInfoMeetsSdkVersionRequirement(
-                                currentProgramInfo, sdkVersion)) {
+                                currentProgramInfo, uid)) {
                             Slogf.e(TAG, "onCurrentProgramInfoChanged: cannot send "
                                     + "program info requiring higher target SDK version");
                             return;
@@ -156,7 +156,7 @@
             fireLater(() -> {
                 synchronized (mLock) {
                     mAntennaConnected = connected;
-                    fanoutAidlCallbackLocked((cb, sdkVersion) -> cb.onAntennaState(connected));
+                    fanoutAidlCallbackLocked((cb, uid) -> cb.onAntennaState(connected));
                 }
             });
         }
@@ -165,7 +165,7 @@
         public void onConfigFlagUpdated(int flag, boolean value) {
             fireLater(() -> {
                 synchronized (mLock) {
-                    fanoutAidlCallbackLocked((cb, sdkVersion) -> {
+                    fanoutAidlCallbackLocked((cb, uid) -> {
                         cb.onConfigFlagUpdated(flag, value);
                     });
                 }
@@ -178,7 +178,7 @@
                 synchronized (mLock) {
                     Map<String, String> cparam =
                             ConversionUtils.vendorInfoFromHalVendorKeyValues(parameters);
-                    fanoutAidlCallbackLocked((cb, sdkVersion) -> {
+                    fanoutAidlCallbackLocked((cb, uid) -> {
                         cb.onParametersUpdated(cparam);
                     });
                 }
@@ -244,14 +244,14 @@
         mService.setTunerCallback(mHalTunerCallback);
     }
 
-    TunerSession openSession(android.hardware.radio.ITunerCallback userCb, int targetSdkVersion)
+    TunerSession openSession(android.hardware.radio.ITunerCallback userCb)
             throws RemoteException {
         mLogger.logRadioEvent("Open TunerSession");
         TunerSession tunerSession;
         Boolean antennaConnected;
         RadioManager.ProgramInfo currentProgramInfo;
         synchronized (mLock) {
-            tunerSession = new TunerSession(this, mService, userCb, targetSdkVersion);
+            tunerSession = new TunerSession(this, mService, userCb);
             mAidlTunerSessions.add(tunerSession);
             antennaConnected = mAntennaConnected;
             currentProgramInfo = mCurrentProgramInfo;
@@ -404,7 +404,7 @@
     }
 
     interface AidlCallbackRunnable {
-        void run(android.hardware.radio.ITunerCallback callback, int targetSdkVersion)
+        void run(android.hardware.radio.ITunerCallback callback, int uid)
                 throws RemoteException;
     }
 
@@ -423,7 +423,7 @@
         for (int i = 0; i < mAidlTunerSessions.size(); i++) {
             try {
                 runnable.run(mAidlTunerSessions.valueAt(i).mCallback,
-                        mAidlTunerSessions.valueAt(i).getTargetSdkVersion());
+                        mAidlTunerSessions.valueAt(i).getUid());
             } catch (DeadObjectException ex) {
                 // The other side died without calling close(), so just purge it from our records.
                 Slogf.e(TAG, "Removing dead TunerSession");
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java b/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java
index fe8c238..0a3823f 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java
@@ -24,6 +24,7 @@
 import android.hardware.radio.ProgramList;
 import android.hardware.radio.ProgramSelector;
 import android.hardware.radio.RadioManager;
+import android.os.Binder;
 import android.os.RemoteException;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -46,7 +47,7 @@
     private final RadioLogger mLogger;
     private final RadioModule mModule;
     final android.hardware.radio.ITunerCallback mCallback;
-    private final int mTargetSdkVersion;
+    private final int mUid;
     private final IBroadcastRadio mService;
 
     @GuardedBy("mLock")
@@ -61,11 +62,11 @@
     private RadioManager.BandConfig mPlaceHolderConfig;
 
     TunerSession(RadioModule radioModule, IBroadcastRadio service,
-            android.hardware.radio.ITunerCallback callback, int targetSdkVersion) {
+            android.hardware.radio.ITunerCallback callback) {
         mModule = Objects.requireNonNull(radioModule, "radioModule cannot be null");
         mService = Objects.requireNonNull(service, "service cannot be null");
         mCallback = Objects.requireNonNull(callback, "callback cannot be null");
-        mTargetSdkVersion = targetSdkVersion;
+        mUid = Binder.getCallingUid();
         mLogger = new RadioLogger(TAG, TUNER_EVENT_LOGGER_QUEUE_SIZE);
     }
 
@@ -130,7 +131,7 @@
             mPlaceHolderConfig = Objects.requireNonNull(config, "config cannot be null");
         }
         Slogf.i(TAG, "Ignoring setConfiguration - not applicable for broadcastradio HAL AIDL");
-        mModule.fanoutAidlCallback((cb, sdkVersion) -> cb.onConfigurationChanged(config));
+        mModule.fanoutAidlCallback((cb, mUid) -> cb.onConfigurationChanged(config));
     }
 
     @Override
@@ -254,7 +255,7 @@
             Slogf.w(TAG, "Cannot start background scan on AIDL HAL client from non-current user");
             return false;
         }
-        mModule.fanoutAidlCallback((cb, sdkVersion) -> {
+        mModule.fanoutAidlCallback((cb, mUid) -> {
             cb.onBackgroundScanComplete();
         });
         return true;
@@ -284,8 +285,8 @@
         mModule.onTunerSessionProgramListFilterChanged(this);
     }
 
-    int getTargetSdkVersion() {
-        return mTargetSdkVersion;
+    int getUid() {
+        return mUid;
     }
 
     ProgramList.Filter getProgramListFilter() {
@@ -323,10 +324,9 @@
         }
         for (int i = 0; i < chunks.size(); i++) {
             try {
-                if (!ConversionUtils.isAtLeastU(getTargetSdkVersion())) {
+                if (!ConversionUtils.isAtLeastU(getUid())) {
                     ProgramList.Chunk downgradedChunk =
-                            ConversionUtils.convertChunkToTargetSdkVersion(chunks.get(i),
-                                    getTargetSdkVersion());
+                            ConversionUtils.convertChunkToTargetSdkVersion(chunks.get(i), getUid());
                     mCallback.onProgramListUpdated(downgradedChunk);
                 } else {
                     mCallback.onProgramListUpdated(chunks.get(i));
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/cpu/CpuAvailabilityInfo.java b/services/core/java/com/android/server/cpu/CpuAvailabilityInfo.java
index 06b45bf..97507be 100644
--- a/services/core/java/com/android/server/cpu/CpuAvailabilityInfo.java
+++ b/services/core/java/com/android/server/cpu/CpuAvailabilityInfo.java
@@ -21,6 +21,8 @@
 
 import com.android.internal.util.Preconditions;
 
+import java.util.Objects;
+
 /** CPU availability information. */
 public final class CpuAvailabilityInfo {
     /** Constant to indicate missing CPU availability percent. */
@@ -35,29 +37,64 @@
     @CpuAvailabilityMonitoringConfig.Cpuset
     public final int cpuset;
 
+    /** Uptime (in milliseconds) when the data in this object was captured. */
+    public final long dataTimestampUptimeMillis;
+
     /** The latest average CPU availability percent. */
     public final int latestAvgAvailabilityPercent;
 
-    /** The past N-second average CPU availability percent. */
-    public final int pastNSecAvgAvailabilityPercent;
+    /**
+     * The past N-millisecond average CPU availability percent.
+     *
+     * <p>When there is not enough data to calculate the past N-millisecond average, this field will
+     * contain the value {@link MISSING_CPU_AVAILABILITY_PERCENT}.
+     */
+    public final int pastNMillisAvgAvailabilityPercent;
 
-    /** The duration over which the {@link pastNSecAvgAvailabilityPercent} was calculated. */
-    public final int avgAvailabilityDurationSec;
+    /** The duration over which the {@link pastNMillisAvgAvailabilityPercent} was calculated. */
+    public final long pastNMillisDuration;
 
     @Override
     public String toString() {
-        return "CpuAvailabilityInfo{" + "cpuset=" + cpuset + ", latestAvgAvailabilityPercent="
-                + latestAvgAvailabilityPercent + ", pastNSecAvgAvailabilityPercent="
-                + pastNSecAvgAvailabilityPercent + ", avgAvailabilityDurationSec="
-                + avgAvailabilityDurationSec + '}';
+        return "CpuAvailabilityInfo{" + "cpuset = " + cpuset + ", dataTimestampUptimeMillis = "
+                + dataTimestampUptimeMillis + ", latestAvgAvailabilityPercent = "
+                + latestAvgAvailabilityPercent + ", pastNMillisAvgAvailabilityPercent = "
+                + pastNMillisAvgAvailabilityPercent + ", pastNMillisDuration = "
+                + pastNMillisDuration + '}';
     }
 
-    CpuAvailabilityInfo(int cpuset, int latestAvgAvailabilityPercent,
-            int pastNSecAvgAvailabilityPercent, int avgAvailabilityDurationSec) {
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!(obj instanceof CpuAvailabilityInfo)) {
+            return false;
+        }
+        CpuAvailabilityInfo info = (CpuAvailabilityInfo) obj;
+        return cpuset == info.cpuset && dataTimestampUptimeMillis == info.dataTimestampUptimeMillis
+                && latestAvgAvailabilityPercent == info.latestAvgAvailabilityPercent
+                && pastNMillisAvgAvailabilityPercent == info.pastNMillisAvgAvailabilityPercent
+                && pastNMillisDuration == info.pastNMillisDuration;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(cpuset, dataTimestampUptimeMillis, latestAvgAvailabilityPercent,
+                pastNMillisAvgAvailabilityPercent, pastNMillisDuration);
+    }
+
+    CpuAvailabilityInfo(int cpuset, long dataTimestampUptimeMillis,
+            int latestAvgAvailabilityPercent, int pastNMillisAvgAvailabilityPercent,
+            long pastNMillisDuration) {
         this.cpuset = Preconditions.checkArgumentInRange(cpuset, CPUSET_ALL, CPUSET_BACKGROUND,
                 "cpuset");
-        this.latestAvgAvailabilityPercent = latestAvgAvailabilityPercent;
-        this.pastNSecAvgAvailabilityPercent = pastNSecAvgAvailabilityPercent;
-        this.avgAvailabilityDurationSec = avgAvailabilityDurationSec;
+        this.dataTimestampUptimeMillis =
+                Preconditions.checkArgumentNonnegative(dataTimestampUptimeMillis);
+        this.latestAvgAvailabilityPercent = Preconditions.checkArgumentNonnegative(
+                latestAvgAvailabilityPercent);
+        this.pastNMillisAvgAvailabilityPercent = pastNMillisAvgAvailabilityPercent;
+        this.pastNMillisDuration = Preconditions.checkArgumentNonnegative(
+                pastNMillisDuration);
     }
 }
diff --git a/services/core/java/com/android/server/cpu/CpuAvailabilityMonitoringConfig.java b/services/core/java/com/android/server/cpu/CpuAvailabilityMonitoringConfig.java
index a3c4c9e..cbe02fc 100644
--- a/services/core/java/com/android/server/cpu/CpuAvailabilityMonitoringConfig.java
+++ b/services/core/java/com/android/server/cpu/CpuAvailabilityMonitoringConfig.java
@@ -90,8 +90,19 @@
 
     @Override
     public String toString() {
-        return "CpuAvailabilityMonitoringConfig{cpuset=" + cpuset + ", mThresholds=" + mThresholds
-                + ')';
+        return "CpuAvailabilityMonitoringConfig{cpuset=" + toCpusetString(cpuset) + ", mThresholds="
+                + mThresholds + ')';
+    }
+
+    /** Returns the string equivalent of the provided cpuset. */
+    public static String toCpusetString(int cpuset) {
+        switch (cpuset) {
+            case CPUSET_ALL:
+                return "CPUSET_ALL";
+            case CPUSET_BACKGROUND:
+                return "CPUSET_BACKGROUND";
+        }
+        return "Invalid cpuset: " + cpuset;
     }
 
     private CpuAvailabilityMonitoringConfig(Builder builder) {
diff --git a/services/core/java/com/android/server/cpu/CpuInfoReader.java b/services/core/java/com/android/server/cpu/CpuInfoReader.java
index ca97a98..ce68edbb 100644
--- a/services/core/java/com/android/server/cpu/CpuInfoReader.java
+++ b/services/core/java/com/android/server/cpu/CpuInfoReader.java
@@ -21,8 +21,10 @@
 
 import android.annotation.IntDef;
 import android.annotation.Nullable;
+import android.os.SystemClock;
 import android.system.Os;
 import android.system.OsConstants;
+import android.util.IndentingPrintWriter;
 import android.util.IntArray;
 import android.util.LongSparseLongArray;
 import android.util.SparseArray;
@@ -50,6 +52,9 @@
     private static final String POLICY_DIR_PREFIX = "policy";
     private static final String RELATED_CPUS_FILE = "related_cpus";
     private static final String AFFECTED_CPUS_FILE = "affected_cpus";
+    // TODO(b/263154344): Avoid reading from cpuinfo_cur_freq because non-root users don't have
+    //  read permission for this file. The file permissions are set by the Kernel. Instead, read
+    //  the current frequency only from scaling_cur_freq.
     private static final String CUR_CPUFREQ_FILE = "cpuinfo_cur_freq";
     private static final String MAX_CPUFREQ_FILE = "cpuinfo_max_freq";
     private static final String CUR_SCALING_FREQ_FILE = "scaling_cur_freq";
@@ -70,16 +75,18 @@
     private static final Pattern TIME_IN_STATE_PATTERN =
             Pattern.compile("(?<freqKHz>[0-9]+)\\s(?<time>[0-9]+)");
     private static final long MILLIS_PER_CLOCK_TICK = 1000L / Os.sysconf(OsConstants._SC_CLK_TCK);
+    private static final long MIN_READ_INTERVAL_MILLISECONDS = 500;
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"FLAG_CPUSET_CATEGORY_"}, flag = true, value = {
             FLAG_CPUSET_CATEGORY_TOP_APP,
             FLAG_CPUSET_CATEGORY_BACKGROUND
     })
-    private @interface CpusetCategory{}
+    /** package **/ @interface CpusetCategory{}
 
     // TODO(b/242722241): Protect updatable variables with a local lock.
     private final File mCpusetDir;
+    private final long mMinReadIntervalMillis;
     private final SparseIntArray mCpusetCategoriesByCpus = new SparseIntArray();
     private final SparseArray<File> mCpuFreqPolicyDirsById = new SparseArray<>();
     private final SparseArray<StaticPolicyInfo> mStaticPolicyInfoById = new SparseArray<>();
@@ -90,16 +97,20 @@
     private SparseArray<CpuUsageStats> mCumulativeCpuUsageStats = new SparseArray<>();
     private boolean mIsEnabled;
     private boolean mHasTimeInStateFile;
+    private long mLastReadUptimeMillis;
+    private SparseArray<CpuInfo> mLastReadCpuInfos;
 
     public CpuInfoReader() {
-        this(new File(CPUSET_DIR_PATH), new File(CPUFREQ_DIR_PATH), new File(PROC_STAT_FILE_PATH));
+        this(new File(CPUSET_DIR_PATH), new File(CPUFREQ_DIR_PATH), new File(PROC_STAT_FILE_PATH),
+                MIN_READ_INTERVAL_MILLISECONDS);
     }
 
     @VisibleForTesting
-    CpuInfoReader(File cpusetDir, File cpuFreqDir, File procStatFile) {
+    CpuInfoReader(File cpusetDir, File cpuFreqDir, File procStatFile, long minReadIntervalMillis) {
         mCpusetDir = cpusetDir;
         mCpuFreqDir = cpuFreqDir;
         mProcStatFile = procStatFile;
+        mMinReadIntervalMillis = minReadIntervalMillis;
     }
 
     /**
@@ -167,6 +178,16 @@
         if (!mIsEnabled) {
             return null;
         }
+        long uptimeMillis = SystemClock.uptimeMillis();
+        if (mLastReadUptimeMillis > 0
+                && uptimeMillis - mLastReadUptimeMillis < mMinReadIntervalMillis) {
+            Slogf.w(TAG, "Skipping reading from device and returning the last read CpuInfos. "
+                    + "Last read was %d ms ago, min read interval is %d ms",
+                    uptimeMillis - mLastReadUptimeMillis, mMinReadIntervalMillis);
+            return mLastReadCpuInfos;
+        }
+        mLastReadUptimeMillis = uptimeMillis;
+        mLastReadCpuInfos = null;
         SparseArray<CpuUsageStats> cpuUsageStatsByCpus = readLatestCpuUsageStats();
         if (cpuUsageStatsByCpus == null || cpuUsageStatsByCpus.size() == 0) {
             Slogf.e(TAG, "Failed to read latest CPU usage stats");
@@ -202,6 +223,12 @@
                         + " policy ID %d", policyId);
                 continue;
             }
+            if (curFreqKHz > maxFreqKHz) {
+                Slogf.w(TAG, "Current CPU frequency (%d) is greater than maximum CPU frequency"
+                        + " (%d) for policy ID (%d). Skipping CPU frequency policy", curFreqKHz,
+                        maxFreqKHz, policyId);
+                continue;
+            }
             for (int coreIdx = 0; coreIdx < staticPolicyInfo.relatedCpuCores.size(); coreIdx++) {
                 int relatedCpuCore = staticPolicyInfo.relatedCpuCores.get(coreIdx);
                 CpuInfo prevCpuInfo = cpuInfoByCpus.get(relatedCpuCore);
@@ -241,9 +268,73 @@
                 }
             }
         }
+        mLastReadCpuInfos = cpuInfoByCpus;
         return cpuInfoByCpus;
     }
 
+    /** Dumps the current state. */
+    public void dump(IndentingPrintWriter writer) {
+        writer.printf("*%s*\n", getClass().getSimpleName());
+        writer.increaseIndent();    // Add intend for the outermost block.
+
+        writer.printf("mCpusetDir = %s\n", mCpusetDir.getAbsolutePath());
+        writer.printf("mCpuFreqDir = %s\n", mCpuFreqDir.getAbsolutePath());
+        writer.printf("mProcStatFile = %s\n", mProcStatFile.getAbsolutePath());
+        writer.printf("mIsEnabled = %s\n", mIsEnabled);
+        writer.printf("mHasTimeInStateFile = %s\n", mHasTimeInStateFile);
+        writer.printf("mLastReadUptimeMillis = %d\n", mLastReadUptimeMillis);
+        writer.printf("mMinReadIntervalMillis = %d\n", mMinReadIntervalMillis);
+
+        writer.printf("Cpuset categories by CPU core:\n");
+        writer.increaseIndent();
+        for (int i = 0; i < mCpusetCategoriesByCpus.size(); i++) {
+            writer.printf("CPU core id = %d, %s\n", mCpusetCategoriesByCpus.keyAt(i),
+                    toCpusetCategoriesStr(mCpusetCategoriesByCpus.valueAt(i)));
+        }
+        writer.decreaseIndent();
+
+        writer.println("Cpu frequency policy directories by policy id:");
+        writer.increaseIndent();
+        for (int i = 0; i < mCpuFreqPolicyDirsById.size(); i++) {
+            writer.printf("Policy id = %d, Dir = %s\n", mCpuFreqPolicyDirsById.keyAt(i),
+                    mCpuFreqPolicyDirsById.valueAt(i));
+        }
+        writer.decreaseIndent();
+
+        writer.println("Static cpu frequency policy infos by policy id:");
+        writer.increaseIndent();
+        for (int i = 0; i < mStaticPolicyInfoById.size(); i++) {
+            writer.printf("Policy id = %d, %s\n", mStaticPolicyInfoById.keyAt(i),
+                    mStaticPolicyInfoById.valueAt(i));
+        }
+        writer.decreaseIndent();
+
+        writer.println("Cpu time in frequency state by policy id:");
+        writer.increaseIndent();
+        for (int i = 0; i < mTimeInStateByPolicyId.size(); i++) {
+            writer.printf("Policy id = %d, Time(millis) in state by CPU frequency(KHz) = %s\n",
+                    mTimeInStateByPolicyId.keyAt(i), mTimeInStateByPolicyId.valueAt(i));
+        }
+        writer.decreaseIndent();
+
+        writer.println("Last read CPU infos:");
+        writer.increaseIndent();
+        for (int i = 0; i < mLastReadCpuInfos.size(); i++) {
+            writer.printf("%s\n", mLastReadCpuInfos.valueAt(i));
+        }
+        writer.decreaseIndent();
+
+        writer.println("Latest cumulative CPU usage stats by CPU core:");
+        writer.increaseIndent();
+        for (int i = 0; i < mCumulativeCpuUsageStats.size(); i++) {
+            writer.printf("CPU core id = %d, %s\n", mCumulativeCpuUsageStats.keyAt(i),
+                    mCumulativeCpuUsageStats.valueAt(i));
+        }
+        writer.decreaseIndent();
+
+        writer.decreaseIndent();    // Remove intend for the outermost block.
+    }
+
     /**
      * Sets the CPU frequency for testing.
      *
@@ -496,6 +587,9 @@
         for (int i = 0; i < timeInState.size(); i++) {
             totalTimeInState += timeInState.valueAt(i);
         }
+        if (totalTimeInState == 0) {
+            return CpuInfo.MISSING_FREQUENCY;
+        }
         double avgFreqKHz = 0;
         for (int i = 0; i < timeInState.size(); i++) {
             avgFreqKHz += (timeInState.keyAt(i) * timeInState.valueAt(i)) / totalTimeInState;
@@ -624,16 +718,29 @@
         @CpusetCategory
         public final int cpusetCategories;
         public final boolean isOnline;
+        public final long maxCpuFreqKHz;
         // Values in the below fields may be missing when a CPU core is offline.
         public final long curCpuFreqKHz;
-        public final long maxCpuFreqKHz;
         public final long avgTimeInStateCpuFreqKHz;
         @Nullable
         public final CpuUsageStats latestCpuUsageStats;
 
+        private long mNormalizedAvailableCpuFreqKHz;
+
         CpuInfo(int cpuCore, @CpusetCategory int cpusetCategories, boolean isOnline,
                 long curCpuFreqKHz, long maxCpuFreqKHz, long avgTimeInStateCpuFreqKHz,
                 CpuUsageStats latestCpuUsageStats) {
+            this(cpuCore, cpusetCategories, isOnline, curCpuFreqKHz, maxCpuFreqKHz,
+                    avgTimeInStateCpuFreqKHz, /* normalizedAvailableCpuFreqKHz= */ 0,
+                    latestCpuUsageStats);
+            this.mNormalizedAvailableCpuFreqKHz = computeNormalizedAvailableCpuFreqKHz();
+        }
+
+        // Should be used only for testing.
+        @VisibleForTesting
+        CpuInfo(int cpuCore, @CpusetCategory int cpusetCategories, boolean isOnline,
+                long curCpuFreqKHz, long maxCpuFreqKHz, long avgTimeInStateCpuFreqKHz,
+                long normalizedAvailableCpuFreqKHz, CpuUsageStats latestCpuUsageStats) {
             this.cpuCore = cpuCore;
             this.cpusetCategories = cpusetCategories;
             this.isOnline = isOnline;
@@ -641,6 +748,11 @@
             this.maxCpuFreqKHz = maxCpuFreqKHz;
             this.avgTimeInStateCpuFreqKHz = avgTimeInStateCpuFreqKHz;
             this.latestCpuUsageStats = latestCpuUsageStats;
+            this.mNormalizedAvailableCpuFreqKHz = normalizedAvailableCpuFreqKHz;
+        }
+
+        public long getNormalizedAvailableCpuFreqKHz() {
+            return mNormalizedAvailableCpuFreqKHz;
         }
 
         @Override
@@ -657,6 +769,8 @@
                     .append(avgTimeInStateCpuFreqKHz == MISSING_FREQUENCY ? "missing"
                             : avgTimeInStateCpuFreqKHz)
                     .append(", latestCpuUsageStats = ").append(latestCpuUsageStats)
+                    .append(", mNormalizedAvailableCpuFreqKHz = ")
+                    .append(mNormalizedAvailableCpuFreqKHz)
                     .append(" }").toString();
         }
 
@@ -673,13 +787,32 @@
                     && isOnline == other.isOnline  && curCpuFreqKHz == other.curCpuFreqKHz
                     && maxCpuFreqKHz == other.maxCpuFreqKHz
                     && avgTimeInStateCpuFreqKHz == other.avgTimeInStateCpuFreqKHz
-                    && latestCpuUsageStats.equals(other.latestCpuUsageStats);
+                    && latestCpuUsageStats.equals(other.latestCpuUsageStats)
+                    && mNormalizedAvailableCpuFreqKHz == other.mNormalizedAvailableCpuFreqKHz;
         }
 
         @Override
         public int hashCode() {
             return Objects.hash(cpuCore, cpusetCategories, isOnline, curCpuFreqKHz, maxCpuFreqKHz,
-                    avgTimeInStateCpuFreqKHz, latestCpuUsageStats);
+                    avgTimeInStateCpuFreqKHz, latestCpuUsageStats, mNormalizedAvailableCpuFreqKHz);
+        }
+
+        private long computeNormalizedAvailableCpuFreqKHz() {
+            if (!isOnline) {
+                return MISSING_FREQUENCY;
+            }
+            long totalTimeMillis = latestCpuUsageStats.getTotalTimeMillis();
+            if (totalTimeMillis == 0) {
+                Slogf.wtf(TAG, "Total CPU time millis is 0. This shouldn't happen unless stats are"
+                        + " polled too frequently");
+                return MISSING_FREQUENCY;
+            }
+            double nonIdlePercent = 100.0 * (totalTimeMillis
+                    - (double) latestCpuUsageStats.idleTimeMillis) / totalTimeMillis;
+            long curFreqKHz = avgTimeInStateCpuFreqKHz == MISSING_FREQUENCY
+                    ? curCpuFreqKHz : avgTimeInStateCpuFreqKHz;
+            double availablePercent = 100.0 - (nonIdlePercent * curFreqKHz / maxCpuFreqKHz);
+            return (long) ((availablePercent * maxCpuFreqKHz) / 100.0);
         }
     }
 
@@ -712,7 +845,7 @@
             this.guestNiceTimeMillis = guestNiceTimeMillis;
         }
 
-        public long getTotalTime() {
+        public long getTotalTimeMillis() {
             return userTimeMillis + niceTimeMillis + systemTimeMillis + idleTimeMillis
                     + iowaitTimeMillis + irqTimeMillis + softirqTimeMillis + stealTimeMillis
                     + guestTimeMillis + guestNiceTimeMillis;
@@ -796,8 +929,8 @@
 
         @Override
         public String toString() {
-            return "FrequencyPair{cpuFreqKHz=" + cpuFreqKHz + ", scalingFreqKHz=" + scalingFreqKHz
-                    + '}';
+            return "FrequencyPair{cpuFreqKHz = " + cpuFreqKHz + ", scalingFreqKHz = "
+                    + scalingFreqKHz + '}';
         }
     }
 
@@ -812,7 +945,7 @@
 
         @Override
         public String toString() {
-            return "StaticPolicyInfo{maxCpuFreqPair=" + maxCpuFreqPair + ", relatedCpuCores="
+            return "StaticPolicyInfo{maxCpuFreqPair = " + maxCpuFreqPair + ", relatedCpuCores = "
                     + relatedCpuCores + '}';
         }
     }
@@ -831,9 +964,9 @@
 
         @Override
         public String toString() {
-            return "DynamicPolicyInfo{curCpuFreqPair=" + curCpuFreqPair
-                    + ", avgTimeInStateCpuFreqKHz=" + avgTimeInStateCpuFreqKHz
-                    + ", affectedCpuCores=" + affectedCpuCores + '}';
+            return "DynamicPolicyInfo{curCpuFreqPair = " + curCpuFreqPair
+                    + ", avgTimeInStateCpuFreqKHz = " + avgTimeInStateCpuFreqKHz
+                    + ", affectedCpuCores = " + affectedCpuCores + '}';
         }
     }
 }
diff --git a/services/core/java/com/android/server/cpu/CpuMonitorService.java b/services/core/java/com/android/server/cpu/CpuMonitorService.java
index 4eefe5c..df8cfad 100644
--- a/services/core/java/com/android/server/cpu/CpuMonitorService.java
+++ b/services/core/java/com/android/server/cpu/CpuMonitorService.java
@@ -18,15 +18,33 @@
 
 import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
 
+import static com.android.server.cpu.CpuAvailabilityMonitoringConfig.CPUSET_ALL;
+import static com.android.server.cpu.CpuAvailabilityMonitoringConfig.CPUSET_BACKGROUND;
+import static com.android.server.cpu.CpuInfoReader.FLAG_CPUSET_CATEGORY_BACKGROUND;
+import static com.android.server.cpu.CpuInfoReader.FLAG_CPUSET_CATEGORY_TOP_APP;
+
+import android.annotation.Nullable;
 import android.content.Context;
 import android.os.Binder;
-import android.util.ArrayMap;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.os.SystemClock;
 import android.util.IndentingPrintWriter;
+import android.util.IntArray;
 import android.util.Log;
+import android.util.LongSparseArray;
+import android.util.SparseArray;
+import android.util.SparseArrayMap;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.DumpUtils;
+import com.android.internal.util.Preconditions;
+import com.android.server.ServiceThread;
 import com.android.server.SystemService;
+import com.android.server.Watchdog;
 import com.android.server.utils.PriorityDump;
 import com.android.server.utils.Slogf;
 
@@ -34,28 +52,55 @@
 import java.io.PrintWriter;
 import java.util.Objects;
 import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
 
 /** Service to monitor CPU availability and usage. */
 public final class CpuMonitorService extends SystemService {
     static final String TAG = CpuMonitorService.class.getSimpleName();
     static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-    // TODO(b/242722241): Make this a resource overlay property.
-    //  Maintain 3 monitoring intervals:
-    //  * One to poll very frequently when mCpuAvailabilityCallbackInfoByCallbacks are available and
-    //    CPU availability is above a threshold (such as at least 10% of CPU is available).
-    //  * One to poll less frequently when mCpuAvailabilityCallbackInfoByCallbacks are available
-    //    and CPU availability is below a threshold (such as less than 10% of CPU is available).
-    //  * One to poll very less frequently when no callbacks are available and the build is either
-    //    user-debug or eng. This will be useful for debugging in development environment.
-    static final int DEFAULT_CPU_MONITORING_INTERVAL_MILLISECONDS = 5_000;
+    // TODO(b/267500110): Make these constants resource overlay properties.
+    /** Default monitoring interval when no monitoring is in progress. */
+    static final long DEFAULT_MONITORING_INTERVAL_MILLISECONDS = -1;
+    /** Monitoring interval when callbacks are registered and the CPU load is normal. */
+    private static final long NORMAL_MONITORING_INTERVAL_MILLISECONDS =
+            TimeUnit.SECONDS.toMillis(5);
+
+    /**
+     * Monitoring interval when no registered callbacks and the build is either user-debug or eng.
+     */
+    private static final long DEBUG_MONITORING_INTERVAL_MILLISECONDS = TimeUnit.MINUTES.toMillis(1);
+    /**
+     * Size of the in-memory cache relative to the current uptime.
+     *
+     * On user-debug or eng builds, continuously cache stats with a bigger cache size for debugging
+     * purposes.
+     */
+    private static final long CACHE_DURATION_MILLISECONDS = Build.IS_USERDEBUG || Build.IS_ENG
+            ? TimeUnit.MINUTES.toMillis(30) : TimeUnit.MINUTES.toMillis(10);
+    // TODO(b/267500110): Investigate whether this duration should change when the monitoring
+    //  interval is updated. When the CPU is under heavy load, the monitoring will happen less
+    //  frequently. Should this duration be increased as well when this happens?
+    private static final long LATEST_AVAILABILITY_DURATION_MILLISECONDS =
+            TimeUnit.SECONDS.toMillis(30);
 
     private final Context mContext;
+    private final HandlerThread mHandlerThread;
+    private final CpuInfoReader mCpuInfoReader;
+    private final boolean mShouldDebugMonitor;
+    private final long mNormalMonitoringIntervalMillis;
+    private final long mDebugMonitoringIntervalMillis;
+    private final long mLatestAvailabilityDurationMillis;
     private final Object mLock = new Object();
     @GuardedBy("mLock")
-    private final ArrayMap<CpuMonitorInternal.CpuAvailabilityCallback, CpuAvailabilityCallbackInfo>
-            mCpuAvailabilityCallbackInfoByCallbacks = new ArrayMap<>();
+    private final SparseArrayMap<CpuMonitorInternal.CpuAvailabilityCallback,
+            CpuAvailabilityCallbackInfo> mAvailabilityCallbackInfosByCallbacksByCpuset;
     @GuardedBy("mLock")
-    private long mMonitoringIntervalMilliseconds = DEFAULT_CPU_MONITORING_INTERVAL_MILLISECONDS;
+    private final SparseArray<CpusetInfo> mCpusetInfosByCpuset;
+    private final Runnable mMonitorCpuStats = this::monitorCpuStats;
+
+    @GuardedBy("mLock")
+    private long mCurrentMonitoringIntervalMillis = DEFAULT_MONITORING_INTERVAL_MILLISECONDS;
+    private Handler mHandler;
 
     private final CpuMonitorInternal mLocalService = new CpuMonitorInternal() {
         @Override
@@ -63,89 +108,389 @@
                 CpuAvailabilityMonitoringConfig config, CpuAvailabilityCallback callback) {
             Objects.requireNonNull(callback, "Callback must be non-null");
             Objects.requireNonNull(config, "Config must be non-null");
+            CpuAvailabilityCallbackInfo callbackInfo;
             synchronized (mLock) {
-                if (mCpuAvailabilityCallbackInfoByCallbacks.containsKey(callback)) {
-                    Slogf.i(TAG, "Overwriting the existing CpuAvailabilityCallback %s",
-                            mCpuAvailabilityCallbackInfoByCallbacks.get(callback));
-                    // TODO(b/242722241): Overwrite any internal cache (will be added in future CLs)
-                    //  that maps callbacks based on the CPU availability thresholds.
+                // Verify all CPUSET entries before adding the callback because this will help
+                // delete any previously added callback for a different CPUSET.
+                for (int i = 0; i < mAvailabilityCallbackInfosByCallbacksByCpuset.numMaps(); i++) {
+                    int cpuset = mAvailabilityCallbackInfosByCallbacksByCpuset.keyAt(i);
+                    callbackInfo = mAvailabilityCallbackInfosByCallbacksByCpuset.delete(cpuset,
+                            callback);
+                    if (callbackInfo != null) {
+                        Slogf.i(TAG, "Overwriting the existing %s", callbackInfo);
+                    }
                 }
-                CpuAvailabilityCallbackInfo info = new CpuAvailabilityCallbackInfo(config,
-                        executor);
-                mCpuAvailabilityCallbackInfoByCallbacks.put(callback, info);
-                if (DEBUG) {
-                    Slogf.d(TAG, "Added a CPU availability callback: %s", info);
-                }
+                callbackInfo = newCallbackInfoLocked(config, callback, executor);
             }
-            // TODO(b/242722241):
-            //  * On the executor or on the handler thread, call the callback with the latest CPU
-            //    availability info and monitoring interval.
-            //  * Monitor the CPU stats more frequently when the first callback is added.
+            asyncNotifyMonitoringIntervalChangeToClient(callbackInfo);
+            if (DEBUG) {
+                Slogf.d(TAG, "Successfully added %s", callbackInfo);
+            }
         }
 
         @Override
         public void removeCpuAvailabilityCallback(CpuAvailabilityCallback callback) {
             synchronized (mLock) {
-                if (!mCpuAvailabilityCallbackInfoByCallbacks.containsKey(callback)) {
-                    Slogf.i(TAG, "CpuAvailabilityCallback was not previously added."
-                            + " Ignoring the remove request");
-                    return;
+                for (int i = 0; i < mAvailabilityCallbackInfosByCallbacksByCpuset.numMaps(); i++) {
+                    int cpuset = mAvailabilityCallbackInfosByCallbacksByCpuset.keyAt(i);
+                    CpuAvailabilityCallbackInfo callbackInfo =
+                            mAvailabilityCallbackInfosByCallbacksByCpuset.delete(cpuset, callback);
+                    if (callbackInfo != null) {
+                        if (DEBUG) {
+                            Slogf.d(TAG, "Successfully removed %s", callbackInfo);
+                        }
+                        checkAndStopMonitoringLocked();
+                        return;
+                    }
                 }
-                CpuAvailabilityCallbackInfo info =
-                        mCpuAvailabilityCallbackInfoByCallbacks.remove(callback);
-                if (DEBUG) {
-                    Slogf.d(TAG, "Removed a CPU availability callback: %s", info);
-                }
+                Slogf.w(TAG, "CpuAvailabilityCallback was not previously added. Ignoring the remove"
+                        + " request");
             }
-            // TODO(b/242722241): Increase CPU monitoring interval when all callbacks are removed.
         }
     };
 
     public CpuMonitorService(Context context) {
+        this(context, new CpuInfoReader(), new ServiceThread(TAG,
+                        Process.THREAD_PRIORITY_BACKGROUND, /* allowIo= */ true),
+                Build.IS_USERDEBUG || Build.IS_ENG, NORMAL_MONITORING_INTERVAL_MILLISECONDS,
+                DEBUG_MONITORING_INTERVAL_MILLISECONDS, LATEST_AVAILABILITY_DURATION_MILLISECONDS);
+    }
+
+    @VisibleForTesting
+    CpuMonitorService(Context context, CpuInfoReader cpuInfoReader, HandlerThread handlerThread,
+            boolean shouldDebugMonitor, long normalMonitoringIntervalMillis,
+            long debugMonitoringIntervalMillis, long latestAvailabilityDurationMillis) {
         super(context);
         mContext = context;
+        mHandlerThread = handlerThread;
+        mShouldDebugMonitor = shouldDebugMonitor;
+        mNormalMonitoringIntervalMillis = normalMonitoringIntervalMillis;
+        mDebugMonitoringIntervalMillis = debugMonitoringIntervalMillis;
+        mLatestAvailabilityDurationMillis = latestAvailabilityDurationMillis;
+        mCpuInfoReader = cpuInfoReader;
+        mCpusetInfosByCpuset = new SparseArray<>(2);
+        mCpusetInfosByCpuset.append(CPUSET_ALL, new CpusetInfo(CPUSET_ALL));
+        mCpusetInfosByCpuset.append(CPUSET_BACKGROUND, new CpusetInfo(CPUSET_BACKGROUND));
+        mAvailabilityCallbackInfosByCallbacksByCpuset = new SparseArrayMap<>();
     }
 
     @Override
     public void onStart() {
+        // Initialize CPU info reader and perform the first read to make sure the CPU stats are
+        // readable without any issues.
+        if (!mCpuInfoReader.init() || mCpuInfoReader.readCpuInfos() == null) {
+            Slogf.wtf(TAG, "Failed to initialize CPU info reader. This happens when the CPU "
+                    + "frequency stats are not available or the sysfs interface has changed in "
+                    + "the Kernel. Cannot monitor CPU without these stats. Terminating CPU monitor "
+                    + "service");
+            return;
+        }
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
         publishLocalService(CpuMonitorInternal.class, mLocalService);
         publishBinderService("cpu_monitor", new CpuMonitorBinder(), /* allowIsolated= */ false,
                 DUMP_FLAG_PRIORITY_CRITICAL);
+        Watchdog.getInstance().addThread(mHandler);
+        synchronized (mLock) {
+            if (mShouldDebugMonitor && !mHandler.hasCallbacks(mMonitorCpuStats)) {
+                mCurrentMonitoringIntervalMillis = mDebugMonitoringIntervalMillis;
+                Slogf.i(TAG, "Starting debug monitoring");
+                mHandler.post(mMonitorCpuStats);
+            }
+        }
+    }
+
+    @VisibleForTesting
+    long getCurrentMonitoringIntervalMillis() {
+        synchronized (mLock) {
+            return mCurrentMonitoringIntervalMillis;
+        }
     }
 
     private void doDump(IndentingPrintWriter writer) {
         writer.printf("*%s*\n", getClass().getSimpleName());
         writer.increaseIndent();
+        mCpuInfoReader.dump(writer);
+        writer.printf("mShouldDebugMonitor = %s\n", mShouldDebugMonitor ? "Yes" : "No");
+        writer.printf("mNormalMonitoringIntervalMillis = %d\n", mNormalMonitoringIntervalMillis);
+        writer.printf("mDebugMonitoringIntervalMillis = %d\n", mDebugMonitoringIntervalMillis);
+        writer.printf("mLatestAvailabilityDurationMillis = %d\n",
+                mLatestAvailabilityDurationMillis);
         synchronized (mLock) {
-            writer.printf("CPU monitoring interval: %d ms\n", mMonitoringIntervalMilliseconds);
-            if (!mCpuAvailabilityCallbackInfoByCallbacks.isEmpty()) {
+            writer.printf("mCurrentMonitoringIntervalMillis = %d\n",
+                    mCurrentMonitoringIntervalMillis);
+            if (hasClientCallbacksLocked()) {
                 writer.println("CPU availability change callbacks:");
                 writer.increaseIndent();
-                for (int i = 0; i < mCpuAvailabilityCallbackInfoByCallbacks.size(); i++) {
-                    writer.printf("%s: %s\n", mCpuAvailabilityCallbackInfoByCallbacks.keyAt(i),
-                            mCpuAvailabilityCallbackInfoByCallbacks.valueAt(i));
+                mAvailabilityCallbackInfosByCallbacksByCpuset.forEach(
+                        (callbackInfo) -> writer.printf("%s\n", callbackInfo));
+                writer.decreaseIndent();
+            }
+            if (mCpusetInfosByCpuset.size() > 0) {
+                writer.println("Cpuset infos:");
+                writer.increaseIndent();
+                for (int i = 0; i < mCpusetInfosByCpuset.size(); i++) {
+                    writer.printf("%s\n", mCpusetInfosByCpuset.valueAt(i));
                 }
                 writer.decreaseIndent();
             }
         }
-        // TODO(b/242722241): Print the recent past CPU stats.
         writer.decreaseIndent();
     }
 
-    private static final class CpuAvailabilityCallbackInfo {
-        public final CpuAvailabilityMonitoringConfig config;
-        public final Executor executor;
+    private void monitorCpuStats() {
+        long uptimeMillis = SystemClock.uptimeMillis();
+        // Remove duplicate callbacks caused by switching form debug to normal monitoring.
+        // The removal of the duplicate callback done in the {@link newCallbackInfoLocked} method
+        // may result in a no-op when a duplicate execution of this callback has already started
+        // on the handler thread.
+        mHandler.removeCallbacks(mMonitorCpuStats);
+        SparseArray<CpuInfoReader.CpuInfo> cpuInfosByCoreId = mCpuInfoReader.readCpuInfos();
+        if (cpuInfosByCoreId == null) {
+            // This shouldn't happen because the CPU infos are read & verified during
+            // the {@link onStart} call.
+            Slogf.wtf(TAG, "Failed to read CPU info from device");
+            synchronized (mLock) {
+                stopMonitoringCpuStatsLocked();
+            }
+            // Monitoring is stopped but no client callback is removed.
+            // TODO(b/267500110): Identify whether the clients should be notified about this state.
+            return;
+        }
 
-        CpuAvailabilityCallbackInfo(CpuAvailabilityMonitoringConfig config,
-                Executor executor) {
+        synchronized (mLock) {
+            // 1. Populate the {@link mCpusetInfosByCpuset} with the latest cpuInfo.
+            for (int i = 0; i < cpuInfosByCoreId.size(); i++) {
+                CpuInfoReader.CpuInfo cpuInfo = cpuInfosByCoreId.valueAt(i);
+                for (int j = 0; j < mCpusetInfosByCpuset.size(); j++) {
+                    mCpusetInfosByCpuset.valueAt(j).appendCpuInfo(uptimeMillis, cpuInfo);
+                }
+            }
+
+            // 2. Verify whether any monitoring thresholds are crossed and notify the corresponding
+            // clients.
+            for (int i = 0; i < mCpusetInfosByCpuset.size(); i++) {
+                CpusetInfo cpusetInfo = mCpusetInfosByCpuset.valueAt(i);
+                cpusetInfo.populateLatestCpuAvailabilityInfo(uptimeMillis,
+                        mLatestAvailabilityDurationMillis);
+                checkClientThresholdsAndNotifyLocked(cpusetInfo);
+            }
+
+            // TODO(b/267500110): Detect heavy CPU load. On detecting heavy CPU load, increase
+            // the monitoring interval and notify the clients.
+
+            // 3. Continue monitoring only when either there is at least one registered client
+            // callback or debug monitoring is enabled.
+            if (mCurrentMonitoringIntervalMillis > 0
+                    && (hasClientCallbacksLocked() || mShouldDebugMonitor)) {
+                mHandler.postAtTime(mMonitorCpuStats,
+                        uptimeMillis + mCurrentMonitoringIntervalMillis);
+            } else {
+                stopMonitoringCpuStatsLocked();
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void checkClientThresholdsAndNotifyLocked(CpusetInfo cpusetInfo) {
+        int prevAvailabilityPercent = cpusetInfo.getPrevCpuAvailabilityPercent();
+        CpuAvailabilityInfo latestAvailabilityInfo = cpusetInfo.getLatestCpuAvailabilityInfo();
+        if (latestAvailabilityInfo == null || prevAvailabilityPercent < 0
+                || mAvailabilityCallbackInfosByCallbacksByCpuset.numElementsForKey(
+                cpusetInfo.cpuset) == 0) {
+            // When either the current or the previous CPU availability percents are
+            // missing, skip the current cpuset as there is not enough data to verify
+            // whether the CPU availability has crossed any monitoring threshold.
+            return;
+        }
+        for (int i = 0; i < mAvailabilityCallbackInfosByCallbacksByCpuset.numMaps(); i++) {
+            for (int j = 0; j < mAvailabilityCallbackInfosByCallbacksByCpuset.numElementsForKeyAt(
+                    i); j++) {
+                CpuAvailabilityCallbackInfo callbackInfo =
+                        mAvailabilityCallbackInfosByCallbacksByCpuset.valueAt(i, j);
+                if (callbackInfo.config.cpuset != cpusetInfo.cpuset) {
+                    continue;
+                }
+                if (didCrossAnyThreshold(prevAvailabilityPercent,
+                        latestAvailabilityInfo.latestAvgAvailabilityPercent,
+                        callbackInfo.config.getThresholds())) {
+                    asyncNotifyCpuAvailabilityToClient(latestAvailabilityInfo, callbackInfo);
+                }
+            }
+        }
+    }
+
+    private void asyncNotifyMonitoringIntervalChangeToClient(
+            CpuAvailabilityCallbackInfo callbackInfo) {
+        if (callbackInfo.executor == null) {
+            mHandler.post(callbackInfo.notifyMonitoringIntervalChangeRunnable);
+        } else {
+            callbackInfo.executor.execute(callbackInfo.notifyMonitoringIntervalChangeRunnable);
+        }
+    }
+
+    private void asyncNotifyCpuAvailabilityToClient(CpuAvailabilityInfo availabilityInfo,
+            CpuAvailabilityCallbackInfo callbackInfo) {
+        callbackInfo.notifyCpuAvailabilityChangeRunnable.prepare(availabilityInfo);
+        if (callbackInfo.executor == null) {
+            mHandler.post(callbackInfo.notifyCpuAvailabilityChangeRunnable);
+        } else {
+            callbackInfo.executor.execute(callbackInfo.notifyCpuAvailabilityChangeRunnable);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private CpuAvailabilityCallbackInfo newCallbackInfoLocked(
+            CpuAvailabilityMonitoringConfig config,
+            CpuMonitorInternal.CpuAvailabilityCallback callback, Executor executor) {
+        CpuAvailabilityCallbackInfo callbackInfo = new CpuAvailabilityCallbackInfo(this, config,
+                callback, executor);
+        String cpusetStr = CpuAvailabilityMonitoringConfig.toCpusetString(
+                callbackInfo.config.cpuset);
+        CpusetInfo cpusetInfo = mCpusetInfosByCpuset.get(callbackInfo.config.cpuset);
+        Preconditions.checkState(cpusetInfo != null, "Missing cpuset info for cpuset %s",
+                cpusetStr);
+        boolean hasExistingClientCallbacks = hasClientCallbacksLocked();
+        mAvailabilityCallbackInfosByCallbacksByCpuset.add(callbackInfo.config.cpuset,
+                callbackInfo.callback, callbackInfo);
+        if (DEBUG) {
+            Slogf.d(TAG, "Added a CPU availability callback: %s", callbackInfo);
+        }
+        CpuAvailabilityInfo latestInfo = cpusetInfo.getLatestCpuAvailabilityInfo();
+        if (latestInfo != null) {
+            asyncNotifyCpuAvailabilityToClient(latestInfo, callbackInfo);
+        }
+        if (hasExistingClientCallbacks && mHandler.hasCallbacks(mMonitorCpuStats)) {
+            return callbackInfo;
+        }
+        // Remove existing callbacks to ensure any debug monitoring (if started) is stopped before
+        // starting normal monitoring.
+        mHandler.removeCallbacks(mMonitorCpuStats);
+        mCurrentMonitoringIntervalMillis = mNormalMonitoringIntervalMillis;
+        mHandler.post(mMonitorCpuStats);
+        return callbackInfo;
+    }
+
+    @GuardedBy("mLock")
+    private void checkAndStopMonitoringLocked() {
+        if (hasClientCallbacksLocked()) {
+            return;
+        }
+        if (mShouldDebugMonitor) {
+            if (DEBUG) {
+                Slogf.e(TAG, "Switching to debug monitoring");
+            }
+            mCurrentMonitoringIntervalMillis = mDebugMonitoringIntervalMillis;
+        } else {
+            stopMonitoringCpuStatsLocked();
+        }
+    }
+
+    @GuardedBy("mLock")
+    private boolean hasClientCallbacksLocked() {
+        for (int i = 0; i < mAvailabilityCallbackInfosByCallbacksByCpuset.numMaps(); i++) {
+            if (mAvailabilityCallbackInfosByCallbacksByCpuset.numElementsForKeyAt(i) > 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @GuardedBy("mLock")
+    private void stopMonitoringCpuStatsLocked() {
+        mHandler.removeCallbacks(mMonitorCpuStats);
+        mCurrentMonitoringIntervalMillis = DEFAULT_MONITORING_INTERVAL_MILLISECONDS;
+        // When the monitoring is stopped, the latest CPU availability info and the snapshots in
+        // {@code mCpusetInfosByCpuset} will become obsolete soon. So, remove them.
+        for (int i = 0; i < mCpusetInfosByCpuset.size(); i++) {
+            mCpusetInfosByCpuset.valueAt(i).clear();
+        }
+    }
+
+    private static boolean containsCpuset(@CpuInfoReader.CpusetCategory int cpusetCategories,
+            @CpuAvailabilityMonitoringConfig.Cpuset int expectedCpuset) {
+        switch (expectedCpuset) {
+            case CPUSET_ALL:
+                return (cpusetCategories & FLAG_CPUSET_CATEGORY_TOP_APP) != 0;
+            case CPUSET_BACKGROUND:
+                return (cpusetCategories & FLAG_CPUSET_CATEGORY_BACKGROUND) != 0;
+            default:
+                Slogf.wtf(TAG, "Provided invalid expectedCpuset %d", expectedCpuset);
+        }
+        return false;
+    }
+
+    private static boolean didCrossAnyThreshold(int prevAvailabilityPercent,
+            int curAvailabilityPercent, IntArray thresholds) {
+        if (prevAvailabilityPercent == curAvailabilityPercent) {
+            return false;
+        }
+        for (int i = 0; i < thresholds.size(); i++) {
+            int threshold = thresholds.get(i);
+            // TODO(b/267500110): Identify whether or not the clients need to be notified when
+            //  the CPU availability jumps too frequently around the provided thresholds.
+            //  A. Should the client be notified twice - once when the availability reaches
+            //     the threshold and once when it moves away (increase/decrease) from the threshold
+            //     immediately?
+            //  B. Should there be some sort of rate-limiting to avoid notifying the client too
+            //     frequently? Should the client be able to config the rate-limit?
+            if (prevAvailabilityPercent < threshold && curAvailabilityPercent >= threshold) {
+                return true;
+            }
+            if (prevAvailabilityPercent >= threshold && curAvailabilityPercent < threshold) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static final class CpuAvailabilityCallbackInfo {
+        public final CpuMonitorService service;
+        public final CpuAvailabilityMonitoringConfig config;
+        public final CpuMonitorInternal.CpuAvailabilityCallback callback;
+        @Nullable
+        public final Executor executor;
+        public final Runnable notifyMonitoringIntervalChangeRunnable = new Runnable() {
+            @Override
+            public void run() {
+                callback.onMonitoringIntervalChanged(service.getCurrentMonitoringIntervalMillis());
+            }
+        };
+        public final NotifyCpuAvailabilityChangeRunnable notifyCpuAvailabilityChangeRunnable =
+                new NotifyCpuAvailabilityChangeRunnable();
+
+        CpuAvailabilityCallbackInfo(CpuMonitorService service,
+                CpuAvailabilityMonitoringConfig config,
+                CpuMonitorInternal.CpuAvailabilityCallback callback, @Nullable Executor executor) {
+            this.service = service;
             this.config = config;
+            this.callback = callback;
             this.executor = executor;
         }
 
         @Override
         public String toString() {
-            return "CpuAvailabilityCallbackInfo{" + "config=" + config + ", mExecutor=" + executor
-                    + '}';
+            return "CpuAvailabilityCallbackInfo{config = " + config + ", callback = " + callback
+                    + ", mExecutor = " + executor + '}';
+        }
+
+        private final class NotifyCpuAvailabilityChangeRunnable implements Runnable {
+            private final Object mLock = new Object();
+            @GuardedBy("mLock")
+            private CpuAvailabilityInfo mCpuAvailabilityInfo;
+
+            public void prepare(CpuAvailabilityInfo cpuAvailabilityInfo) {
+                synchronized (mLock) {
+                    mCpuAvailabilityInfo = cpuAvailabilityInfo;
+                }
+            }
+
+            @Override
+            public void run() {
+                synchronized (mLock) {
+                    callback.onAvailabilityChanged(mCpuAvailabilityInfo);
+                }
+            }
         }
     }
 
@@ -170,4 +515,157 @@
             PriorityDump.dump(mPriorityDumper, fd, pw, args);
         }
     }
+
+    private static final class CpusetInfo {
+        @CpuAvailabilityMonitoringConfig.Cpuset
+        public final int cpuset;
+        private final LongSparseArray<Snapshot> mSnapshotsByUptime;
+        @Nullable
+        private CpuAvailabilityInfo mLatestCpuAvailabilityInfo;
+
+        CpusetInfo(int cpuset) {
+            this.cpuset = cpuset;
+            mSnapshotsByUptime = new LongSparseArray<>();
+        }
+
+        public void appendCpuInfo(long uptimeMillis, CpuInfoReader.CpuInfo cpuInfo) {
+            if (!containsCpuset(cpuInfo.cpusetCategories, cpuset)) {
+                return;
+            }
+            Snapshot currentSnapshot = mSnapshotsByUptime.get(uptimeMillis);
+            if (currentSnapshot == null) {
+                currentSnapshot = new Snapshot(uptimeMillis);
+                mSnapshotsByUptime.append(uptimeMillis, currentSnapshot);
+                if (mSnapshotsByUptime.size() > 0
+                        && (uptimeMillis - mSnapshotsByUptime.valueAt(0).uptimeMillis)
+                        > CACHE_DURATION_MILLISECONDS) {
+                    mSnapshotsByUptime.removeAt(0);
+                }
+            }
+            currentSnapshot.appendCpuInfo(cpuInfo);
+        }
+
+        @Nullable
+        public CpuAvailabilityInfo getLatestCpuAvailabilityInfo() {
+            return mLatestCpuAvailabilityInfo;
+        }
+
+        public void populateLatestCpuAvailabilityInfo(long currentUptimeMillis,
+                long latestAvailabilityDurationMillis) {
+            int numSnapshots = mSnapshotsByUptime.size();
+            if (numSnapshots == 0) {
+                mLatestCpuAvailabilityInfo = null;
+                return;
+            }
+            Snapshot latestSnapshot = mSnapshotsByUptime.valueAt(numSnapshots - 1);
+            if (latestSnapshot.uptimeMillis != currentUptimeMillis) {
+                // When the cpuset has no stats available for the current polling, the uptime will
+                // mismatch. When this happens, return {@code null} to avoid returning stale
+                // information.
+                if (DEBUG) {
+                    Slogf.d(TAG, "Skipping stale CPU availability information for cpuset %s",
+                            CpuAvailabilityMonitoringConfig.toCpusetString(cpuset));
+                }
+                mLatestCpuAvailabilityInfo = null;
+                return;
+            }
+            // Avoid constructing {@link mLatestCpuAvailabilityInfo} if the uptime hasn't changed.
+            if (mLatestCpuAvailabilityInfo != null
+                    && mLatestCpuAvailabilityInfo.dataTimestampUptimeMillis
+                    == latestSnapshot.uptimeMillis) {
+                return;
+            }
+            long earliestUptimeMillis = currentUptimeMillis - latestAvailabilityDurationMillis;
+            mLatestCpuAvailabilityInfo = new CpuAvailabilityInfo(cpuset,
+                    latestSnapshot.uptimeMillis, latestSnapshot.getAverageAvailableCpuFreqPercent(),
+                    getCumulativeAvgAvailabilityPercent(earliestUptimeMillis),
+                    latestAvailabilityDurationMillis);
+        }
+
+        public int getPrevCpuAvailabilityPercent() {
+            int numSnapshots = mSnapshotsByUptime.size();
+            if (numSnapshots < 2) {
+                return -1;
+            }
+            return mSnapshotsByUptime.valueAt(numSnapshots - 2).getAverageAvailableCpuFreqPercent();
+        }
+
+        private int getCumulativeAvgAvailabilityPercent(long earliestUptimeMillis) {
+            long totalAvailableCpuFreqKHz = 0;
+            long totalOnlineMaxCpuFreqKHz = 0;
+            int totalAccountedSnapshots = 0;
+            long earliestSeenUptimeMillis = Long.MAX_VALUE;
+            for (int i = mSnapshotsByUptime.size() - 1; i >= 0; i--) {
+                Snapshot snapshot = mSnapshotsByUptime.valueAt(i);
+                earliestSeenUptimeMillis = snapshot.uptimeMillis;
+                if (snapshot.uptimeMillis <= earliestUptimeMillis) {
+                    break;
+                }
+                totalAccountedSnapshots++;
+                totalAvailableCpuFreqKHz += snapshot.totalNormalizedAvailableCpuFreqKHz;
+                totalOnlineMaxCpuFreqKHz += snapshot.totalOnlineMaxCpuFreqKHz;
+            }
+            // The cache must have at least 2 snapshots within the given duration and
+            // the {@link earliestSeenUptimeMillis} must be earlier than (i,e., less than) the given
+            // {@link earliestUptimeMillis}. Otherwise, the cache doesn't have enough data to
+            // calculate the cumulative average for the given duration.
+            // TODO(b/267500110): Investigate whether the cumulative average duration should be
+            //  shrunk when not enough data points are available.
+            if (earliestSeenUptimeMillis > earliestUptimeMillis || totalAccountedSnapshots < 2) {
+                return CpuAvailabilityInfo.MISSING_CPU_AVAILABILITY_PERCENT;
+            }
+            return (int) ((totalAvailableCpuFreqKHz * 100.0) / totalOnlineMaxCpuFreqKHz);
+        }
+
+        public void clear() {
+            mLatestCpuAvailabilityInfo = null;
+            mSnapshotsByUptime.clear();
+        }
+
+        @Override
+        public String toString() {
+            return "CpusetInfo{cpuset = " + CpuAvailabilityMonitoringConfig.toCpusetString(cpuset)
+                    + ", mSnapshotsByUptime = " + mSnapshotsByUptime
+                    + ", mLatestCpuAvailabilityInfo = " + mLatestCpuAvailabilityInfo + '}';
+        }
+
+        private static final class Snapshot {
+            public final long uptimeMillis;
+            public int totalOnlineCpus;
+            public int totalOfflineCpus;
+            public long totalNormalizedAvailableCpuFreqKHz;
+            public long totalOnlineMaxCpuFreqKHz;
+            public long totalOfflineMaxCpuFreqKHz;
+
+            Snapshot(long uptimeMillis) {
+                this.uptimeMillis = uptimeMillis;
+            }
+
+            public void appendCpuInfo(CpuInfoReader.CpuInfo cpuInfo) {
+                if (!cpuInfo.isOnline) {
+                    totalOfflineCpus++;
+                    totalOfflineMaxCpuFreqKHz += cpuInfo.maxCpuFreqKHz;
+                    return;
+                }
+                ++totalOnlineCpus;
+                totalNormalizedAvailableCpuFreqKHz += cpuInfo.getNormalizedAvailableCpuFreqKHz();
+                totalOnlineMaxCpuFreqKHz += cpuInfo.maxCpuFreqKHz;
+            }
+
+            public int getAverageAvailableCpuFreqPercent() {
+                return (int) ((totalNormalizedAvailableCpuFreqKHz * 100.0)
+                        / totalOnlineMaxCpuFreqKHz);
+            }
+
+            @Override
+            public String toString() {
+                return "Snapshot{uptimeMillis = " + uptimeMillis + ", totalOnlineCpus = "
+                        + totalOnlineCpus + ", totalOfflineCpus = " + totalOfflineCpus
+                        + ", totalNormalizedAvailableCpuFreqKHz = "
+                        + totalNormalizedAvailableCpuFreqKHz
+                        + ", totalOnlineMaxCpuFreqKHz = " + totalOnlineMaxCpuFreqKHz
+                        + ", totalOfflineMaxCpuFreqKHz = " + totalOfflineMaxCpuFreqKHz + '}';
+            }
+        }
+    }
 }
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/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/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index f873a1b..4d4a87e 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -65,6 +65,7 @@
 import com.android.internal.messages.nano.SystemMessageProto;
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.util.XmlUtils;
+import com.android.server.inputmethod.InputMethodManagerInternal;
 
 import libcore.io.Streams;
 
@@ -1226,9 +1227,15 @@
                 mContext.getSystemService(UserManager.class));
         InputMethodManager inputMethodManager = Objects.requireNonNull(
                 mContext.getSystemService(InputMethodManager.class));
+        // Need to use InputMethodManagerInternal to call getEnabledInputMethodListAsUser()
+        // instead of using InputMethodManager which uses enforceCallingPermissions() that
+        // breaks when we are calling the method for work profile user ID since it doesn't check
+        // self permissions.
+        InputMethodManagerInternal inputMethodManagerInternal = InputMethodManagerInternal.get();
         for (UserHandle userHandle : userManager.getUserHandles(true /* excludeDying */)) {
             int userId = userHandle.getIdentifier();
-            for (InputMethodInfo imeInfo : inputMethodManager.getEnabledInputMethodListAsUser(
+            for (InputMethodInfo imeInfo :
+                    inputMethodManagerInternal.getEnabledInputMethodListAsUser(
                     userId)) {
                 for (InputMethodSubtype imeSubtype :
                         inputMethodManager.getEnabledInputMethodSubtypeList(
diff --git a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
index 9f7ff31..0ae1e80 100644
--- a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
+++ b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
@@ -136,17 +136,16 @@
                 mWindowManagerInternal.showImePostLayout(windowToken, statsToken);
                 break;
             case STATE_HIDE_IME:
-                if (mService.mCurFocusedWindowClient != null) {
+                if (mService.hasAttachedClient()) {
                     ImeTracker.forLogging().onProgress(statsToken,
                             ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
                     // IMMS only knows of focused window, not the actual IME target.
                     // e.g. it isn't aware of any window that has both
                     // NOT_FOCUSABLE, ALT_FOCUSABLE_IM flags set and can the IME target.
-                    // Send it to window manager to hide IME from IME target window.
-                    // TODO(b/139861270): send to mCurClient.client once IMMS is aware of
-                    // actual IME target.
+                    // Send it to window manager to hide IME from the actual IME control target
+                    // of the target display.
                     mWindowManagerInternal.hideIme(windowToken,
-                            mService.mCurFocusedWindowClient.mSelfReportedDisplayId, statsToken);
+                            mService.getDisplayIdToShowImeLocked(), statsToken);
                 } else {
                     ImeTracker.forLogging().onFailed(statsToken,
                             ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 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/location/gnss/GnssConfiguration.java b/services/core/java/com/android/server/location/gnss/GnssConfiguration.java
index 77cd673..a081dff 100644
--- a/services/core/java/com/android/server/location/gnss/GnssConfiguration.java
+++ b/services/core/java/com/android/server/location/gnss/GnssConfiguration.java
@@ -92,6 +92,8 @@
     // Represents an HAL interface version. Instances of this class are created in the JNI layer
     // and returned through native methods.
     static class HalInterfaceVersion {
+        // mMajor being this value denotes AIDL HAL. In this case, mMinor denotes the AIDL version.
+        static final int AIDL_INTERFACE = 3;
         final int mMajor;
         final int mMinor;
 
diff --git a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
index 6c4c829..041f11d 100644
--- a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
@@ -21,6 +21,7 @@
 import static com.android.server.location.gnss.GnssManagerService.D;
 import static com.android.server.location.gnss.GnssManagerService.TAG;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
 import android.location.GnssMeasurementRequest;
@@ -31,6 +32,7 @@
 import android.stats.location.LocationStatsEnums;
 import android.util.Log;
 
+import com.android.server.location.gnss.GnssConfiguration.HalInterfaceVersion;
 import com.android.server.location.gnss.hal.GnssNative;
 import com.android.server.location.injector.AppOpsHelper;
 import com.android.server.location.injector.Injector;
@@ -115,16 +117,6 @@
         if (request.getIntervalMillis() == GnssMeasurementRequest.PASSIVE_INTERVAL) {
             return true;
         }
-        // The HAL doc does not specify if consecutive start() calls will be allowed.
-        // Some vendors may ignore the 2nd start() call if stop() is not called.
-        // Thus, here we always call stop() before calling start() to avoid being ignored.
-        if (mGnssNative.stopMeasurementCollection()) {
-            if (D) {
-                Log.d(TAG, "stopping gnss measurements");
-            }
-        } else {
-            Log.e(TAG, "error stopping gnss measurements");
-        }
         if (mGnssNative.startMeasurementCollection(request.isFullTracking(),
                 request.isCorrelationVectorOutputsEnabled(),
                 request.getIntervalMillis())) {
@@ -139,6 +131,28 @@
     }
 
     @Override
+    protected boolean reregisterWithService(GnssMeasurementRequest old,
+            GnssMeasurementRequest request,
+            @NonNull Collection<GnssListenerRegistration> registrations) {
+        if (request.getIntervalMillis() == GnssMeasurementRequest.PASSIVE_INTERVAL) {
+            unregisterWithService();
+            return true;
+        }
+        HalInterfaceVersion halInterfaceVersion =
+                mGnssNative.getConfiguration().getHalInterfaceVersion();
+        boolean aidlV3Plus = halInterfaceVersion.mMajor == HalInterfaceVersion.AIDL_INTERFACE
+                && halInterfaceVersion.mMinor >= 3;
+        if (!aidlV3Plus) {
+            // The HAL doc does not specify if consecutive start() calls will be allowed.
+            // Some vendors may ignore the 2nd start() call if stop() is not called.
+            // Thus, here we always call stop() before calling start() to avoid being ignored.
+            // AIDL v3+ is free from this issue.
+            unregisterWithService();
+        }
+        return registerWithService(request, registrations);
+    }
+
+    @Override
     protected void unregisterWithService() {
         if (mGnssNative.stopMeasurementCollection()) {
             if (D) {
diff --git a/services/core/java/com/android/server/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..bb79c99 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2340,7 +2340,6 @@
                 mAppOps,
                 new SysUiStatsEvent.BuilderFactory(),
                 mShowReviewPermissionsNotification);
-        mPreferencesHelper.updateFixedImportance(mUm.getUsers());
         mRankingHelper = new RankingHelper(getContext(),
                 mRankingHandler,
                 mPreferencesHelper,
@@ -2771,6 +2770,9 @@
             maybeShowInitialReviewPermissionsNotification();
         } else if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
             mSnoozeHelper.scheduleRepostsForPersistedNotifications(System.currentTimeMillis());
+        } else if (phase == SystemService.PHASE_DEVICE_SPECIFIC_SERVICES_READY) {
+            mPreferencesHelper.updateFixedImportance(mUm.getUsers());
+            mPreferencesHelper.migrateNotificationPermissions(mUm.getUsers());
         }
     }
 
@@ -6864,7 +6866,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/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 4bafbc7..aa97aa3 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -237,7 +237,6 @@
                     Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
                     NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW);
         }
-        ArrayList<PermissionHelper.PackagePermission> pkgPerms = new ArrayList<>();
         synchronized (mPackagePreferences) {
             while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
                 tag = parser.getName();
@@ -255,27 +254,18 @@
                         String name = parser.getAttributeValue(null, ATT_NAME);
                         if (!TextUtils.isEmpty(name)) {
                             restorePackage(parser, forRestore, userId, name, upgradeForBubbles,
-                                    migrateToPermission, pkgPerms);
+                                    migrateToPermission);
                         }
                     }
                 }
             }
         }
-        if (migrateToPermission) {
-            for (PackagePermission p : pkgPerms) {
-                try {
-                    mPermissionHelper.setNotificationPermission(p);
-                } catch (Exception e) {
-                    Slog.e(TAG, "could not migrate setting for " + p.packageName, e);
-                }
-            }
-        }
     }
 
     @GuardedBy("mPackagePreferences")
     private void restorePackage(TypedXmlPullParser parser, boolean forRestore,
             @UserIdInt int userId, String name, boolean upgradeForBubbles,
-            boolean migrateToPermission, ArrayList<PermissionHelper.PackagePermission> pkgPerms) {
+            boolean migrateToPermission) {
         try {
             int uid = parser.getAttributeInt(null, ATT_UID, UNKNOWN_UID);
             if (forRestore) {
@@ -379,14 +369,6 @@
             if (migrateToPermission) {
                 r.importance = appImportance;
                 r.migrateToPm = true;
-                if (r.uid != UNKNOWN_UID) {
-                    // Don't call into permission system until we have a valid uid
-                    PackagePermission pkgPerm = new PackagePermission(
-                            r.pkg, UserHandle.getUserId(r.uid),
-                            r.importance != IMPORTANCE_NONE,
-                            hasUserConfiguredSettings(r));
-                    pkgPerms.add(pkgPerm);
-                }
             }
         } catch (Exception e) {
             Slog.w(TAG, "Failed to restore pkg", e);
@@ -2663,6 +2645,31 @@
         }
     }
 
+    public void migrateNotificationPermissions(List<UserInfo> users) {
+        for (UserInfo user : users) {
+            List<PackageInfo> packages = mPm.getInstalledPackagesAsUser(
+                    PackageManager.PackageInfoFlags.of(PackageManager.MATCH_ALL),
+                    user.getUserHandle().getIdentifier());
+            for (PackageInfo pi : packages) {
+                synchronized (mPackagePreferences) {
+                    PackagePreferences p = getOrCreatePackagePreferencesLocked(
+                            pi.packageName, pi.applicationInfo.uid);
+                    if (p.migrateToPm && p.uid != UNKNOWN_UID) {
+                        try {
+                            PackagePermission pkgPerm = new PackagePermission(
+                                    p.pkg, UserHandle.getUserId(p.uid),
+                                    p.importance != IMPORTANCE_NONE,
+                                    hasUserConfiguredSettings(p));
+                            mPermissionHelper.setNotificationPermission(pkgPerm);
+                        } catch (Exception e) {
+                            Slog.e(TAG, "could not migrate setting for " + p.pkg, e);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
     private void updateConfig() {
         mRankingHandler.requestSort();
     }
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/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index a119a3c..9a5ee81 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -19,6 +19,7 @@
 import static android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+import static android.content.pm.PackageManager.DELETE_SUCCEEDED;
 import static android.content.pm.PackageManager.MATCH_KNOWN_PACKAGES;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.storage.StorageManager.FLAG_STORAGE_CE;
@@ -339,7 +340,7 @@
             packageInstallerService.onInstallerPackageDeleted(uninstalledPs.getAppId(), removeUser);
         }
 
-        return res ? PackageManager.DELETE_SUCCEEDED : PackageManager.DELETE_FAILED_INTERNAL_ERROR;
+        return res ? DELETE_SUCCEEDED : PackageManager.DELETE_FAILED_INTERNAL_ERROR;
     }
 
     /*
@@ -777,21 +778,30 @@
                     returnCode = deletePackageX(internalPackageName, versionCode,
                             userId, deleteFlags, false /*removedBySystem*/);
 
-                    // Get a list of child user profiles and delete if package is
-                    // present in that profile.
-                    int[] childUserIds = mUserManagerInternal.getProfileIds(userId, true);
-                    int returnCodeOfChild;
-                    for (int childId : childUserIds) {
-                        if (childId == userId) continue;
-                        UserProperties userProperties = mUserManagerInternal
-                                .getUserProperties(childId);
-                        if (userProperties != null && userProperties.getDeleteAppWithParent()) {
-                            returnCodeOfChild = deletePackageX(internalPackageName, versionCode,
-                                    childId, deleteFlags, false /*removedBySystem*/);
-                            if (returnCodeOfChild != PackageManager.DELETE_SUCCEEDED) {
-                                Slog.w(TAG, "Package delete failed for user " + childId
-                                        + ", returnCode " + returnCodeOfChild);
-                                returnCode = PackageManager.DELETE_FAILED_FOR_CHILD_PROFILE;
+                    // Delete package in child only if successfully deleted in parent.
+                    if (returnCode == DELETE_SUCCEEDED && packageState != null) {
+                        // Get a list of child user profiles and delete if package is
+                        // present in that profile.
+                        int[] childUserIds = mUserManagerInternal.getProfileIds(userId, true);
+                        int returnCodeOfChild;
+                        for (int childId : childUserIds) {
+                            if (childId == userId) continue;
+
+                            // If package is not present in child then don't attempt to delete.
+                            if (!packageState.getUserStateOrDefault(childId).isInstalled()) {
+                                continue;
+                            }
+
+                            UserProperties userProperties = mUserManagerInternal
+                                    .getUserProperties(childId);
+                            if (userProperties != null && userProperties.getDeleteAppWithParent()) {
+                                returnCodeOfChild = deletePackageX(internalPackageName, versionCode,
+                                        childId, deleteFlags, false /*removedBySystem*/);
+                                if (returnCodeOfChild != DELETE_SUCCEEDED) {
+                                    Slog.w(TAG, "Package delete failed for user " + childId
+                                            + ", returnCode " + returnCodeOfChild);
+                                    returnCode = PackageManager.DELETE_FAILED_FOR_CHILD_PROFILE;
+                                }
                             }
                         }
                     }
@@ -809,7 +819,7 @@
                             if (!ArrayUtils.contains(blockUninstallUserIds, userId1)) {
                                 returnCode = deletePackageX(internalPackageName, versionCode,
                                         userId1, userFlags, false /*removedBySystem*/);
-                                if (returnCode != PackageManager.DELETE_SUCCEEDED) {
+                                if (returnCode != DELETE_SUCCEEDED) {
                                     Slog.w(TAG, "Package delete failed for user " + userId1
                                             + ", returnCode " + returnCode);
                                 }
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/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 569999e..7fe6c7d 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -1134,22 +1134,22 @@
         // behavior.
         if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE,
                 "MinInstallableTargetSdk__install_block_enabled",
-                true)) {
+                false)) {
             int minInstallableTargetSdk =
                     DeviceConfig.getInt(DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE,
                             "MinInstallableTargetSdk__min_installable_target_sdk",
-                            PackageManagerService.MIN_INSTALLABLE_TARGET_SDK);
+                            0);
 
             // Determine if enforcement is in strict mode
             boolean strictMode = false;
 
             if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE,
                     "MinInstallableTargetSdk__install_block_strict_mode_enabled",
-                    true)) {
+                    false)) {
                 if (parsedPackage.getTargetSdkVersion()
                         < DeviceConfig.getInt(DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE,
                         "MinInstallableTargetSdk__strict_mode_target_sdk",
-                        PackageManagerService.MIN_INSTALLABLE_TARGET_SDK)) {
+                        0)) {
                     strictMode = true;
                 }
             }
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 c5d7d07..6bc8760 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -557,14 +557,6 @@
     // How many required verifiers can be on the system.
     private static final int REQUIRED_VERIFIERS_MAX_COUNT = 2;
 
-    /**
-     * Specifies the minimum target SDK version an apk must specify in order to be installed
-     * on the system. This improves security and privacy by blocking low
-     * target sdk apps as malware can target older sdk versions to avoid
-     * the enforcement of new API behavior.
-     */
-    public static final int MIN_INSTALLABLE_TARGET_SDK = Build.VERSION_CODES.M;
-
     // Compilation reasons.
     // TODO(b/260124949): Clean this up with the legacy dexopt code.
     public static final int REASON_FIRST_BOOT = 0;
@@ -1336,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())) {
@@ -2577,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");
@@ -6819,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/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 586e112..232ca45 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -4400,15 +4400,9 @@
         pw.println("      -f: force compilation even if not needed");
         pw.println("      -m: select compilation mode");
         pw.println("          MODE is one of the dex2oat compiler filters:");
-        pw.println("            assume-verified");
-        pw.println("            extract");
         pw.println("            verify");
-        pw.println("            quicken");
-        pw.println("            space-profile");
-        pw.println("            space");
         pw.println("            speed-profile");
         pw.println("            speed");
-        pw.println("            everything");
         pw.println("      -r: select compilation reason");
         pw.println("          REASON is one of:");
         for (int i = 0; i < PackageManagerServiceCompilerMapping.REASON_STRINGS.length; i++) {
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/ReconcilePackageUtils.java b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
index d160740..5312ae6 100644
--- a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
+++ b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
@@ -69,7 +69,6 @@
 
         for (InstallRequest installRequest :  installRequests) {
             installRequest.onReconcileStarted();
-            final String installPackageName = installRequest.getParsedPackage().getPackageName();
 
             // add / replace existing with incoming packages
             combinedPackages.put(installRequest.getScannedPackageSetting().getPackageName(),
@@ -84,12 +83,17 @@
                             incomingSharedLibraries, info)) {
                         throw ReconcileFailure.ofInternalError(
                                 "Shared Library " + info.getName()
-                                + " is being installed twice in this set!",
+                                        + " is being installed twice in this set!",
                                 PackageManagerException.INTERNAL_ERROR_SHARED_LIB_INSTALLED_TWICE);
                     }
                 }
             }
+        }
 
+        for (InstallRequest installRequest : installRequests) {
+            final String installPackageName = installRequest.getParsedPackage().getPackageName();
+            final List<SharedLibraryInfo> allowedSharedLibInfos =
+                    sharedLibraries.getAllowedSharedLibInfos(installRequest);
 
             final DeletePackageAction deletePackageAction;
             // we only want to try to delete for non system apps
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/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index cc40363..7414640 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -4737,9 +4737,9 @@
                             UserManager.USER_OPERATION_ERROR_MAX_USERS);
                 }
                 // Keep logic in sync with getRemainingCreatableUserCount()
-                if (!isGuest && !isProfile && !isDemo && isUserLimitReached()) {
+                if (!isGuest && !isManagedProfile && !isDemo && isUserLimitReached()) {
                     // If the user limit has been reached, we cannot add a user (except guest/demo).
-                    // Note that profiles can bypass it in certain circumstances (taken
+                    // Note that managed profiles can bypass it in certain circumstances (taken
                     // into account in the profile check below).
                     throwCheckedUserOperationException(
                             "Cannot add user. Maximum user limit is reached.",
@@ -5865,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
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/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/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index ea53ea5..3eeafeb 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3183,6 +3183,13 @@
                     }
                 }
                 break;
+            case KeyEvent.KEYCODE_LANGUAGE_SWITCH:
+                if (down && repeatCount == 0) {
+                    int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
+                    sendSwitchKeyboardLayout(event, direction);
+                    return key_consumed;
+                }
+                break;
             case KeyEvent.KEYCODE_SPACE:
                 // Handle keyboard layout switching. (META + SPACE)
                 if ((metaState & KeyEvent.META_META_MASK) == 0) {
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/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/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 5442b6d..ad789d8 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -2128,7 +2128,7 @@
         public void notifyTvMessage(IBinder sessionToken, int type, Bundle data, int userId) {
             final int callingUid = Binder.getCallingUid();
             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
-                    userId, "timeShiftEnablePositionTracking");
+                    userId, "notifyTvmessage");
             final long identity = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
@@ -2136,7 +2136,28 @@
                         getSessionLocked(sessionToken, callingUid, resolvedUserId)
                                 .notifyTvMessage(type, data);
                     } catch (RemoteException | SessionNotFoundException e) {
-                        Slog.e(TAG, "error in timeShiftEnablePositionTracking", e);
+                        Slog.e(TAG, "error in notifyTvMessage", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void setTvMessageEnabled(IBinder sessionToken, int type, boolean enabled,
+                int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "setTvMessageEnabled");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
+                                .setTvMessageEnabled(type, enabled);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slog.e(TAG, "error in setTvMessageEnabled", e);
                     }
                 }
             } finally {
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/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index c9eef38..55060a6 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -90,7 +90,6 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.os.storage.StorageManager;
 import android.service.wallpaper.IWallpaperConnection;
 import android.service.wallpaper.IWallpaperEngine;
 import android.service.wallpaper.IWallpaperService;
@@ -2210,12 +2209,7 @@
     public ParcelFileDescriptor getWallpaperWithFeature(String callingPkg, String callingFeatureId,
             IWallpaperManagerCallback cb, final int which, Bundle outParams, int wallpaperUserId,
             boolean getCropped) {
-        final boolean hasPrivilege = hasPermission(READ_WALLPAPER_INTERNAL);
-        if (!hasPrivilege) {
-            mContext.getSystemService(StorageManager.class).checkPermissionReadImages(true,
-                    Binder.getCallingPid(), Binder.getCallingUid(), callingPkg, callingFeatureId);
-        }
-
+        checkPermission(READ_WALLPAPER_INTERNAL);
         wallpaperUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
                 Binder.getCallingUid(), wallpaperUserId, false, true, "getWallpaper", null);
 
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index d249f8c..e21c156 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4357,17 +4357,6 @@
     @Override
     void addWindow(WindowState w) {
         super.addWindow(w);
-
-        boolean gotReplacementWindow = false;
-        for (int i = mChildren.size() - 1; i >= 0; i--) {
-            final WindowState candidate = mChildren.get(i);
-            gotReplacementWindow |= candidate.setReplacementWindowIfNeeded(w);
-        }
-
-        // if we got a replacement window, reset the timeout to give drawing more time
-        if (gotReplacementWindow) {
-            mWmService.scheduleWindowReplacementTimeouts(this);
-        }
         checkKeyguardFlagsChanged();
     }
 
@@ -4382,12 +4371,6 @@
         updateLetterboxSurface(child);
     }
 
-    void onWindowReplacementTimeout() {
-        for (int i = mChildren.size() - 1; i >= 0; --i) {
-            (mChildren.get(i)).onWindowReplacementTimeout();
-        }
-    }
-
     void setAppLayoutChanges(int changes, String reason) {
         if (!mChildren.isEmpty()) {
             final DisplayContent dc = getDisplayContent();
@@ -4398,15 +4381,6 @@
         }
     }
 
-    void removeReplacedWindowIfNeeded(WindowState replacement) {
-        for (int i = mChildren.size() - 1; i >= 0; i--) {
-            final WindowState win = mChildren.get(i);
-            if (win.removeReplacedWindowIfNeeded(replacement)) {
-                return;
-            }
-        }
-    }
-
     private boolean transferStartingWindow(@NonNull ActivityRecord fromActivity) {
         final WindowState tStartingWindow = fromActivity.mStartingWindow;
         if (tStartingWindow != null && fromActivity.mStartingSurface != null) {
@@ -5225,6 +5199,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;
         }
@@ -5263,13 +5242,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));
+                }
             }
         }
 
@@ -5346,6 +5330,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)) {
@@ -5619,10 +5607,7 @@
         // * activity is transitioning visibility state
         // * or the activity was marked as hidden and is exiting before we had a chance to play the
         // transition animation
-        // * or this is an opening app and windows are being replaced (e.g. freeform window to
-        //   normal window).
-        return isVisible() != visible || mRequestForceTransition || (!isVisible() && mIsExiting)
-                || (visible && forAllWindows(WindowState::waitingForReplacement, true));
+        return isVisible() != visible || mRequestForceTransition || (!isVisible() && mIsExiting);
     }
 
     /**
@@ -7366,35 +7351,6 @@
         }
     }
 
-    void setWillReplaceWindows(boolean animate) {
-        ProtoLog.d(WM_DEBUG_ADD_REMOVE,
-                "Marking app token %s with replacing windows.", this);
-
-        for (int i = mChildren.size() - 1; i >= 0; i--) {
-            final WindowState w = mChildren.get(i);
-            w.setWillReplaceWindow(animate);
-        }
-    }
-
-    void setWillReplaceChildWindows() {
-        ProtoLog.d(WM_DEBUG_ADD_REMOVE, "Marking app token %s"
-                + " with replacing child windows.", this);
-        for (int i = mChildren.size() - 1; i >= 0; i--) {
-            final WindowState w = mChildren.get(i);
-            w.setWillReplaceChildWindows();
-        }
-    }
-
-    void clearWillReplaceWindows() {
-        ProtoLog.d(WM_DEBUG_ADD_REMOVE,
-                "Resetting app token %s of replacing window marks.", this);
-
-        for (int i = mChildren.size() - 1; i >= 0; i--) {
-            final WindowState w = mChildren.get(i);
-            w.clearWillReplaceWindow();
-        }
-    }
-
     void requestUpdateWallpaperIfNeeded() {
         for (int i = mChildren.size() - 1; i >= 0; i--) {
             final WindowState w = mChildren.get(i);
diff --git a/services/core/java/com/android/server/wm/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/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 0b28ba2..bc9efc8 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -377,15 +377,10 @@
             mDragWindowHandle.ownerUid = MY_UID;
             mDragWindowHandle.scaleFactor = 1.0f;
 
-            // InputConfig.PREVENT_SPLITTING: To keep the default behavior of this window to be
-            // focusable, which allows the system to consume keys when dragging is active. This can
-            // also be used to modify the drag state on key press. For example, cancel drag on
-            // escape key.
             // InputConfig.TRUSTED_OVERLAY: To not block any touches while D&D ongoing and allowing
             // touches to pass through to windows underneath. This allows user to interact with the
             // UI to navigate while dragging.
-            mDragWindowHandle.inputConfig =
-                    InputConfig.PREVENT_SPLITTING | InputConfig.TRUSTED_OVERLAY;
+            mDragWindowHandle.inputConfig = InputConfig.TRUSTED_OVERLAY;
 
             // The drag window cannot receive new touches.
             mDragWindowHandle.touchableRegion.setEmpty();
diff --git a/services/core/java/com/android/server/wm/LaunchParamsPersister.java b/services/core/java/com/android/server/wm/LaunchParamsPersister.java
index bf511adf0..bb50372 100644
--- a/services/core/java/com/android/server/wm/LaunchParamsPersister.java
+++ b/services/core/java/com/android/server/wm/LaunchParamsPersister.java
@@ -263,8 +263,8 @@
         boolean changed = !Objects.equals(params.mDisplayUniqueId, info.uniqueId);
         params.mDisplayUniqueId = info.uniqueId;
 
-        changed |= params.mWindowingMode != task.getWindowingMode();
-        params.mWindowingMode = task.getWindowingMode();
+        changed |= params.mWindowingMode != task.getTaskDisplayArea().getWindowingMode();
+        params.mWindowingMode = task.getTaskDisplayArea().getWindowingMode();
 
         if (task.mLastNonFullscreenBounds != null) {
             changed |= !Objects.equals(params.mBounds, task.mLastNonFullscreenBounds);
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index f355f08..980a941 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -447,6 +447,10 @@
 
     @ScreenOrientation
     int overrideOrientationIfNeeded(@ScreenOrientation int candidate) {
+        // In some cases (e.g. Kids app) we need to map the candidate orientation to some other
+        // orientation.
+        candidate = mActivityRecord.mWmService.mapOrientationRequest(candidate);
+
         if (FALSE.equals(mBooleanPropertyAllowOrientationOverride)) {
             return candidate;
         }
@@ -997,7 +1001,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 +1109,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/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index b2a4df1..fda2125 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -44,7 +44,6 @@
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WALLPAPER;
 import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
-import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
 import static com.android.server.policy.PhoneWindowManager.SYSTEM_DIALOG_REASON_ASSIST;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
@@ -432,13 +431,6 @@
         }
     };
 
-    private static final Consumer<WindowState> sRemoveReplacedWindowsConsumer = w -> {
-        final ActivityRecord activity = w.mActivityRecord;
-        if (activity != null) {
-            activity.removeReplacedWindowIfNeeded(w);
-        }
-    };
-
     RootWindowContainer(WindowManagerService service) {
         super(service);
         mHandler = new MyHandler(service.mH.getLooper());
@@ -662,17 +654,6 @@
         forAllWindows(mCloseSystemDialogsConsumer, false /* traverseTopToBottom */);
     }
 
-    void removeReplacedWindows() {
-        ProtoLog.i(WM_SHOW_TRANSACTIONS, ">>> OPEN TRANSACTION removeReplacedWindows");
-        mWmService.openSurfaceTransaction();
-        try {
-            forAllWindows(sRemoveReplacedWindowsConsumer, true /* traverseTopToBottom */);
-        } finally {
-            mWmService.closeSurfaceTransaction("removeReplacedWindows");
-            ProtoLog.i(WM_SHOW_TRANSACTIONS, "<<< CLOSE TRANSACTION removeReplacedWindows");
-        }
-    }
-
     boolean hasPendingLayoutChanges(WindowAnimator animator) {
         boolean hasChanges = false;
 
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index ce9bff8..78ee6f9 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -232,11 +232,6 @@
     }
 
     @Override
-    public void prepareToReplaceWindows(IBinder appToken, boolean childrenOnly) {
-        mService.setWillReplaceWindows(appToken, childrenOnly);
-    }
-
-    @Override
     public boolean cancelDraw(IWindow window) {
         return mService.cancelDraw(this, window);
     }
@@ -862,7 +857,7 @@
     @Override
     public void grantInputChannel(int displayId, SurfaceControl surface,
             IWindow window, IBinder hostInputToken, int flags, int privateFlags, int type,
-            IBinder windowToken, IBinder focusGrantToken, String inputHandleName,
+            int inputFeatures, IBinder windowToken, IBinder focusGrantToken, String inputHandleName,
             InputChannel outInputChannel) {
         if (hostInputToken == null && !mCanAddInternalSystemWindow) {
             // Callers without INTERNAL_SYSTEM_WINDOW permission cannot grant input channel to
@@ -874,7 +869,7 @@
         try {
             mService.grantInputChannel(this, mUid, mPid, displayId, surface, window, hostInputToken,
                     flags, mCanAddInternalSystemWindow ? privateFlags : 0,
-                    type, windowToken, focusGrantToken, inputHandleName,
+                    type, inputFeatures, windowToken, focusGrantToken, inputHandleName,
                     outInputChannel);
         } finally {
             Binder.restoreCallingIdentity(identity);
@@ -883,11 +878,11 @@
 
     @Override
     public void updateInputChannel(IBinder channelToken, int displayId, SurfaceControl surface,
-            int flags, int privateFlags, Region region) {
+            int flags, int privateFlags, int inputFeatures, Region region) {
         final long identity = Binder.clearCallingIdentity();
         try {
             mService.updateInputChannel(channelToken, displayId, surface, flags,
-                    mCanAddInternalSystemWindow ? privateFlags : 0, region);
+                    mCanAddInternalSystemWindow ? privateFlags : 0, inputFeatures, region);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 969f65c..8c6de8e 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -866,23 +866,8 @@
             return false;
         }
 
-        final int toRootTaskWindowingMode = toRootTask.getWindowingMode();
         final ActivityRecord topActivity = getTopNonFinishingActivity();
 
-        final boolean mightReplaceWindow = topActivity != null
-                && replaceWindowsOnTaskMove(getWindowingMode(), toRootTaskWindowingMode);
-        if (mightReplaceWindow) {
-            // We are about to relaunch the activity because its configuration changed due to
-            // being maximized, i.e. size change. The activity will first remove the old window
-            // and then add a new one. This call will tell window manager about this, so it can
-            // preserve the old window until the new one is drawn. This prevents having a gap
-            // between the removal and addition, in which no window is visible. We also want the
-            // entrance of the new window to be properly animated.
-            // Note here we always set the replacing window first, as the flags might be needed
-            // during the relaunch. If we end up not doing any relaunch, we clear the flags later.
-            windowManager.setWillReplaceWindow(topActivity.token, animate);
-        }
-
         mAtmService.deferWindowLayout();
         boolean kept = true;
         try {
@@ -926,17 +911,10 @@
             mAtmService.continueWindowLayout();
         }
 
-        if (mightReplaceWindow) {
-            // If we didn't actual do a relaunch (indicated by kept==true meaning we kept the old
-            // window), we need to clear the replace window settings. Otherwise, we schedule a
-            // timeout to remove the old window if the replacing window is not coming in time.
-            windowManager.scheduleClearWillReplaceWindows(topActivity.token, !kept);
-        }
-
         if (!deferResume) {
             // The task might have already been running and its visibility needs to be synchronized
             // with the visibility of the root task / windows.
-            root.ensureActivitiesVisible(null, 0, !mightReplaceWindow);
+            root.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
             root.resumeFocusedTasksTopActivities();
         }
 
@@ -947,17 +925,6 @@
         return (preferredRootTask == toRootTask);
     }
 
-    /**
-     * @return {@code true} if the windows of tasks being moved to the target root task from the
-     * source root task should be replaced, meaning that window manager will keep the old window
-     * around until the new is ready.
-     */
-    private static boolean replaceWindowsOnTaskMove(
-            int sourceWindowingMode, int targetWindowingMode) {
-        return sourceWindowingMode == WINDOWING_MODE_FREEFORM
-                || targetWindowingMode == WINDOWING_MODE_FREEFORM;
-    }
-
     void touchActiveTime() {
         lastActiveTime = SystemClock.elapsedRealtime();
     }
@@ -2277,10 +2244,11 @@
             return;
         }
 
-        // Don't persist state if display isn't in freeform mode. Then the task will be launched
-        // back to its last state in a freeform display when it's launched in a freeform display
-        // next time.
-        if (getWindowConfiguration().getDisplayWindowingMode() != WINDOWING_MODE_FREEFORM) {
+        // Don't persist state if Task Display Area isn't in freeform mode. Then the task will be
+        // launched back to its last state in a freeform Task Display Area when it's launched in a
+        // freeform Task Display Area next time.
+        if (getTaskDisplayArea() == null
+                || getTaskDisplayArea().getWindowingMode() != WINDOWING_MODE_FREEFORM) {
             return;
         }
 
@@ -3398,8 +3366,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/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index b131365..93c8c36 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -1166,12 +1166,15 @@
     }
 
     @Override
-    public void setIsIgnoreOrientationRequestDisabled(boolean isDisabled) {
-        enforceTaskPermission("setIsIgnoreOrientationRequestDisabled()");
+    public void setOrientationRequestPolicy(boolean isIgnoreOrientationRequestDisabled,
+            @Nullable int[] fromOrientations, @Nullable int[] toOrientations) {
+        enforceTaskPermission("setOrientationRequestPolicy()");
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
-                mService.mWindowManager.setIsIgnoreOrientationRequestDisabled(isDisabled);
+                mService.mWindowManager
+                        .setOrientationRequestPolicy(isIgnoreOrientationRequestDisabled,
+                                fromOrientations, toOrientations);
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 4e0f120..873a83d 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,52 @@
     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;
+            final Task transientRootTask = activity.getRootTask();
+            // 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() && t != transientRootTask) {
+                        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;
             }
         }
@@ -805,6 +829,7 @@
         }
         mLogger.mFinishTimeNs = SystemClock.elapsedRealtimeNanos();
         mController.mLoggerHandler.post(mLogger::logOnFinish);
+        mController.mTransitionTracer.logFinishedTransition(this);
         // Close the transactions now. They were originally copied to Shell in case we needed to
         // apply them due to a remote failure. Since we don't need to apply them anymore, free them
         // immediately.
@@ -814,6 +839,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 +1014,7 @@
             dc.removeImeSurfaceImmediately();
             dc.handleCompleteDeferredRemoval();
         }
+        validateVisibility();
 
         mState = STATE_FINISHED;
         mController.mTransitionTracer.logState(this);
@@ -995,6 +1030,7 @@
 
         // Handle back animation if it's already started.
         mController.mAtm.mBackNavigationController.handleDeferredBackAnimation(mTargets);
+        mController.mFinishingTransition = null;
     }
 
     void abort() {
@@ -1010,6 +1046,7 @@
         }
         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Aborting Transition: %d", mSyncId);
         mState = STATE_ABORT;
+        mController.mTransitionTracer.logAbortedTransition(this);
         // Syncengine abort will call through to onTransactionReady()
         mSyncEngine.abort(mSyncId);
         mController.dispatchLegacyAppTransitionCancelled();
@@ -1073,12 +1110,13 @@
         // needs to be updated for STATE_ABORT.
         commitVisibleActivities(transaction);
 
+        // Fall-back to the default display if there isn't one participating.
+        final DisplayContent primaryDisplay = !mTargetDisplays.isEmpty() ? mTargetDisplays.get(0)
+                : mController.mAtm.mRootWindowContainer.getDefaultDisplay();
+
         if (mState == STATE_ABORT) {
             mController.abort(this);
-            // Fall-back to the default display if there isn't one participating.
-            final DisplayContent dc = !mTargetDisplays.isEmpty() ? mTargetDisplays.get(0)
-                    : mController.mAtm.mRootWindowContainer.getDefaultDisplay();
-            dc.getPendingTransaction().merge(transaction);
+            primaryDisplay.getPendingTransaction().merge(transaction);
             mSyncId = -1;
             mOverrideOptions = null;
             cleanUpInternal();
@@ -1090,6 +1128,10 @@
         mFinishTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get();
         mController.moveToPlaying(this);
 
+        // Flags must be assigned before calculateTransitionInfo. Otherwise it won't take effect.
+        if (primaryDisplay.isKeyguardLocked()) {
+            mFlags |= TRANSIT_FLAG_KEYGUARD_LOCKED;
+        }
         // Check whether the participants were animated from back navigation.
         final boolean markBackAnimated = mController.mAtm.mBackNavigationController
                 .containsBackAnimationTargets(this);
@@ -1103,9 +1145,6 @@
             final DisplayContent dc = mController.mAtm.mRootWindowContainer.getDisplayContent(
                     info.getRoot(i).getDisplayId());
             mTargetDisplays.add(dc);
-            if (dc.isKeyguardLocked()) {
-                mFlags |= TRANSIT_FLAG_KEYGUARD_LOCKED;
-            }
         }
 
         if (markBackAnimated) {
@@ -1173,14 +1212,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
@@ -1213,8 +1251,7 @@
                         "Calling onTransitionReady: %s", info);
                 mLogger.mSendTimeNs = SystemClock.elapsedRealtimeNanos();
                 mLogger.mInfo = info;
-                mController.mTransitionTracer.logSentTransition(
-                        this, mTargets, mLogger.mCreateTimeNs, mLogger.mSendTimeNs, info);
+                mController.mTransitionTracer.logSentTransition(this, mTargets, info);
                 mController.getTransitionPlayer().onTransitionReady(
                         mToken, info, transaction, mFinishTransaction);
                 if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
@@ -1274,7 +1311,7 @@
         if (mFinishTransaction != null) {
             mFinishTransaction.apply();
         }
-        mController.finishTransition(mToken);
+        mController.finishTransition(this);
     }
 
     private void cleanUpInternal() {
@@ -1888,6 +1925,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 +2183,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 +2288,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..c74f167 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);
@@ -969,6 +976,8 @@
         WindowContainerTransaction mStartWCT;
         int mSyncId;
         TransitionInfo mInfo;
+        ProtoOutputStream mProtoOutputStream = new ProtoOutputStream();
+        long mProtoToken;
 
         private String buildOnSendLog() {
             StringBuilder sb = new StringBuilder("Sent Transition #").append(mSyncId)
diff --git a/services/core/java/com/android/server/wm/TransitionTracer.java b/services/core/java/com/android/server/wm/TransitionTracer.java
index 7b1975d..57c0d65 100644
--- a/services/core/java/com/android/server/wm/TransitionTracer.java
+++ b/services/core/java/com/android/server/wm/TransitionTracer.java
@@ -18,14 +18,6 @@
 
 import static android.os.Build.IS_USER;
 
-import static com.android.server.wm.shell.ChangeInfo.CHANGE_FLAGS;
-import static com.android.server.wm.shell.ChangeInfo.HAS_CHANGED;
-import static com.android.server.wm.shell.ChangeInfo.TRANSIT_MODE;
-import static com.android.server.wm.shell.ChangeInfo.WINDOWING_MODE;
-import static com.android.server.wm.shell.ChangeInfo.WINDOW_IDENTIFIER;
-import static com.android.server.wm.shell.TransitionInfoChange.LAYER_ID;
-import static com.android.server.wm.shell.TransitionInfoChange.MODE;
-import static com.android.server.wm.shell.TransitionState.CHANGE;
 import static com.android.server.wm.shell.TransitionTraceProto.MAGIC_NUMBER;
 import static com.android.server.wm.shell.TransitionTraceProto.MAGIC_NUMBER_H;
 import static com.android.server.wm.shell.TransitionTraceProto.MAGIC_NUMBER_L;
@@ -66,20 +58,67 @@
 
     /**
      * Records key information about a transition that has been sent to Shell to be played.
+     * More information will be appended to the same proto object once the transition is finished or
+     * aborted.
+     * Transition information won't be added to the trace buffer until
+     * {@link #logFinishedTransition} or {@link #logAbortedTransition} is called for this
+     * transition.
+     *
      * @param transition The transition that has been sent to Shell.
      * @param targets Information about the target windows of the transition.
-     * @param createTimeNs System elapsed time (nanoseconds since boot including sleep time) at
- *                     which the transition to be recorded was created.
-     * @param sendTimeNs System elapsed time (nanoseconds since boot including sleep time) at which
-     * @param info
+     * @param info The TransitionInfo send over to Shell to execute the transition.
      */
     public void logSentTransition(Transition transition, ArrayList<ChangeInfo> targets,
-            long createTimeNs, long sendTimeNs, TransitionInfo info) {
-        mTraceBuffer.pushSentTransition(transition, targets, createTimeNs, sendTimeNs);
+            TransitionInfo info) {
+        // Dump the info to proto that will not be available when the transition finishes or
+        // is canceled
+        final ProtoOutputStream outputStream = transition.mLogger.mProtoOutputStream;
+        transition.mLogger.mProtoToken = outputStream
+                .start(com.android.server.wm.shell.TransitionTraceProto.FINISHED_TRANSITIONS);
+        outputStream.write(com.android.server.wm.shell.Transition.START_TRANSACTION_ID,
+                transition.getStartTransaction().getId());
+        outputStream.write(com.android.server.wm.shell.Transition.FINISH_TRANSACTION_ID,
+                transition.getFinishTransaction().getId());
+        dumpTransitionTargetsToProto(outputStream, transition, targets);
+
         logTransitionInfo(transition, info);
     }
 
     /**
+     * Completes the information dumped in {@link #logSentTransition} for a transition
+     * that has finished or aborted, and add the proto object to the trace buffer.
+     *
+     * @param transition The transition that has finished.
+     */
+    public void logFinishedTransition(Transition transition) {
+        if (transition.mLogger.mProtoToken == 0) {
+            // Transition finished but never sent, so open token never added
+            final ProtoOutputStream outputStream = transition.mLogger.mProtoOutputStream;
+            transition.mLogger.mProtoToken = outputStream
+                    .start(com.android.server.wm.shell.TransitionTraceProto.FINISHED_TRANSITIONS);
+        }
+
+        // Dump the rest of the transition's info that wasn't dumped during logSentTransition
+        dumpFinishedTransitionToProto(transition.mLogger.mProtoOutputStream, transition);
+        transition.mLogger.mProtoOutputStream.end(transition.mLogger.mProtoToken);
+        mTraceBuffer.pushTransitionProto(transition.mLogger.mProtoOutputStream);
+    }
+
+    /**
+     * Same as {@link #logFinishedTransition} but don't add the transition to the trace buffer
+     * unless actively tracing.
+     *
+     * @param transition The transition that has been aborted
+     */
+    public void logAbortedTransition(Transition transition) {
+        // We don't care about aborted transitions unless actively tracing
+        if (!mActiveTracingEnabled) {
+            return;
+        }
+        logFinishedTransition(transition);
+    }
+
+    /**
      * Records the current state of a transition in the transition trace (if it is running).
      * @param transition the transition that we want to record the state of.
      */
@@ -87,7 +126,9 @@
         if (!mActiveTracingEnabled) {
             return;
         }
-        mTraceBuffer.pushTransitionState(transition);
+        final ProtoOutputStream outputStream = new ProtoOutputStream();
+        dumpTransitionStateToProto(outputStream, transition);
+        mTraceBuffer.pushTransitionState(outputStream);
     }
 
     /**
@@ -99,171 +140,180 @@
         if (!mActiveTracingEnabled) {
             return;
         }
-        mTraceBuffer.pushTransitionInfo(transition, info);
+        final ProtoOutputStream outputStream = new ProtoOutputStream();
+        dumpTransitionInfoToProto(outputStream, transition, info);
+        mTraceBuffer.pushTransitionInfo(outputStream);
+    }
+
+    private void dumpTransitionTargetsToProto(ProtoOutputStream outputStream,
+            Transition transition, ArrayList<ChangeInfo> targets) {
+        Trace.beginSection("TransitionTracer#dumpTransitionTargetsToProto");
+        if (mActiveTracingEnabled) {
+            outputStream.write(com.android.server.wm.shell.Transition.ID,
+                    transition.getSyncId());
+        }
+
+        outputStream.write(com.android.server.wm.shell.Transition.TYPE, transition.mType);
+
+        for (int i = 0; i < targets.size(); ++i) {
+            final long changeToken = outputStream
+                    .start(com.android.server.wm.shell.Transition.TARGETS);
+
+            final Transition.ChangeInfo target = targets.get(i);
+
+            final int mode = target.getTransitMode(target.mContainer);
+            final int layerId;
+            if (target.mContainer.mSurfaceControl.isValid()) {
+                layerId = target.mContainer.mSurfaceControl.getLayerId();
+            } else {
+                layerId = -1;
+            }
+
+            outputStream.write(com.android.server.wm.shell.Target.MODE, mode);
+            outputStream.write(com.android.server.wm.shell.Target.LAYER_ID, layerId);
+
+            if (mActiveTracingEnabled) {
+                // What we use in the WM trace
+                final int windowId = System.identityHashCode(target.mContainer);
+                outputStream.write(com.android.server.wm.shell.Target.WINDOW_ID, windowId);
+            }
+
+            outputStream.end(changeToken);
+        }
+
+        Trace.endSection();
+    }
+
+    private void dumpFinishedTransitionToProto(
+            ProtoOutputStream outputStream,
+            Transition transition
+    ) {
+        Trace.beginSection("TransitionTracer#dumpFinishedTransitionToProto");
+
+        outputStream.write(com.android.server.wm.shell.Transition.CREATE_TIME_NS,
+                transition.mLogger.mCreateTimeNs);
+        outputStream.write(com.android.server.wm.shell.Transition.SEND_TIME_NS,
+                transition.mLogger.mSendTimeNs);
+        outputStream.write(com.android.server.wm.shell.Transition.FINISH_TIME_NS,
+                transition.mLogger.mFinishTimeNs);
+
+        Trace.endSection();
+    }
+
+    private void dumpTransitionStateToProto(ProtoOutputStream outputStream, Transition transition) {
+        Trace.beginSection("TransitionTracer#dumpTransitionStateToProto");
+
+        final long stateToken = outputStream
+                .start(com.android.server.wm.shell.TransitionTraceProto.TRANSITION_STATES);
+
+        outputStream.write(com.android.server.wm.shell.TransitionState.TIME_NS,
+                SystemClock.elapsedRealtimeNanos());
+        outputStream.write(com.android.server.wm.shell.TransitionState.TRANSITION_ID,
+                transition.getSyncId());
+        outputStream.write(com.android.server.wm.shell.TransitionState.TRANSITION_TYPE,
+                transition.mType);
+        outputStream.write(com.android.server.wm.shell.TransitionState.STATE,
+                transition.getState());
+        outputStream.write(com.android.server.wm.shell.TransitionState.FLAGS,
+                transition.getFlags());
+
+        for (int i = 0; i < transition.mChanges.size(); ++i) {
+            final WindowContainer window = transition.mChanges.keyAt(i);
+            final ChangeInfo changeInfo = transition.mChanges.valueAt(i);
+            dumpChangeInfoToProto(outputStream, window, changeInfo);
+        }
+
+        for (int i = 0; i < transition.mParticipants.size(); ++i) {
+            final WindowContainer window = transition.mParticipants.valueAt(i);
+            window.writeIdentifierToProto(outputStream,
+                    com.android.server.wm.shell.TransitionState.PARTICIPANTS);
+        }
+
+        outputStream.end(stateToken);
+        Trace.endSection();
+    }
+
+    private void dumpChangeInfoToProto(ProtoOutputStream outputStream, WindowContainer window,
+            ChangeInfo changeInfo) {
+        Trace.beginSection("TransitionTraceBuffer#writeChange");
+        final long changeEntryToken =
+                outputStream.start(com.android.server.wm.shell.TransitionState.CHANGE);
+
+        final int transitMode = changeInfo.getTransitMode(window);
+        final boolean hasChanged = changeInfo.hasChanged();
+        final int changeFlags = changeInfo.getChangeFlags(window);
+        final int windowingMode = changeInfo.mWindowingMode;
+
+        outputStream.write(com.android.server.wm.shell.ChangeInfo.TRANSIT_MODE, transitMode);
+        outputStream.write(com.android.server.wm.shell.ChangeInfo.HAS_CHANGED, hasChanged);
+        outputStream.write(com.android.server.wm.shell.ChangeInfo.CHANGE_FLAGS, changeFlags);
+        outputStream.write(com.android.server.wm.shell.ChangeInfo.WINDOWING_MODE, windowingMode);
+        window.writeIdentifierToProto(
+                outputStream, com.android.server.wm.shell.ChangeInfo.WINDOW_IDENTIFIER);
+
+        outputStream.end(changeEntryToken);
+        Trace.endSection();
+    }
+
+    private void dumpTransitionInfoToProto(ProtoOutputStream outputStream,
+            Transition transition, TransitionInfo info) {
+        Trace.beginSection("TransitionTracer#dumpTransitionInfoToProto");
+        final long transitionInfoToken = outputStream
+                .start(com.android.server.wm.shell.TransitionTraceProto.TRANSITION_INFO);
+
+        outputStream.write(com.android.server.wm.shell.TransitionInfo.TRANSITION_ID,
+                transition.getSyncId());
+        for (int i = 0; i < info.getChanges().size(); ++i) {
+            TransitionInfo.Change change = info.getChanges().get(i);
+            dumpTransitionInfoChangeToProto(outputStream, change);
+        }
+
+        outputStream.end(transitionInfoToken);
+        Trace.endSection();
+    }
+
+    private void dumpTransitionInfoChangeToProto(
+            ProtoOutputStream outputStream,
+            TransitionInfo.Change change
+    ) {
+        Trace.beginSection("TransitionTracer#dumpTransitionInfoChangeToProto");
+        final long changeEntryToken = outputStream
+                .start(com.android.server.wm.shell.TransitionInfo.CHANGE);
+
+        outputStream.write(com.android.server.wm.shell.TransitionInfoChange.LAYER_ID,
+                change.getLeash().getLayerId());
+        outputStream.write(com.android.server.wm.shell.TransitionInfoChange.MODE, change.getMode());
+
+        outputStream.end(changeEntryToken);
+        Trace.endSection();
     }
 
     private class TransitionTraceBuffer {
-        private final TraceBuffer mBuffer = new TraceBuffer(ALWAYS_ON_TRACING_CAPACITY);
+        private final TraceBuffer mTransitionBuffer = new TraceBuffer(ALWAYS_ON_TRACING_CAPACITY);
         private final TraceBuffer mStateBuffer = new TraceBuffer(ACTIVE_TRACING_BUFFER_CAPACITY);
         private final TraceBuffer mTransitionInfoBuffer =
                 new TraceBuffer(ACTIVE_TRACING_BUFFER_CAPACITY);
 
-        public void pushSentTransition(
-                Transition transition,
-                ArrayList<ChangeInfo> targets,
-                long createTimeNs,
-                long sendTimeNs
-        ) {
-            Trace.beginSection("TransitionTraceBuffer#pushSentTransition");
-            final ProtoOutputStream outputStream = new ProtoOutputStream();
-            final long transitionToken = outputStream
-                    .start(com.android.server.wm.shell.TransitionTraceProto.SENT_TRANSITIONS);
-
-            if (mActiveTracingEnabled) {
-                outputStream.write(com.android.server.wm.shell.Transition.ID,
-                        transition.getSyncId());
-            }
-
-            outputStream.write(com.android.server.wm.shell.Transition.START_TRANSACTION_ID,
-                    transition.getStartTransaction().getId());
-            outputStream.write(com.android.server.wm.shell.Transition.FINISH_TRANSACTION_ID,
-                    transition.getFinishTransaction().getId());
-
-            outputStream.write(com.android.server.wm.shell.Transition.CREATE_TIME_NS, createTimeNs);
-            outputStream.write(com.android.server.wm.shell.Transition.SEND_TIME_NS, sendTimeNs);
-
-            for (int i = 0; i < targets.size(); ++i) {
-                final long changeToken = outputStream
-                        .start(com.android.server.wm.shell.Transition.TARGETS);
-
-                final Transition.ChangeInfo target = targets.get(i);
-
-                final int mode = target.getTransitMode(target.mContainer);
-                final int layerId;
-                if (target.mContainer.mSurfaceControl.isValid()) {
-                    layerId = target.mContainer.mSurfaceControl.getLayerId();
-                } else {
-                    layerId = -1;
-                }
-
-                outputStream.write(com.android.server.wm.shell.Target.MODE, mode);
-                outputStream.write(com.android.server.wm.shell.Target.LAYER_ID, layerId);
-
-                if (mActiveTracingEnabled) {
-                    // What we use in the WM trace
-                    final int windowId = System.identityHashCode(target.mContainer);
-                    outputStream.write(com.android.server.wm.shell.Target.WINDOW_ID, windowId);
-                }
-
-                outputStream.end(changeToken);
-            }
-
-            outputStream.end(transitionToken);
-            mBuffer.add(outputStream);
-
-            Trace.endSection();
+        private void pushTransitionProto(ProtoOutputStream outputStream) {
+            mTransitionBuffer.add(outputStream);
         }
 
-        private void pushTransitionState(Transition transition) {
-            Trace.beginSection("TransitionTraceBuffer#pushTransitionState");
-            final ProtoOutputStream outputStream = new ProtoOutputStream();
-            final long stateToken = outputStream
-                    .start(com.android.server.wm.shell.TransitionTraceProto.TRANSITION_STATES);
-
-            outputStream.write(com.android.server.wm.shell.TransitionState.TIME_NS,
-                    SystemClock.elapsedRealtimeNanos());
-            outputStream.write(com.android.server.wm.shell.TransitionState.TRANSITION_ID,
-                    transition.getSyncId());
-            outputStream.write(com.android.server.wm.shell.TransitionState.TRANSITION_TYPE,
-                    transition.mType);
-            outputStream.write(com.android.server.wm.shell.TransitionState.STATE,
-                    transition.getState());
-            outputStream.write(com.android.server.wm.shell.TransitionState.FLAGS,
-                    transition.getFlags());
-
-            for (int i = 0; i < transition.mChanges.size(); ++i) {
-                final WindowContainer window = transition.mChanges.keyAt(i);
-                final ChangeInfo changeInfo = transition.mChanges.valueAt(i);
-                writeChange(outputStream, window, changeInfo);
-            }
-
-            for (int i = 0; i < transition.mChanges.size(); ++i) {
-                final WindowContainer window = transition.mChanges.keyAt(i);
-                final ChangeInfo changeInfo = transition.mChanges.valueAt(i);
-                writeChange(outputStream, window, changeInfo);
-            }
-
-            for (int i = 0; i < transition.mParticipants.size(); ++i) {
-                final WindowContainer window = transition.mParticipants.valueAt(i);
-                window.writeIdentifierToProto(outputStream,
-                        com.android.server.wm.shell.TransitionState.PARTICIPANTS);
-            }
-
-            outputStream.end(stateToken);
-
+        private void pushTransitionState(ProtoOutputStream outputStream) {
             mStateBuffer.add(outputStream);
-            Trace.endSection();
         }
 
-        private void pushTransitionInfo(Transition transition, TransitionInfo info) {
-            Trace.beginSection("TransitionTraceBuffer#pushTransitionInfo");
-            final ProtoOutputStream outputStream = new ProtoOutputStream();
-            final long transitionInfoToken = outputStream
-                    .start(com.android.server.wm.shell.TransitionTraceProto.TRANSITION_INFO);
-
-            outputStream.write(com.android.server.wm.shell.TransitionInfo.TRANSITION_ID,
-                    transition.getSyncId());
-            for (int i = 0; i < info.getChanges().size(); ++i) {
-                TransitionInfo.Change change = info.getChanges().get(i);
-                writeTransitionInfoChange(outputStream, change);
-            }
-
-            outputStream.end(transitionInfoToken);
+        private void pushTransitionInfo(ProtoOutputStream outputStream) {
             mTransitionInfoBuffer.add(outputStream);
-            Trace.endSection();
-        }
-
-        private void writeChange(ProtoOutputStream outputStream, WindowContainer window,
-                ChangeInfo changeInfo) {
-            Trace.beginSection("TransitionTraceBuffer#writeChange");
-            final long changeEntryToken = outputStream.start(CHANGE);
-
-            final int transitMode = changeInfo.getTransitMode(window);
-            final boolean hasChanged = changeInfo.hasChanged();
-            final int changeFlags = changeInfo.getChangeFlags(window);
-            final int windowingMode = changeInfo.mWindowingMode;
-
-            outputStream.write(TRANSIT_MODE, transitMode);
-            outputStream.write(HAS_CHANGED, hasChanged);
-            outputStream.write(CHANGE_FLAGS, changeFlags);
-            outputStream.write(WINDOWING_MODE, windowingMode);
-            window.writeIdentifierToProto(outputStream, WINDOW_IDENTIFIER);
-
-            outputStream.end(changeEntryToken);
-            Trace.endSection();
-        }
-
-        private void writeTransitionInfoChange(
-                ProtoOutputStream outputStream,
-                TransitionInfo.Change change
-        ) {
-            Trace.beginSection("TransitionTraceBuffer#writeTransitionInfoChange");
-            final long changeEntryToken = outputStream
-                    .start(com.android.server.wm.shell.TransitionInfo.CHANGE);
-
-            outputStream.write(LAYER_ID, change.getLeash().getLayerId());
-            outputStream.write(MODE, change.getMode());
-
-            outputStream.end(changeEntryToken);
-            Trace.endSection();
         }
 
         public void writeToFile(File file, ProtoOutputStream proto) throws IOException {
-            mBuffer.writeTraceToFile(file, proto);
+            mTransitionBuffer.writeTraceToFile(file, proto);
         }
 
         public void reset() {
-            mBuffer.resetBuffer();
+            mTransitionBuffer.resetBuffer();
+            mStateBuffer.resetBuffer();
+            mTransitionInfoBuffer.resetBuffer();
         }
     }
 
@@ -280,7 +330,7 @@
         LogAndPrintln.i(pw, "Starting shell transition trace.");
         synchronized (mEnabledLock) {
             mActiveTracingEnabled = true;
-            mTraceBuffer.mBuffer.setCapacity(ACTIVE_TRACING_BUFFER_CAPACITY);
+            mTraceBuffer.mTransitionBuffer.setCapacity(ACTIVE_TRACING_BUFFER_CAPACITY);
             mTraceBuffer.reset();
         }
         Trace.endSection();
@@ -309,7 +359,8 @@
         synchronized (mEnabledLock) {
             mActiveTracingEnabled = false;
             writeTraceToFileLocked(pw, outputFile);
-            mTraceBuffer.mBuffer.setCapacity(ALWAYS_ON_TRACING_CAPACITY);
+            mTraceBuffer.reset();
+            mTraceBuffer.mTransitionBuffer.setCapacity(ALWAYS_ON_TRACING_CAPACITY);
         }
         Trace.endSection();
     }
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 2b848d5..0b9ceea 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -163,14 +163,6 @@
         if (DEBUG_WALLPAPER) Slog.v(TAG, "Win " + w + ": isOnScreen=" + w.isOnScreen()
                 + " mDrawState=" + w.mWinAnimator.mDrawState);
 
-        if (w.mWillReplaceWindow && mWallpaperTarget == null
-                && !mFindResults.useTopWallpaperAsTarget) {
-            // When we are replacing a window and there was wallpaper before replacement, we want to
-            // keep the window until the new windows fully appear and can determine the visibility,
-            // to avoid flickering.
-            mFindResults.setUseTopWallpaperAsTarget(true);
-        }
-
         final WindowContainer animatingContainer = w.mActivityRecord != null
                 ? w.mActivityRecord.getAnimatingContainer() : null;
         final boolean keyguardGoingAwayWithWallpaper = (animatingContainer != null
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index c11391e..2596533 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -69,10 +69,6 @@
     SparseArray<DisplayContentsAnimator> mDisplayContentsAnimators = new SparseArray<>(2);
     private boolean mInitialized = false;
 
-    // When set to true the animator will go over all windows after an animation frame is posted and
-    // check if some got replaced and can be removed.
-    private boolean mRemoveReplacedWindows = false;
-
     private Choreographer mChoreographer;
 
     /**
@@ -217,11 +213,6 @@
         mService.closeSurfaceTransaction("WindowAnimator");
         ProtoLog.i(WM_SHOW_TRANSACTIONS, "<<< CLOSE TRANSACTION animate");
 
-        if (mRemoveReplacedWindows) {
-            root.removeReplacedWindows();
-            mRemoveReplacedWindows = false;
-        }
-
         mService.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
         executeAfterPrepareSurfacesRunnables();
 
@@ -286,10 +277,6 @@
         return displayAnimator;
     }
 
-    void requestRemovalOfReplacedWindows(WindowState win) {
-        mRemoveReplacedWindows = true;
-    }
-
     void scheduleAnimation() {
         if (!mAnimationFrameCallbackScheduled) {
             mAnimationFrameCallbackScheduled = true;
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 2f3a70e..969afe5 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -740,7 +740,7 @@
     /**
      * Show IME on imeTargetWindow once IME has finished layout.
      *
-     * @param imeTargetWindowToken token of the (IME target) window on which IME should be shown.
+     * @param imeTargetWindowToken token of the (IME target) window which IME should be shown.
      * @param statsToken the token tracking the current IME show request or {@code null} otherwise.
      */
     public abstract void showImePostLayout(IBinder imeTargetWindowToken,
@@ -749,7 +749,7 @@
     /**
      * Hide IME using imeTargetWindow when requested.
      *
-     * @param imeTargetWindowToken token of the (IME target) window on which IME should be hidden.
+     * @param imeTargetWindowToken token of the (IME target) window on which requests hiding IME.
      * @param displayId the id of the display the IME is on.
      * @param statsToken the token tracking the current IME hide request or {@code null} otherwise.
      */
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 42d23e7..f7641f5 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -88,7 +88,6 @@
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED;
 import static android.view.WindowManager.TRANSIT_NONE;
-import static android.view.WindowManager.TRANSIT_RELAUNCH;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 import static android.view.WindowManager.fixScale;
 import static android.view.WindowManagerGlobal.ADD_OKAY;
@@ -238,6 +237,7 @@
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
 import android.util.TimeUtils;
 import android.util.TypedValue;
 import android.util.proto.ProtoOutputStream;
@@ -382,9 +382,6 @@
     /** Amount of time (in milliseconds) to delay before declaring a window freeze timeout. */
     static final int WINDOW_FREEZE_TIMEOUT_DURATION = 2000;
 
-    /** Amount of time (in milliseconds) to delay before declaring a window replacement timeout. */
-    static final int WINDOW_REPLACEMENT_TIMEOUT_DURATION = 2000;
-
     /** Amount of time to allow a last ANR message to exist before freeing the memory. */
     static final int LAST_ANR_LIFETIME_DURATION_MSECS = 2 * 60 * 60 * 1000; // Two hours
 
@@ -565,12 +562,6 @@
     final WindowManagerGlobalLock mGlobalLock;
 
     /**
-     * List of app window tokens that are waiting for replacing windows. If the
-     * replacement doesn't come in time the stale windows needs to be disposed of.
-     */
-    final ArrayList<ActivityRecord> mWindowReplacementTimeouts = new ArrayList<>();
-
-    /**
      * Windows that are being resized.  Used so we can tell the client about
      * the resize after closing the transaction in which we resized the
      * underlying surface.
@@ -604,6 +595,13 @@
     /** List of window currently causing non-system overlay windows to be hidden. */
     private ArrayList<WindowState> mHidingNonSystemOverlayWindows = new ArrayList<>();
 
+    /**
+     * In some cases (e.g. when {@link R.bool.config_reverseDefaultRotation} has value
+     * {@value true}) we need to map some orientation to others. This {@link SparseIntArray}
+     * contains the relation between the source orientation and the one to use.
+     */
+    private final SparseIntArray mOrientationMapping = new SparseIntArray();
+
     final AccessibilityController mAccessibilityController;
     private RecentsAnimationController mRecentsAnimationController;
 
@@ -1774,15 +1772,6 @@
             final WindowStateAnimator winAnimator = win.mWinAnimator;
             winAnimator.mEnterAnimationPending = true;
             winAnimator.mEnteringAnimation = true;
-            // Check if we need to prepare a transition for replacing window first.
-            if (!win.mTransitionController.isShellTransitionsEnabled()
-                    && activity != null && activity.isVisible()
-                    && !prepareWindowReplacementTransition(activity)) {
-                // If not, check if need to set up a dummy transition during display freeze
-                // so that the unfreeze wait for the apps to draw. This might be needed if
-                // the app is relaunching.
-                prepareNoneTransitionForRelaunching(activity);
-            }
 
             if (displayPolicy.areSystemBarsForcedConsumedLw()) {
                 res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS;
@@ -1944,48 +1933,6 @@
     }
 
     /**
-     * Returns true if we're done setting up any transitions.
-     */
-    private boolean prepareWindowReplacementTransition(ActivityRecord activity) {
-        activity.clearAllDrawn();
-        final WindowState replacedWindow = activity.getReplacingWindow();
-        if (replacedWindow == null) {
-            // We expect to already receive a request to remove the old window. If it did not
-            // happen, let's just simply add a window.
-            return false;
-        }
-        // We use the visible frame, because we want the animation to morph the window from what
-        // was visible to the user to the final destination of the new window.
-        final Rect frame = new Rect(replacedWindow.getFrame());
-        final WindowManager.LayoutParams attrs = replacedWindow.mAttrs;
-        frame.inset(replacedWindow.getInsetsStateWithVisibilityOverride().calculateVisibleInsets(
-                frame, attrs.type, replacedWindow.getWindowingMode(), attrs.softInputMode,
-                attrs.flags));
-        // We treat this as if this activity was opening, so we can trigger the app transition
-        // animation and piggy-back on existing transition animation infrastructure.
-        final DisplayContent dc = activity.getDisplayContent();
-        dc.mOpeningApps.add(activity);
-        dc.prepareAppTransition(TRANSIT_RELAUNCH);
-        dc.mAppTransition.overridePendingAppTransitionClipReveal(frame.left, frame.top,
-                frame.width(), frame.height());
-        dc.executeAppTransition();
-        return true;
-    }
-
-    private void prepareNoneTransitionForRelaunching(ActivityRecord activity) {
-        // Set up a none-transition and add the app to opening apps, so that the display
-        // unfreeze wait for the apps to be drawn.
-        // Note that if the display unfroze already because app unfreeze timed out,
-        // we don't set up the transition anymore and just let it go.
-        final DisplayContent dc = activity.getDisplayContent();
-        if (mDisplayFrozen && !dc.mOpeningApps.contains(activity) && activity.isRelaunching()) {
-            dc.mOpeningApps.add(activity);
-            dc.prepareAppTransition(TRANSIT_NONE);
-            dc.executeAppTransition();
-        }
-    }
-
-    /**
      * Set whether screen capture is disabled for all windows of a specific user from
      * the device policy cache.
      */
@@ -2390,12 +2337,6 @@
 
             // If we are not currently running the exit animation, we need to see about starting
             // one.
-            // We don't want to animate visibility of windows which are pending replacement.
-            // In the case of activity relaunch child windows could request visibility changes as
-            // they are detached from the main application window during the tear down process.
-            // If we satisfied these visibility changes though, we would cause a visual glitch
-            // hiding the window before it's replacement was available. So we just do nothing on
-            // our side.
             // This must be called before the call to performSurfacePlacement.
             if (!shouldRelayout && winAnimator.hasSurface() && !win.mAnimatingExit) {
                 if (DEBUG_VISIBILITY) {
@@ -2403,20 +2344,18 @@
                             "Relayout invis " + win + ": mAnimatingExit=" + win.mAnimatingExit);
                 }
                 result |= RELAYOUT_RES_SURFACE_CHANGED;
-                if (!win.mWillReplaceWindow) {
-                    // When FLAG_SHOW_WALLPAPER flag is removed from a window, we usually set a flag
-                    // in DC#pendingLayoutChanges and update the wallpaper target later.
-                    // However it's possible that FLAG_SHOW_WALLPAPER flag is removed from a window
-                    // when the window is about to exit, so we update the wallpaper target
-                    // immediately here. Otherwise this window will be stuck in exiting and its
-                    // surface remains on the screen.
-                    // TODO(b/189856716): Allow destroying surface even if it belongs to the
-                    //  keyguard target.
-                    if (wallpaperMayMove) {
-                        displayContent.mWallpaperController.adjustWallpaperWindows();
-                    }
-                    tryStartExitingAnimation(win, winAnimator);
+                // When FLAG_SHOW_WALLPAPER flag is removed from a window, we usually set a flag
+                // in DC#pendingLayoutChanges and update the wallpaper target later.
+                // However it's possible that FLAG_SHOW_WALLPAPER flag is removed from a window
+                // when the window is about to exit, so we update the wallpaper target
+                // immediately here. Otherwise this window will be stuck in exiting and its
+                // surface remains on the screen.
+                // TODO(b/189856716): Allow destroying surface even if it belongs to the
+                //  keyguard target.
+                if (wallpaperMayMove) {
+                    displayContent.mWallpaperController.adjustWallpaperWindows();
                 }
+                tryStartExitingAnimation(win, winAnimator);
             }
 
             // Create surfaceControl before surface placement otherwise layout will be skipped
@@ -4180,25 +4119,52 @@
 
     /**
      * Controls whether ignore orientation request logic in {@link DisplayArea} is disabled
-     * at runtime.
+     * at runtime and how to optionally map some requested orientations to others.
      *
      * <p>Note: this assumes that {@link #mGlobalLock} is held by the caller.
      *
-     * @param isDisabled when {@code true}, the system always ignores the value of {@link
-     *                   DisplayArea#getIgnoreOrientationRequest} and app requested orientation is
-     *                   respected.
+     * @param isIgnoreOrientationRequestDisabled when {@code true}, the system always ignores the
+     *                   value of {@link DisplayArea#getIgnoreOrientationRequest} and app requested
+     *                   orientation is respected.
+     * @param fromOrientations The orientations we want to map to the correspondent orientations
+     *                        in toOrientation.
+     * @param toOrientations The orientations we map to the ones in fromOrientations at  the same
+     *                       index
      */
-    void setIsIgnoreOrientationRequestDisabled(boolean isDisabled) {
-        if (isDisabled == mIsIgnoreOrientationRequestDisabled) {
+    void setOrientationRequestPolicy(boolean isIgnoreOrientationRequestDisabled,
+            @Nullable int[] fromOrientations, @Nullable int[] toOrientations) {
+        mOrientationMapping.clear();
+        if (fromOrientations != null && toOrientations != null
+                && fromOrientations.length == toOrientations.length) {
+            for (int i = 0; i < fromOrientations.length; i++) {
+                mOrientationMapping.put(fromOrientations[i], toOrientations[i]);
+            }
+        }
+        if (isIgnoreOrientationRequestDisabled == mIsIgnoreOrientationRequestDisabled) {
             return;
         }
-        mIsIgnoreOrientationRequestDisabled = isDisabled;
+        mIsIgnoreOrientationRequestDisabled = isIgnoreOrientationRequestDisabled;
         for (int i = mRoot.getChildCount() - 1; i >= 0; i--) {
             mRoot.getChildAt(i).onIsIgnoreOrientationRequestDisabledChanged();
         }
     }
 
     /**
+     * When {@link mIsIgnoreOrientationRequestDisabled} is {@value true} this method returns the
+     * orientation to use in place of the one in input. It returns the same requestedOrientation in
+     * input otherwise.
+     *
+     * @param requestedOrientation The orientation that can be mapped.
+     * @return The orientation to use in place of requestedOrientation.
+     */
+    int mapOrientationRequest(int requestedOrientation) {
+        if (!mIsIgnoreOrientationRequestDisabled) {
+            return requestedOrientation;
+        }
+        return mOrientationMapping.get(requestedOrientation, requestedOrientation);
+    }
+
+    /**
      * Whether the system ignores the value of {@link DisplayArea#getIgnoreOrientationRequest} and
      * app requested orientation is respected.
      *
@@ -5328,8 +5294,6 @@
 
         public static final int UPDATE_MULTI_WINDOW_STACKS = 41;
 
-        public static final int WINDOW_REPLACEMENT_TIMEOUT = 46;
-
         public static final int UPDATE_ANIMATION_SCALE = 51;
         public static final int WINDOW_HIDE_TIMEOUT = 52;
         public static final int RESTORE_POINTER_ICON = 55;
@@ -5555,16 +5519,6 @@
                     }
                     break;
                 }
-                case WINDOW_REPLACEMENT_TIMEOUT: {
-                    synchronized (mGlobalLock) {
-                        for (int i = mWindowReplacementTimeouts.size() - 1; i >= 0; i--) {
-                            final ActivityRecord activity = mWindowReplacementTimeouts.get(i);
-                            activity.onWindowReplacementTimeout();
-                        }
-                        mWindowReplacementTimeouts.clear();
-                    }
-                    break;
-                }
                 case WINDOW_HIDE_TIMEOUT: {
                     final WindowState window = (WindowState) msg.obj;
                     synchronized (mGlobalLock) {
@@ -7083,98 +7037,6 @@
         return mGlobalLock;
     }
 
-    /**
-     * Hint to a token that its activity will relaunch, which will trigger removal and addition of
-     * a window.
-     *
-     * @param token Application token for which the activity will be relaunched.
-     */
-    void setWillReplaceWindow(IBinder token, boolean animate) {
-        final ActivityRecord activity = mRoot.getActivityRecord(token);
-        if (activity == null) {
-            ProtoLog.w(WM_ERROR, "Attempted to set replacing window on non-existing app token %s",
-                    token);
-            return;
-        }
-        if (!activity.hasContentToDisplay()) {
-            ProtoLog.w(WM_ERROR,
-                    "Attempted to set replacing window on app token with no content %s",
-                    token);
-            return;
-        }
-        activity.setWillReplaceWindows(animate);
-    }
-
-    /**
-     * Hint to a token that its windows will be replaced across activity relaunch.
-     * The windows would otherwise be removed  shortly following this as the
-     * activity is torn down.
-     * @param token Application token for which the activity will be relaunched.
-     * @param childrenOnly Whether to mark only child windows for replacement
-     *                     (for the case where main windows are being preserved/
-     *                     reused rather than replaced).
-     *
-     */
-    // TODO: The s at the end of the method name is the only difference with the name of the method
-    // above. We should combine them or find better names.
-    void setWillReplaceWindows(IBinder token, boolean childrenOnly) {
-        synchronized (mGlobalLock) {
-            final ActivityRecord activity = mRoot.getActivityRecord(token);
-            if (activity == null) {
-                ProtoLog.w(WM_ERROR,
-                        "Attempted to set replacing window on non-existing app token %s",
-                        token);
-                return;
-            }
-            if (!activity.hasContentToDisplay()) {
-                ProtoLog.w(WM_ERROR,
-                        "Attempted to set replacing window on app token with no content %s",
-                        token);
-                return;
-            }
-
-            if (childrenOnly) {
-                activity.setWillReplaceChildWindows();
-            } else {
-                activity.setWillReplaceWindows(false /* animate */);
-            }
-
-            scheduleClearWillReplaceWindows(token, true /* replacing */);
-        }
-    }
-
-    /**
-     * If we're replacing the window, schedule a timer to clear the replaced window
-     * after a timeout, in case the replacing window is not coming.
-     *
-     * If we're not replacing the window, clear the replace window settings of the app.
-     *
-     * @param token     Application token for the activity whose window might be replaced.
-     * @param replacing Whether the window is being replaced or not.
-     */
-    void scheduleClearWillReplaceWindows(IBinder token, boolean replacing) {
-        final ActivityRecord activity = mRoot.getActivityRecord(token);
-        if (activity == null) {
-            ProtoLog.w(WM_ERROR, "Attempted to reset replacing window on non-existing app token %s",
-                    token);
-            return;
-        }
-        if (replacing) {
-            scheduleWindowReplacementTimeouts(activity);
-        } else {
-            activity.clearWillReplaceWindows();
-        }
-    }
-
-    void scheduleWindowReplacementTimeouts(ActivityRecord activity) {
-        if (!mWindowReplacementTimeouts.contains(activity)) {
-            mWindowReplacementTimeouts.add(activity);
-        }
-        mH.removeMessages(H.WINDOW_REPLACEMENT_TIMEOUT);
-        mH.sendEmptyMessageDelayed(
-                H.WINDOW_REPLACEMENT_TIMEOUT, WINDOW_REPLACEMENT_TIMEOUT_DURATION);
-    }
-
     @Override
     public int getDockedStackSide() {
         return 0;
@@ -8744,8 +8606,8 @@
      */
     void grantInputChannel(Session session, int callingUid, int callingPid, int displayId,
             SurfaceControl surface, IWindow window, IBinder hostInputToken,
-            int flags, int privateFlags, int type, IBinder windowToken, IBinder focusGrantToken,
-            String inputHandleName, InputChannel outInputChannel) {
+            int flags, int privateFlags, int inputFeatures, int type, IBinder windowToken,
+            IBinder focusGrantToken, String inputHandleName, InputChannel outInputChannel) {
         final int sanitizedType = sanitizeWindowType(session, displayId, windowToken, type);
         final InputApplicationHandle applicationHandle;
         final String name;
@@ -8762,7 +8624,7 @@
         }
 
         updateInputChannel(clientChannel.getToken(), callingUid, callingPid, displayId, surface,
-                name, applicationHandle, flags, privateFlags, sanitizedType,
+                name, applicationHandle, flags, privateFlags, inputFeatures, sanitizedType,
                 null /* region */, window);
 
         clientChannel.copyTo(outInputChannel);
@@ -8803,13 +8665,14 @@
     private void updateInputChannel(IBinder channelToken, int callingUid, int callingPid,
             int displayId, SurfaceControl surface, String name,
             InputApplicationHandle applicationHandle, int flags,
-            int privateFlags, int type, Region region, IWindow window) {
+            int privateFlags, int inputFeatures, int type, Region region, IWindow window) {
         final InputWindowHandle h = new InputWindowHandle(applicationHandle, displayId);
         h.token = channelToken;
         h.setWindowToken(window);
         h.name = name;
 
         flags = sanitizeFlagSlippery(flags, name, callingUid, callingPid);
+        inputFeatures = sanitizeSpyWindow(inputFeatures, name, callingUid, callingPid);
 
         final int sanitizedLpFlags =
                 (flags & (FLAG_NOT_TOUCHABLE | FLAG_SLIPPERY | LayoutParams.FLAG_NOT_FOCUSABLE))
@@ -8819,7 +8682,7 @@
 
         // Do not allow any input features to be set without sanitizing them first.
         h.inputConfig = InputConfigAdapter.getInputConfigFromWindowParams(
-                        type, sanitizedLpFlags, 0 /*inputFeatures*/);
+                        type, sanitizedLpFlags, inputFeatures);
 
 
         if ((flags & LayoutParams.FLAG_NOT_FOCUSABLE) != 0) {
@@ -8856,7 +8719,7 @@
      * is undefined.
      */
     void updateInputChannel(IBinder channelToken, int displayId, SurfaceControl surface,
-            int flags, int privateFlags, Region region) {
+            int flags, int privateFlags, int inputFeatures, Region region) {
         final InputApplicationHandle applicationHandle;
         final String name;
         final EmbeddedWindowController.EmbeddedWindow win;
@@ -8871,7 +8734,8 @@
         }
 
         updateInputChannel(channelToken, win.mOwnerUid, win.mOwnerPid, displayId, surface, name,
-                applicationHandle, flags, privateFlags, win.mWindowType, region, win.mClient);
+                applicationHandle, flags, privateFlags, inputFeatures, win.mWindowType, region,
+                win.mClient);
     }
 
     /** Return whether layer tracing is enabled */
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 8652914..17d4f1b 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -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/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index d6c0311..8a083aa 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -59,13 +59,11 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
 import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
@@ -632,22 +630,6 @@
 
     boolean mHasSurface = false;
 
-    // This window will be replaced due to relaunch. This allows window manager
-    // to differentiate between simple removal of a window and replacement. In the latter case it
-    // will preserve the old window until the new one is drawn.
-    boolean mWillReplaceWindow = false;
-    // If true, the replaced window was already requested to be removed.
-    private boolean mReplacingRemoveRequested = false;
-    // Whether the replacement of the window should trigger app transition animation.
-    private boolean mAnimateReplacingWindow = false;
-    // If not null, the window that will be used to replace the old one. This is being set when
-    // the window is added and unset when this window reports its first draw.
-    private WindowState mReplacementWindow = null;
-    // For the new window in the replacement transition, if we have
-    // requested to replace without animation, then we should
-    // make sure we also don't apply an enter animation for
-    // the new window.
-    boolean mSkipEnterAnimationForSeamlessReplacement = false;
     // Whether this window is being moved via the resize API
     private boolean mMovedByResize;
 
@@ -1309,13 +1291,6 @@
     }
 
     boolean skipLayout() {
-        if (mWillReplaceWindow && (mAnimatingExit || !mReplacingRemoveRequested)) {
-            // This window is being replaced and either already got information that it's being
-            // removed or we are still waiting for some information. Because of this we don't
-            // want to apply any more changes to it, so it remains in this state until new window
-            // appears.
-            return true;
-        }
         // Skip layout of the window when in transition to pip mode.
         return mActivityRecord != null && mActivityRecord.mWaitForEnteringPinnedMode;
     }
@@ -2354,24 +2329,6 @@
         }
     }
 
-    void onWindowReplacementTimeout() {
-        if (mWillReplaceWindow) {
-            // Since the window already timed out, remove it immediately now.
-            // Use WindowState#removeImmediately() instead of WindowState#removeIfPossible(), as
-            // the latter delays removal on certain conditions, which will leave the stale window
-            // in the root task and marked mWillReplaceWindow=false, so the window will never be
-            // removed.
-            //
-            // Also removes child windows.
-            removeImmediately();
-        } else {
-            for (int i = mChildren.size() - 1; i >= 0; --i) {
-                final WindowState c = mChildren.get(i);
-                c.onWindowReplacementTimeout();
-            }
-        }
-    }
-
     @Override
     void removeImmediately() {
         if (mRemoved) {
@@ -2388,11 +2345,6 @@
         mWinAnimator.destroySurfaceLocked(getSyncTransaction());
         super.removeImmediately();
 
-        mWillReplaceWindow = false;
-        if (mReplacementWindow != null) {
-            mReplacementWindow.mSkipEnterAnimationForSeamlessReplacement = false;
-        }
-
         final DisplayContent dc = getDisplayContent();
         if (isImeLayeringTarget()) {
             // Remove the attached IME screenshot surface.
@@ -2472,12 +2424,11 @@
             ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
                     "Remove %s: mSurfaceController=%s mAnimatingExit=%b mRemoveOnExit=%b "
                             + "mHasSurface=%b surfaceShowing=%b animating=%b app-animation=%b "
-                            + "mWillReplaceWindow=%b mDisplayFrozen=%b callers=%s",
+                            + "mDisplayFrozen=%b callers=%s",
                     this, mWinAnimator.mSurfaceController, mAnimatingExit, mRemoveOnExit,
                     mHasSurface, mWinAnimator.getShown(),
                     isAnimating(TRANSITION | PARENTS),
                     mActivityRecord != null && mActivityRecord.isAnimating(PARENTS | TRANSITION),
-                    mWillReplaceWindow,
                     mWmService.mDisplayFrozen, Debug.getCallers(6));
 
             // Visibility of the removed window. Will be used later to update orientation later on.
@@ -2487,22 +2438,6 @@
             // window until the animation is done. If the display is frozen, just remove immediately,
             // since the animation wouldn't be seen.
             if (mHasSurface && mToken.okToAnimate()) {
-                if (mWillReplaceWindow) {
-                    // This window is going to be replaced. We need to keep it around until the new one
-                    // gets added, then we will get rid of this one.
-                    ProtoLog.v(WM_DEBUG_ADD_REMOVE,
-                            "Preserving %s until the new one is added", this);
-                    // TODO: We are overloading mAnimatingExit flag to prevent the window state from
-                    // been removed. We probably need another flag to indicate that window removal
-                    // should be deffered vs. overloading the flag that says we are playing an exit
-                    // animation.
-                    ProtoLog.v(WM_DEBUG_ANIM,
-                            "Set animatingExit: reason=remove/replaceWindow win=%s", this);
-                    mAnimatingExit = true;
-                    mReplacingRemoveRequested = true;
-                    return;
-                }
-
                 // If we are not currently running the exit animation, we need to see about starting one
                 wasVisible = isVisible();
 
@@ -2714,53 +2649,6 @@
         mInputWindowHandle.setToken(null);
     }
 
-    /** Returns true if the replacement window was removed. */
-    boolean removeReplacedWindowIfNeeded(WindowState replacement) {
-        if (mWillReplaceWindow && mReplacementWindow == replacement && replacement.hasDrawn()) {
-            replacement.mSkipEnterAnimationForSeamlessReplacement = false;
-            removeReplacedWindow();
-            return true;
-        }
-
-        for (int i = mChildren.size() - 1; i >= 0; --i) {
-            final WindowState c = mChildren.get(i);
-            if (c.removeReplacedWindowIfNeeded(replacement)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private void removeReplacedWindow() {
-        ProtoLog.d(WM_DEBUG_ADD_REMOVE, "Removing replaced window: %s", this);
-        mWillReplaceWindow = false;
-        mAnimateReplacingWindow = false;
-        mReplacingRemoveRequested = false;
-        mReplacementWindow = null;
-        if (mAnimatingExit || !mAnimateReplacingWindow) {
-            removeImmediately();
-        }
-    }
-
-    boolean setReplacementWindowIfNeeded(WindowState replacementCandidate) {
-        boolean replacementSet = false;
-
-        if (mWillReplaceWindow && mReplacementWindow == null
-                && getWindowTag().toString().equals(replacementCandidate.getWindowTag().toString())) {
-
-            mReplacementWindow = replacementCandidate;
-            replacementCandidate.mSkipEnterAnimationForSeamlessReplacement = !mAnimateReplacingWindow;
-            replacementSet = true;
-        }
-
-        for (int i = mChildren.size() - 1; i >= 0; --i) {
-            final WindowState c = mChildren.get(i);
-            replacementSet |= c.setReplacementWindowIfNeeded(replacementCandidate);
-        }
-
-        return replacementSet;
-    }
-
     void setDisplayLayoutNeeded() {
         final DisplayContent dc = getDisplayContent();
         if (dc != null) {
@@ -4391,49 +4279,6 @@
         return parent != null && parent.isGoneForLayout();
     }
 
-    void setWillReplaceWindow(boolean animate) {
-        for (int i = mChildren.size() - 1; i >= 0; i--) {
-            final WindowState c = mChildren.get(i);
-            c.setWillReplaceWindow(animate);
-        }
-
-        if ((mAttrs.privateFlags & PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH) != 0
-                || mAttrs.type == TYPE_APPLICATION_STARTING) {
-            // We don't set replacing on starting windows since they are added by window manager and
-            // not the client so won't be replaced by the client.
-            return;
-        }
-
-        mWillReplaceWindow = true;
-        mReplacementWindow = null;
-        mAnimateReplacingWindow = animate;
-    }
-
-    void clearWillReplaceWindow() {
-        mWillReplaceWindow = false;
-        mReplacementWindow = null;
-        mAnimateReplacingWindow = false;
-
-        for (int i = mChildren.size() - 1; i >= 0; i--) {
-            final WindowState c = mChildren.get(i);
-            c.clearWillReplaceWindow();
-        }
-    }
-
-    boolean waitingForReplacement() {
-        if (mWillReplaceWindow) {
-            return true;
-        }
-
-        for (int i = mChildren.size() - 1; i >= 0; i--) {
-            final WindowState c = mChildren.get(i);
-            if (c.waitingForReplacement()) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     void requestUpdateWallpaperIfNeeded() {
         final DisplayContent dc = getDisplayContent();
         if (dc != null && ((mIsWallpaper && !mLastConfigReportedToClient) || hasWallpaper())) {
@@ -4464,43 +4309,6 @@
         return winY;
     }
 
-    // During activity relaunch due to resize, we sometimes use window replacement
-    // for only child windows (as the main window is handled by window preservation)
-    // and the big surface.
-    //
-    // Though windows of TYPE_APPLICATION or TYPE_DRAWN_APPLICATION (as opposed to
-    // TYPE_BASE_APPLICATION) are not children in the sense of an attached window,
-    // we also want to replace them at such phases, as they won't be covered by window
-    // preservation, and in general we expect them to return following relaunch.
-    boolean shouldBeReplacedWithChildren() {
-        return mIsChildWindow || mAttrs.type == TYPE_APPLICATION
-                || mAttrs.type == TYPE_DRAWN_APPLICATION;
-    }
-
-    void setWillReplaceChildWindows() {
-        if (shouldBeReplacedWithChildren()) {
-            setWillReplaceWindow(false /* animate */);
-        }
-        for (int i = mChildren.size() - 1; i >= 0; i--) {
-            final WindowState c = mChildren.get(i);
-            c.setWillReplaceChildWindows();
-        }
-    }
-
-    WindowState getReplacingWindow() {
-        if (mAnimatingExit && mWillReplaceWindow && mAnimateReplacingWindow) {
-            return this;
-        }
-        for (int i = mChildren.size() - 1; i >= 0; i--) {
-            final WindowState c = mChildren.get(i);
-            final WindowState replacing = c.getReplacingWindow();
-            if (replacing != null) {
-                return replacing;
-            }
-        }
-        return null;
-    }
-
     int getRotationAnimationHint() {
         if (mActivityRecord != null) {
             return mActivityRecord.mRotationAnimationHint;
@@ -4990,14 +4798,11 @@
 
     boolean clearAnimatingFlags() {
         boolean didSomething = false;
-        // We don't want to clear it out for windows that get replaced, because the
-        // animation depends on the flag to remove the replaced window.
-        //
         // We also don't clear the mAnimatingExit flag for windows which have the
         // mRemoveOnExit flag. This indicates an explicit remove request has been issued
         // by the client. We should let animation proceed and not clear this flag or
         // they won't eventually be removed by WindowStateAnimator#finishExit.
-        if (!mWillReplaceWindow && !mRemoveOnExit) {
+        if (!mRemoveOnExit) {
             // Clear mAnimating flag together with mAnimatingExit. When animation
             // changes from exiting to entering, we need to clear this flag until the
             // new animation gets applied, so that isAnimationStarting() becomes true
@@ -5312,7 +5117,7 @@
                 return activity.needsZBoost();
             }
         }
-        return mWillReplaceWindow;
+        return false;
     }
 
     private boolean isStartingWindowAssociatedToTask() {
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index e8625bc..3aac816 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -449,7 +449,6 @@
             if (prepared && mDrawState == HAS_DRAWN) {
                 if (mLastHidden) {
                     mSurfaceController.showRobustly(t);
-                    mAnimator.requestRemovalOfReplacedWindows(w);
                     mLastHidden = false;
                     final DisplayContent displayContent = w.getDisplayContent();
                     if (!displayContent.getLastHasContent()) {
@@ -504,13 +503,6 @@
     }
 
     void applyEnterAnimationLocked() {
-        // If we are the new part of a window replacement transition and we have requested
-        // not to animate, we instead want to make it seamless, so we don't want to apply
-        // an enter transition.
-        if (mWin.mSkipEnterAnimationForSeamlessReplacement) {
-            return;
-        }
-
         final int transit;
         if (mEnterAnimationPending) {
             mEnterAnimationPending = false;
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index c6b7898..327483e 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -315,17 +315,6 @@
         return mChildren.isEmpty();
     }
 
-    WindowState getReplacingWindow() {
-        for (int i = mChildren.size() - 1; i >= 0; i--) {
-            final WindowState win = mChildren.get(i);
-            final WindowState replacing = win.getReplacingWindow();
-            if (replacing != null) {
-                return replacing;
-            }
-        }
-        return null;
-    }
-
     /** Return true if this token has a window that wants the wallpaper displayed behind it. */
     boolean windowsCanBeWallpaperTarget() {
         for (int j = mChildren.size() - 1; j >= 0; j--) {
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index da44da4..a5b1943 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -379,7 +379,7 @@
     jobject mServiceObj;
     sp<Looper> mLooper;
 
-    Mutex mLock;
+    std::mutex mLock;
     struct Locked {
         // Display size information.
         std::vector<DisplayViewport> viewports{};
@@ -469,7 +469,7 @@
         dump += StringPrintf(INDENT "Interactive: %s\n", toString(mInteractive.load()));
     }
     {
-        AutoMutex _l(mLock);
+        std::scoped_lock _l(mLock);
         dump += StringPrintf(INDENT "System UI Lights Out: %s\n",
                              toString(mLocked.systemUiLightsOut));
         dump += StringPrintf(INDENT "Pointer Speed: %" PRId32 "\n", mLocked.pointerSpeed);
@@ -532,7 +532,7 @@
     }
 
     { // acquire lock
-        AutoMutex _l(mLock);
+        std::scoped_lock _l(mLock);
         mLocked.viewports = viewports;
         std::shared_ptr<PointerController> controller = mLocked.pointerController.lock();
         if (controller != nullptr) {
@@ -669,7 +669,7 @@
     }
 
     { // acquire lock
-        AutoMutex _l(mLock);
+        std::scoped_lock _l(mLock);
 
         outConfig->pointerVelocityControlParameters.scale = exp2f(mLocked.pointerSpeed
                 * POINTER_SPEED_EXPONENT);
@@ -717,7 +717,7 @@
 std::shared_ptr<PointerControllerInterface> NativeInputManager::obtainPointerController(
         int32_t /* deviceId */) {
     ATRACE_CALL();
-    AutoMutex _l(mLock);
+    std::scoped_lock _l(mLock);
 
     std::shared_ptr<PointerController> controller = mLocked.pointerController.lock();
     if (controller == nullptr) {
@@ -1065,7 +1065,7 @@
 }
 
 void NativeInputManager::setSystemUiLightsOut(bool lightsOut) {
-    AutoMutex _l(mLock);
+    std::scoped_lock _l(mLock);
 
     if (mLocked.systemUiLightsOut != lightsOut) {
         mLocked.systemUiLightsOut = lightsOut;
@@ -1085,7 +1085,7 @@
 
 void NativeInputManager::setPointerDisplayId(int32_t displayId) {
     { // acquire lock
-        AutoMutex _l(mLock);
+        std::scoped_lock _l(mLock);
 
         if (mLocked.pointerDisplayId == displayId) {
             return;
@@ -1101,7 +1101,7 @@
 
 void NativeInputManager::setPointerSpeed(int32_t speed) {
     { // acquire lock
-        AutoMutex _l(mLock);
+        std::scoped_lock _l(mLock);
 
         if (mLocked.pointerSpeed == speed) {
             return;
@@ -1117,7 +1117,7 @@
 
 void NativeInputManager::setPointerAcceleration(float acceleration) {
     { // acquire lock
-        AutoMutex _l(mLock);
+        std::scoped_lock _l(mLock);
 
         if (mLocked.pointerAcceleration == acceleration) {
             return;
@@ -1133,7 +1133,7 @@
 
 void NativeInputManager::setTouchpadPointerSpeed(int32_t speed) {
     { // acquire lock
-        AutoMutex _l(mLock);
+        std::scoped_lock _l(mLock);
 
         if (mLocked.touchpadPointerSpeed == speed) {
             return;
@@ -1149,7 +1149,7 @@
 
 void NativeInputManager::setTouchpadNaturalScrollingEnabled(bool enabled) {
     { // acquire lock
-        AutoMutex _l(mLock);
+        std::scoped_lock _l(mLock);
 
         if (mLocked.touchpadNaturalScrollingEnabled == enabled) {
             return;
@@ -1165,7 +1165,7 @@
 
 void NativeInputManager::setTouchpadTapToClickEnabled(bool enabled) {
     { // acquire lock
-        AutoMutex _l(mLock);
+        std::scoped_lock _l(mLock);
 
         if (mLocked.touchpadTapToClickEnabled == enabled) {
             return;
@@ -1181,7 +1181,7 @@
 
 void NativeInputManager::setTouchpadRightClickZoneEnabled(bool enabled) {
     { // acquire lock
-        AutoMutex _l(mLock);
+        std::scoped_lock _l(mLock);
 
         if (mLocked.touchpadRightClickZoneEnabled == enabled) {
             return;
@@ -1197,7 +1197,7 @@
 
 void NativeInputManager::setInputDeviceEnabled(uint32_t deviceId, bool enabled) {
     { // acquire lock
-        AutoMutex _l(mLock);
+        std::scoped_lock _l(mLock);
 
         auto it = mLocked.disabledInputDevices.find(deviceId);
         bool currentlyEnabled = it == mLocked.disabledInputDevices.end();
@@ -1215,7 +1215,7 @@
 
 void NativeInputManager::setShowTouches(bool enabled) {
     { // acquire lock
-        AutoMutex _l(mLock);
+        std::scoped_lock _l(mLock);
 
         if (mLocked.showTouches == enabled) {
             return;
@@ -1243,7 +1243,7 @@
 }
 
 void NativeInputManager::setPointerIconType(PointerIconStyle iconId) {
-    AutoMutex _l(mLock);
+    std::scoped_lock _l(mLock);
     std::shared_ptr<PointerController> controller = mLocked.pointerController.lock();
     if (controller != nullptr) {
         controller->updatePointerIcon(iconId);
@@ -1251,7 +1251,7 @@
 }
 
 void NativeInputManager::reloadPointerIcons() {
-    AutoMutex _l(mLock);
+    std::scoped_lock _l(mLock);
     std::shared_ptr<PointerController> controller = mLocked.pointerController.lock();
     if (controller != nullptr) {
         controller->reloadPointerResources();
@@ -1259,7 +1259,7 @@
 }
 
 void NativeInputManager::setCustomPointerIcon(const SpriteIcon& icon) {
-    AutoMutex _l(mLock);
+    std::scoped_lock _l(mLock);
     std::shared_ptr<PointerController> controller = mLocked.pointerController.lock();
     if (controller != nullptr) {
         controller->setCustomPointerIcon(icon);
@@ -1522,7 +1522,7 @@
 
 void NativeInputManager::setPointerCapture(const PointerCaptureRequest& request) {
     { // acquire lock
-        AutoMutex _l(mLock);
+        std::scoped_lock _l(mLock);
 
         if (mLocked.pointerCaptureRequest == request) {
             return;
@@ -1633,7 +1633,7 @@
 
 void NativeInputManager::setStylusButtonMotionEventsEnabled(bool enabled) {
     { // acquire lock
-        AutoMutex _l(mLock);
+        std::scoped_lock _l(mLock);
 
         if (mLocked.stylusButtonMotionEventsEnabled == enabled) {
             return;
@@ -1657,7 +1657,7 @@
 }
 
 FloatPoint NativeInputManager::getMouseCursorPosition() {
-    AutoMutex _l(mLock);
+    std::scoped_lock _l(mLock);
     const auto pc = mLocked.pointerController.lock();
     if (!pc) return {AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION};
 
diff --git a/services/core/jni/gnss/GnssConfiguration.cpp b/services/core/jni/gnss/GnssConfiguration.cpp
index 3677641..b57e451 100644
--- a/services/core/jni/gnss/GnssConfiguration.cpp
+++ b/services/core/jni/gnss/GnssConfiguration.cpp
@@ -67,7 +67,7 @@
       : mIGnssConfiguration(iGnssConfiguration) {}
 
 jobject GnssConfiguration::getVersion(JNIEnv* env) {
-    return createHalInterfaceVersionJavaObject(env, 3, 0);
+    return createHalInterfaceVersionJavaObject(env, 3, mIGnssConfiguration->getInterfaceVersion());
 }
 
 jboolean GnssConfiguration::setEmergencySuplPdn(jint enable) {
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index 793d83e..4f8235a 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -168,7 +168,7 @@
 
     private void respondToClientWithErrorAndFinish(String errorType, String errorMsg) {
         Log.i(TAG, "respondToClientWithErrorAndFinish");
-
+        // TODO add exception bit
         if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
             Log.i(TAG, "Request has already been completed. This is strange.");
             return;
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 4c5c366..85a48d9 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -592,9 +592,13 @@
         }
 
         private void finalizeAndEmitInitialPhaseMetric(RequestSession session) {
-            var initMetric = session.mInitialPhaseMetric;
-            initMetric.setCredentialServiceBeginQueryTimeNanoseconds(System.nanoTime());
-            MetricUtilities.logApiCalled(initMetric);
+            try {
+                var initMetric = session.mInitialPhaseMetric;
+                initMetric.setCredentialServiceBeginQueryTimeNanoseconds(System.nanoTime());
+                MetricUtilities.logApiCalled(initMetric);
+            } catch (Exception e) {
+                Log.w(TAG, "Unexpected error during metric logging: " + e);
+            }
         }
 
         @Override
diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
index ed139b5..99f3b3e 100644
--- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java
+++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
@@ -81,6 +81,34 @@
     }
 
     /**
+     * A logging utility used primarily for the candidate phase of the current metric setup.
+     *
+     * @param providers            a map with known providers
+     * @param emitSequenceId       an emitted sequence id for the current session
+     */
+    protected static void logApiCalled(Map<String, ProviderSession> providers,
+            int emitSequenceId) {
+        try {
+            var providerSessions = providers.values();
+            int providerSize = providerSessions.size();
+            int[] candidateUidList = new int[providerSize];
+            int[] candidateQueryRoundTripTimeList = new int[providerSize];
+            int[] candidateStatusList = new int[providerSize];
+            int index = 0;
+            for (var session : providerSessions) {
+                CandidatePhaseMetric metric = session.mCandidatePhasePerProviderMetric;
+                candidateUidList[index] = metric.getCandidateUid();
+                candidateQueryRoundTripTimeList[index] = metric.getQueryLatencyMicroseconds();
+                candidateStatusList[index] = metric.getProviderQueryStatus();
+                index++;
+            }
+            // TODO Handle the emit here
+        } catch (Exception e) {
+            Log.w(TAG, "Unexpected error during metric logging: " + e);
+        }
+    }
+
+    /**
      * The most common logging helper, handles the overall status of the API request with the
      * provider status and latencies. Other versions of this method may be more useful depending
      * on the situation, as this is geared towards the logging of {@link ProviderSession} types.
@@ -90,6 +118,7 @@
      * @param providers            a map with known providers
      * @param callingUid           the calling UID of the client app
      * @param chosenProviderFinalPhaseMetric the metric data type of the final chosen provider
+     * TODO remove soon
      */
     protected static void logApiCalled(ApiName apiName, ApiStatus apiStatus,
             Map<String, ProviderSession> providers, int callingUid,
@@ -133,6 +162,7 @@
      * contain default values for all other optional parameters.
      *
      * TODO(b/271135048) - given space requirements, this may be a good candidate for another atom
+     * TODO immediately remove and carry over TODO to new log for this setup
      *
      * @param apiName    the api name to log
      * @param apiStatus  the status to log
diff --git a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
index 950cf4f..b86daba 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
@@ -33,7 +33,7 @@
  *
  * @hide
  */
-public final class  ProviderClearSession extends ProviderSession<ClearCredentialStateRequest,
+public final class ProviderClearSession extends ProviderSession<ClearCredentialStateRequest,
         Void>
         implements
         RemoteCredentialService.ProviderCallbacks<Void> {
@@ -42,7 +42,8 @@
     private ClearCredentialStateException mProviderException;
 
     /** Creates a new provider session to be used by the request session. */
-    @Nullable public static ProviderClearSession createNewSession(
+    @Nullable
+    public static ProviderClearSession createNewSession(
             Context context,
             @UserIdInt int userId,
             CredentialProviderInfo providerInfo,
@@ -53,7 +54,7 @@
                         clearRequestSession.mClientRequest,
                         clearRequestSession.mClientAppInfo);
         return new ProviderClearSession(context, providerInfo, clearRequestSession, userId,
-                    remoteCredentialService, providerRequest);
+                remoteCredentialService, providerRequest);
     }
 
     @Nullable
@@ -90,6 +91,7 @@
         if (exception instanceof ClearCredentialStateException) {
             mProviderException = (ClearCredentialStateException) exception;
         }
+        captureCandidateFailure();
         updateStatusAndInvokeCallback(toStatus(errorCode));
     }
 
@@ -120,14 +122,7 @@
     @Override
     protected void invokeSession() {
         if (mRemoteCredentialService != null) {
-            /*
-            InitialPhaseMetric initMetric = ((RequestSession)mCallbacks).initMetric;
-            TODO immediately once the other change patched through
-            mCandidateProviderMetric.setSessionId(initMetric
-            .mInitialPhaseMetric.getSessionId());
-            mCandidateProviderMetric.setStartTime(initMetric.getStartTime())
-             */
-            mCandidatePhasePerProviderMetric.setStartQueryTimeNanoseconds(System.nanoTime());
+            startCandidateMetrics();
             mRemoteCredentialService.onClearCredentialState(mProviderRequest, this);
         }
     }
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
index 3ec0fc0..bbbb156 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -40,6 +40,8 @@
 import android.util.Pair;
 import android.util.Slog;
 
+import com.android.server.credentials.metrics.EntryEnum;
+
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -65,7 +67,8 @@
     private final ProviderResponseDataHandler mProviderResponseDataHandler;
 
     /** Creates a new provider session to be used by the request session. */
-    @Nullable public static ProviderCreateSession createNewSession(
+    @Nullable
+    public static ProviderCreateSession createNewSession(
             Context context,
             @UserIdInt int userId,
             CredentialProviderInfo providerInfo,
@@ -155,6 +158,7 @@
             // Store query phase exception for aggregation with final response
             mProviderException = (CreateCredentialException) exception;
         }
+        captureCandidateFailure();
         updateStatusAndInvokeCallback(toStatus(errorCode));
     }
 
@@ -175,14 +179,32 @@
         mProviderResponseDataHandler.addResponseContent(response.getCreateEntries(),
                 response.getRemoteCreateEntry());
         if (mProviderResponseDataHandler.isEmptyResponse(response)) {
+            gatheCandidateEntryMetrics(response);
             updateStatusAndInvokeCallback(Status.EMPTY_RESPONSE);
         } else {
+            gatheCandidateEntryMetrics(response);
             updateStatusAndInvokeCallback(Status.SAVE_ENTRIES_RECEIVED);
         }
     }
 
+    private void gatheCandidateEntryMetrics(BeginCreateCredentialResponse response) {
+        try {
+            var createEntries = response.getCreateEntries();
+            int numCreateEntries = createEntries == null ? 0 : createEntries.size();
+            // TODO confirm how to get types from slice
+            if (numCreateEntries > 0) {
+                createEntries.forEach(c ->
+                        mCandidatePhasePerProviderMetric.addEntry(EntryEnum.CREDENTIAL_ENTRY));
+            }
+            mCandidatePhasePerProviderMetric.setNumEntriesTotal(numCreateEntries);
+        } catch (Exception e) {
+            Log.w(TAG, "Unexpected error during metric logging: " + e);
+        }
+    }
+
     @Override
-    @Nullable protected CreateCredentialProviderData prepareUiData()
+    @Nullable
+    protected CreateCredentialProviderData prepareUiData()
             throws IllegalArgumentException {
         Log.i(TAG, "In prepareUiData");
         if (!ProviderSession.isUiInvokingStatus(getStatus())) {
@@ -226,14 +248,7 @@
     @Override
     protected void invokeSession() {
         if (mRemoteCredentialService != null) {
-            /*
-            InitialPhaseMetric initMetric = ((RequestSession)mCallbacks).initMetric;
-            TODO immediately once the other change patched through
-            mCandidateProviderMetric.setSessionId(initMetric
-            .mInitialPhaseMetric.getSessionId());
-            mCandidateProviderMetric.setStartTime(initMetric.getStartTime())
-             */
-            mCandidatePhasePerProviderMetric.setStartQueryTimeNanoseconds(System.nanoTime());
+            startCandidateMetrics();
             mRemoteCredentialService.onCreateCredential(mProviderRequest, this);
         }
     }
@@ -305,12 +320,14 @@
     }
 
     private class ProviderResponseDataHandler {
-        @Nullable private final ComponentName mExpectedRemoteEntryProviderService;
+        @Nullable
+        private final ComponentName mExpectedRemoteEntryProviderService;
 
         @NonNull
         private final Map<String, Pair<CreateEntry, Entry>> mUiCreateEntries = new HashMap<>();
 
-        @Nullable private Pair<String, Pair<RemoteEntry, Entry>> mUiRemoteEntry = null;
+        @Nullable
+        private Pair<String, Pair<RemoteEntry, Entry>> mUiRemoteEntry = null;
 
         ProviderResponseDataHandler(@Nullable ComponentName expectedRemoteEntryProviderService) {
             mExpectedRemoteEntryProviderService = expectedRemoteEntryProviderService;
@@ -323,6 +340,7 @@
                 setRemoteEntry(remoteEntry);
             }
         }
+
         public void addCreateEntry(CreateEntry createEntry) {
             String id = generateUniqueId();
             Entry entry = new Entry(SAVE_ENTRY_KEY,
@@ -373,6 +391,7 @@
         private boolean isEmptyResponse() {
             return mUiCreateEntries.isEmpty() && mUiRemoteEntry == null;
         }
+
         @Nullable
         public RemoteEntry getRemoteEntry(String entryKey) {
             return mUiRemoteEntry == null || mUiRemoteEntry
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index ec8bf22..bf1db37 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -43,6 +43,8 @@
 import android.util.Pair;
 import android.util.Slog;
 
+import com.android.server.credentials.metrics.EntryEnum;
+
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -82,7 +84,8 @@
     private final ProviderResponseDataHandler mProviderResponseDataHandler;
 
     /** Creates a new provider session to be used by the request session. */
-    @Nullable public static ProviderGetSession createNewSession(
+    @Nullable
+    public static ProviderGetSession createNewSession(
             Context context,
             @UserIdInt int userId,
             CredentialProviderInfo providerInfo,
@@ -113,6 +116,7 @@
         Log.i(TAG, "Unable to create provider session");
         return null;
     }
+
     private static BeginGetCredentialRequest constructQueryPhaseRequest(
             android.credentials.GetCredentialRequest filteredRequest,
             CallingAppInfo callingAppInfo,
@@ -169,7 +173,7 @@
             CallingAppInfo callingAppInfo,
             Map<String, CredentialOption> beginGetOptionToCredentialOptionMap,
             String hybridService) {
-        super(context, beginGetRequest, callbacks, info.getComponentName() ,
+        super(context, beginGetRequest, callbacks, info.getComponentName(),
                 userId, remoteCredentialService);
         mCompleteRequest = completeGetRequest;
         mCallingAppInfo = callingAppInfo;
@@ -191,6 +195,7 @@
         if (exception instanceof GetCredentialException) {
             mProviderException = (GetCredentialException) exception;
         }
+        captureCandidateFailure();
         updateStatusAndInvokeCallback(toStatus(errorCode));
     }
 
@@ -269,20 +274,14 @@
     @Override
     protected void invokeSession() {
         if (mRemoteCredentialService != null) {
-            /*
-            InitialPhaseMetric initMetric = ((RequestSession)mCallbacks).initMetric;
-            TODO immediately once the other change patched through
-            mCandidateProviderMetric.setSessionId(initMetric
-            .mInitialPhaseMetric.getSessionId());
-            mCandidateProviderMetric.setStartTime(initMetric.getStartTime())
-             */
-            mCandidatePhasePerProviderMetric.setStartQueryTimeNanoseconds(System.nanoTime());
+            startCandidateMetrics();
             mRemoteCredentialService.onBeginGetCredential(mProviderRequest, this);
         }
     }
 
     @Override // Call from request session to data to be shown on the UI
-    @Nullable protected GetCredentialProviderData prepareUiData() throws IllegalArgumentException {
+    @Nullable
+    protected GetCredentialProviderData prepareUiData() throws IllegalArgumentException {
         Log.i(TAG, "In prepareUiData");
         if (!ProviderSession.isUiInvokingStatus(getStatus())) {
             Log.i(TAG, "In prepareUiData - provider does not want to show UI: "
@@ -389,6 +388,7 @@
         GetCredentialException exception = maybeGetPendingIntentException(
                 providerPendingIntentResponse);
         if (exception != null) {
+            // TODO (b/271135048), for AuthenticationEntry callback selection, set error
             invokeCallbackWithError(exception.getType(),
                     exception.getMessage());
             // Additional content received is in the form of an exception which ends the flow.
@@ -439,11 +439,34 @@
             updateStatusAndInvokeCallback(Status.EMPTY_RESPONSE);
             return;
         }
-        // TODO immediately, add to Candidate Phase counts, repeat across all sessions
-        // Use sets to dedup type counts
+        gatherCandidateEntryMetrics(response);
         updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED);
     }
 
+    private void gatherCandidateEntryMetrics(BeginGetCredentialResponse response) {
+        try {
+            int numCredEntries = response.getCredentialEntries().size();
+            int numActionEntries = response.getActions().size();
+            int numAuthEntries = response.getAuthenticationActions().size();
+            // TODO immediately add remote entries
+            // TODO immediately confirm how to get types from slice to get unique type count via
+            //  dedupe
+            response.getCredentialEntries().forEach(c ->
+                    mCandidatePhasePerProviderMetric.addEntry(EntryEnum.CREDENTIAL_ENTRY));
+            response.getActions().forEach(c ->
+                    mCandidatePhasePerProviderMetric.addEntry(EntryEnum.ACTION_ENTRY));
+            response.getAuthenticationActions().forEach(c ->
+                    mCandidatePhasePerProviderMetric.addEntry(EntryEnum.AUTHENTICATION_ENTRY));
+            mCandidatePhasePerProviderMetric.setNumEntriesTotal(numCredEntries + numAuthEntries
+                    + numActionEntries);
+            mCandidatePhasePerProviderMetric.setCredentialEntryCount(numCredEntries);
+            mCandidatePhasePerProviderMetric.setActionEntryCount(numActionEntries);
+            mCandidatePhasePerProviderMetric.setAuthenticationEntryCount(numAuthEntries);
+        } catch (Exception e) {
+            Log.w(TAG, "Unexpected error during metric logging: " + e);
+        }
+    }
+
     /**
      * When an invalid state occurs, e.g. entry mismatch or no response from provider,
      * we send back a TYPE_NO_CREDENTIAL error as to the developer.
@@ -471,11 +494,12 @@
                                 .STATUS_UNLOCKED_BUT_EMPTY_LESS_RECENT
                                 || e.second.getStatus()
                                 == AuthenticationEntry.STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT
-        );
+                );
     }
 
     private class ProviderResponseDataHandler {
-        @Nullable private final ComponentName mExpectedRemoteEntryProviderService;
+        @Nullable
+        private final ComponentName mExpectedRemoteEntryProviderService;
         @NonNull
         private final Map<String, Pair<CredentialEntry, Entry>> mUiCredentialEntries =
                 new HashMap<>();
@@ -485,7 +509,8 @@
         private final Map<String, Pair<Action, AuthenticationEntry>> mUiAuthenticationEntries =
                 new HashMap<>();
 
-        @Nullable private Pair<String, Pair<RemoteEntry, Entry>> mUiRemoteEntry = null;
+        @Nullable
+        private Pair<String, Pair<RemoteEntry, Entry>> mUiRemoteEntry = null;
 
         ProviderResponseDataHandler(@Nullable ComponentName expectedRemoteEntryProviderService) {
             mExpectedRemoteEntryProviderService = expectedRemoteEntryProviderService;
@@ -509,6 +534,7 @@
                 setRemoteEntry(remoteEntry);
             }
         }
+
         public void addCredentialEntry(CredentialEntry credentialEntry) {
             String id = generateUniqueId();
             Entry entry = new Entry(CREDENTIAL_ENTRY_KEY,
@@ -559,7 +585,6 @@
         }
 
 
-
         public GetCredentialProviderData toGetCredentialProviderData() {
             return new GetCredentialProviderData.Builder(
                     mComponentName.flattenToString()).setActionChips(prepareActionEntries())
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index 77d4e77..faa91dc 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -32,6 +32,7 @@
 import android.util.Log;
 
 import com.android.server.credentials.metrics.CandidatePhaseMetric;
+import com.android.server.credentials.metrics.InitialPhaseMetric;
 import com.android.server.credentials.metrics.ProviderStatusForMetrics;
 
 import java.util.UUID;
@@ -72,8 +73,9 @@
     @NonNull
     protected Boolean mProviderResponseSet = false;
     // Specific candidate provider metric for the provider this session handles
-    @Nullable
-    protected CandidatePhaseMetric mCandidatePhasePerProviderMetric;
+    @NonNull
+    protected final CandidatePhaseMetric mCandidatePhasePerProviderMetric =
+            new CandidatePhaseMetric();
     @NonNull
     private int mProviderSessionUid;
 
@@ -143,7 +145,6 @@
         mUserId = userId;
         mComponentName = componentName;
         mRemoteCredentialService = remoteCredentialService;
-        mCandidatePhasePerProviderMetric = new CandidatePhaseMetric();
         mProviderSessionUid = MetricUtilities.getPackageUid(mContext, mComponentName);
     }
 
@@ -208,6 +209,12 @@
         return mRemoteCredentialService;
     }
 
+    protected void captureCandidateFailure() {
+        mCandidatePhasePerProviderMetric.setHasException(true);
+        // TODO(b/271135048) - this is a true exception, but what about the empty case?
+        // Add more nuance in next iteration.
+    }
+
     /** Updates the status . */
     protected void updateStatusAndInvokeCallback(@NonNull Status status) {
         setStatus(status);
@@ -216,18 +223,37 @@
     }
 
     private void updateCandidateMetric(Status status) {
-        mCandidatePhasePerProviderMetric.setCandidateUid(mProviderSessionUid);
-        // TODO immediately update the candidate phase here to have more new data
-        mCandidatePhasePerProviderMetric
-                .setQueryFinishTimeNanoseconds(System.nanoTime());
-        if (isTerminatingStatus(status)) {
-            mCandidatePhasePerProviderMetric.setProviderQueryStatus(
-                    ProviderStatusForMetrics.QUERY_FAILURE
-                            .getMetricCode());
-        } else if (isCompletionStatus(status)) {
-            mCandidatePhasePerProviderMetric.setProviderQueryStatus(
-                    ProviderStatusForMetrics.QUERY_SUCCESS
-                            .getMetricCode());
+        try {
+            mCandidatePhasePerProviderMetric.setCandidateUid(mProviderSessionUid);
+            // TODO immediately update the candidate phase here to have more new data
+            mCandidatePhasePerProviderMetric
+                    .setQueryFinishTimeNanoseconds(System.nanoTime());
+            if (isTerminatingStatus(status)) {
+                mCandidatePhasePerProviderMetric.setQueryReturned(false);
+                mCandidatePhasePerProviderMetric.setProviderQueryStatus(
+                        ProviderStatusForMetrics.QUERY_FAILURE
+                                .getMetricCode());
+            } else if (isCompletionStatus(status)) {
+                mCandidatePhasePerProviderMetric.setQueryReturned(true);
+                mCandidatePhasePerProviderMetric.setProviderQueryStatus(
+                        ProviderStatusForMetrics.QUERY_SUCCESS
+                                .getMetricCode());
+            }
+        } catch (Exception e) {
+            Log.w(TAG, "Unexpected error during metric logging: " + e);
+        }
+    }
+
+    // Common method to transfer metrics from the initial phase to the candidate phase per provider
+    protected void startCandidateMetrics() {
+        try {
+            InitialPhaseMetric initMetric = ((RequestSession) mCallbacks).mInitialPhaseMetric;
+            mCandidatePhasePerProviderMetric.setSessionId(initMetric.getSessionId());
+            mCandidatePhasePerProviderMetric.setServiceBeganTimeNanoseconds(
+                    initMetric.getCredentialServiceStartedTimeNanoseconds());
+            mCandidatePhasePerProviderMetric.setStartQueryTimeNanoseconds(System.nanoTime());
+        } catch (Exception e) {
+            Log.w(TAG, "Unexpected error during metric logging: " + e);
         }
     }
 
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index 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..303de12 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -77,6 +77,7 @@
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_WIFI;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_WINDOWS;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_WIPE_DATA;
+import static android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS;
 import static android.Manifest.permission.QUERY_ADMIN_POLICY;
 import static android.Manifest.permission.REQUEST_PASSWORD_COMPLEXITY;
 import static android.Manifest.permission.SET_TIME;
@@ -95,6 +96,7 @@
 import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE;
 import static android.app.admin.DevicePolicyIdentifiers.AUTO_TIMEZONE_POLICY;
 import static android.app.admin.DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE;
+import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_FINANCING_STATE_CHANGED;
 import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED;
 import static android.app.admin.DevicePolicyManager.ACTION_MANAGED_PROFILE_PROVISIONED;
 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE;
@@ -3224,7 +3226,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));
@@ -3578,6 +3580,8 @@
         mInjector.binderWithCleanCallingIdentity(() ->
                 mInjector.getPackageManagerInternal().setOwnerProtectedPackages(
                         targetUserId, protectedPackages));
+        mUsageStatsManagerInternal.setAdminProtectedPackages(new ArraySet(protectedPackages),
+                targetUserId);
     }
 
     void handleUnlockUser(int userId) {
@@ -3964,7 +3968,7 @@
         }
         Objects.requireNonNull(adminReceiver, "ComponentName is null");
         Preconditions.checkCallAuthorization(isAdb(getCallerIdentity())
-                        || hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS),
+                        || hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS),
                 "Caller must be shell or hold MANAGE_PROFILE_AND_DEVICE_OWNERS to call "
                         + "forceRemoveActiveAdmin");
         mInjector.binderWithCleanCallingIdentity(() -> {
@@ -9478,7 +9482,7 @@
         Preconditions.checkCallAuthorization(
                 isDefaultDeviceOwner(caller) || canManageUsers(caller) || isFinancedDeviceOwner(
                         caller) || hasCallingOrSelfPermission(
-                        permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+                        MANAGE_PROFILE_AND_DEVICE_OWNERS));
         return mOwners.hasDeviceOwner();
     }
 
@@ -9647,7 +9651,7 @@
         }
         if (!callingUserOnly) {
             Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity())
-                    || hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+                    || hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
         }
         synchronized (getLockObject()) {
             if (!mOwners.hasDeviceOwner()) {
@@ -9697,7 +9701,7 @@
             return null;
         }
         Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity())
-                || hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+                || hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
 
         synchronized (getLockObject()) {
             if (!mOwners.hasDeviceOwner()) {
@@ -10101,7 +10105,7 @@
         }
         final CallerIdentity caller = getCallerIdentity();
         Preconditions.checkCallAuthorization(canManageUsers(caller)
-                || hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+                || hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
 
         if (userHandle != caller.getUserId()) {
             Preconditions.checkCallAuthorization(canManageUsers(caller)
@@ -10119,7 +10123,7 @@
             return;
         }
         Preconditions.checkCallAuthorization(
-                hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+                hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
 
         final CallerIdentity caller = getCallerIdentity();
         final long id = mInjector.binderClearCallingIdentity();
@@ -10432,7 +10436,7 @@
             return null;
         }
         Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity())
-                || hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+                || hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
         return getProfileOwnerNameUnchecked(userHandle);
     }
 
@@ -10639,7 +10643,7 @@
             return;
         }
         Preconditions.checkCallAuthorization(
-                hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+                hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
 
         if ((mIsWatch || hasUserSetupCompleted(userHandle))) {
             Preconditions.checkState(isSystemUid(caller),
@@ -10663,7 +10667,7 @@
             boolean hasIncompatibleAccountsOrNonAdb) {
         if (!isAdb(caller)) {
             Preconditions.checkCallAuthorization(
-                    hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+                    hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
         }
 
         final int code = checkDeviceOwnerProvisioningPreConditionLocked(owner,
@@ -11776,6 +11780,20 @@
         final CallerIdentity caller = getCallerIdentity();
         Preconditions.checkCallAuthorization(canManageUsers(caller) || canQueryAdminPolicy(caller));
 
+        // Move AccessibilityManager out of lock to prevent potential deadlock
+        final List<AccessibilityServiceInfo> installedServices;
+        long id = mInjector.binderClearCallingIdentity();
+        try {
+            UserInfo user = getUserInfo(userId);
+            if (user.isManagedProfile()) {
+                userId = user.profileGroupId;
+            }
+            installedServices = withAccessibilityManager(userId,
+                    AccessibilityManager::getInstalledAccessibilityServiceList);
+        } finally {
+            mInjector.binderRestoreCallingIdentity(id);
+        }
+
         synchronized (getLockObject()) {
             List<String> result = null;
             // If we have multiple profiles we return the intersection of the
@@ -11802,27 +11820,14 @@
 
             // If we have a permitted list add all system accessibility services.
             if (result != null) {
-                long id = mInjector.binderClearCallingIdentity();
-                try {
-                    UserInfo user = getUserInfo(userId);
-                    if (user.isManagedProfile()) {
-                        userId = user.profileGroupId;
-                    }
-                    final List<AccessibilityServiceInfo> installedServices =
-                            withAccessibilityManager(userId,
-                                    AccessibilityManager::getInstalledAccessibilityServiceList);
-
-                    if (installedServices != null) {
-                        for (AccessibilityServiceInfo service : installedServices) {
-                            ServiceInfo serviceInfo = service.getResolveInfo().serviceInfo;
-                            ApplicationInfo applicationInfo = serviceInfo.applicationInfo;
-                            if ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
-                                result.add(serviceInfo.packageName);
-                            }
+                if (installedServices != null) {
+                    for (AccessibilityServiceInfo service : installedServices) {
+                        ServiceInfo serviceInfo = service.getResolveInfo().serviceInfo;
+                        ApplicationInfo applicationInfo = serviceInfo.applicationInfo;
+                        if ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+                            result.add(serviceInfo.packageName);
                         }
                     }
-                } finally {
-                    mInjector.binderRestoreCallingIdentity(id);
                 }
             }
 
@@ -15440,7 +15445,8 @@
             Slogf.i(LOG_TAG, "Sending %s broadcast to manifest receivers.", intent.getAction());
             broadcastIntentToCrossProfileManifestReceivers(
                     intent, parentHandle, requiresPermission);
-            broadcastIntentToDevicePolicyManagerRoleHolder(intent, parentHandle);
+            broadcastExplicitIntentToRoleHolder(
+                    intent, RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT, parentHandle);
         }
 
         @Override
@@ -15481,36 +15487,6 @@
             }
         }
 
-        private void broadcastIntentToDevicePolicyManagerRoleHolder(
-                Intent intent, UserHandle userHandle) {
-            final int userId = userHandle.getIdentifier();
-            final String packageName = getDevicePolicyManagementRoleHolderPackageName(mContext);
-            if (packageName == null) {
-                return;
-            }
-            try {
-                final Intent packageIntent = new Intent(intent)
-                        .setPackage(packageName);
-                final List<ResolveInfo> receivers = mIPackageManager.queryIntentReceivers(
-                        packageIntent,
-                        /* resolvedType= */ null,
-                        STOCK_PM_FLAGS,
-                        userId).getList();
-                if (receivers.isEmpty()) {
-                    return;
-                }
-                for (ResolveInfo receiver : receivers) {
-                    final Intent componentIntent = new Intent(packageIntent)
-                            .setComponent(receiver.getComponentInfo().getComponentName())
-                            .addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-                    mContext.sendBroadcastAsUser(componentIntent, userHandle);
-                }
-            } catch (RemoteException ex) {
-                Slogf.w(LOG_TAG, "Cannot get list of broadcast receivers for %s because: %s.",
-                        intent.getAction(), ex);
-            }
-        }
-
         /**
          * Checks whether the package {@code packageName} has the {@code MODIFY_QUIET_MODE}
          * permission granted for the user {@code userId}.
@@ -15949,7 +15925,7 @@
     private boolean canWriteCredentialManagerPolicy(CallerIdentity caller) {
         return (isProfileOwner(caller) && isManagedProfile(caller.getUserId()))
                         || isDefaultDeviceOwner(caller)
-                        || hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
+                        || hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS);
     }
 
     @Override
@@ -16452,7 +16428,7 @@
         Objects.requireNonNull(packageName, "packageName is null");
         final CallerIdentity caller = getCallerIdentity();
         Preconditions.checkCallAuthorization(
-                hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+                hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
 
         long originalId = mInjector.binderClearCallingIdentity();
         try {
@@ -17185,7 +17161,7 @@
         // Only adb or system apps with the right permission can mark a profile owner on
         // organization-owned device.
         if (!(isAdb(caller) || hasCallingPermission(permission.MARK_DEVICE_ORGANIZATION_OWNED)
-                || hasCallingPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS))) {
+                || hasCallingPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS))) {
             throw new SecurityException(
                     "Only the system can mark a profile owner of organization-owned device.");
         }
@@ -17786,7 +17762,7 @@
     @Override
     public void forceUpdateUserSetupComplete(@UserIdInt int userId) {
         Preconditions.checkCallAuthorization(
-                hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+                hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
 
         boolean isUserCompleted = mInjector.settingsSecureGetIntForUser(
                 Settings.Secure.USER_SETUP_COMPLETE, 0, userId) != 0;
@@ -18724,7 +18700,7 @@
     public List<String> getDisallowedSystemApps(ComponentName admin, int userId,
             String provisioningAction) throws RemoteException {
         Preconditions.checkCallAuthorization(
-                hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+                hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
 
         return new ArrayList<>(
                 mOverlayPackagesProvider.getNonRequiredApps(admin, userId, provisioningAction));
@@ -19541,7 +19517,7 @@
             return false;
         }
         Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity())
-                || hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+                || hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
 
         long id = mInjector.binderClearCallingIdentity();
         try {
@@ -19568,7 +19544,7 @@
             return false;
         }
         Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity())
-                || hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+                || hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
 
         return mInjector.binderWithCleanCallingIdentity(() -> isUnattendedManagedKioskUnchecked());
     }
@@ -20548,7 +20524,7 @@
     @Override
     public void clearOrganizationIdForUser(int userHandle) {
         Preconditions.checkCallAuthorization(
-                hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+                hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
 
         synchronized (getLockObject()) {
             final ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(userHandle);
@@ -20570,7 +20546,7 @@
 
         final CallerIdentity caller = getCallerIdentity(callerPackage);
         Preconditions.checkCallAuthorization(
-                hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+                hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
 
         provisioningParams.logParams(callerPackage);
 
@@ -20668,7 +20644,7 @@
     public void finalizeWorkProfileProvisioning(UserHandle managedProfileUser,
             Account migratedAccount) {
         Preconditions.checkCallAuthorization(
-                hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+                hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
 
         if (!isManagedProfile(managedProfileUser.getIdentifier())) {
             throw new IllegalStateException("Given user is not a managed profile");
@@ -20718,7 +20694,7 @@
 
     private void maybeInstallDevicePolicyManagementRoleHolderInUser(int targetUserId) {
         String devicePolicyManagerRoleHolderPackageName =
-                getDevicePolicyManagementRoleHolderPackageName(mContext);
+                getRoleHolderPackageName(mContext, RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT);
         if (devicePolicyManagerRoleHolderPackageName == null) {
             Slogf.d(LOG_TAG, "No device policy management role holder specified.");
             return;
@@ -20744,14 +20720,24 @@
         }
     }
 
+    /**
+     * If multiple packages hold the role, returns the first package in the list.
+     */
+    @Nullable
+    private String getRoleHolderPackageName(Context context, String role) {
+        return getRoleHolderPackageNameOnUser(context, role, Process.myUserHandle());
+    }
 
-    private String getDevicePolicyManagementRoleHolderPackageName(Context context) {
+    /**
+     * If multiple packages hold the role, returns the first package in the list.
+     */
+    @Nullable
+    private String getRoleHolderPackageNameOnUser(Context context, String role, UserHandle user) {
         RoleManager roleManager = context.getSystemService(RoleManager.class);
 
         // Calling identity needs to be cleared as this method is used in the permissions checks.
         return mInjector.binderWithCleanCallingIdentity(() -> {
-            List<String> roleHolders =
-                    roleManager.getRoleHolders(RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT);
+            List<String> roleHolders = roleManager.getRoleHoldersAsUser(role, user);
             if (roleHolders.isEmpty()) {
                 return null;
             }
@@ -20760,15 +20746,65 @@
     }
 
     private boolean isCallerDevicePolicyManagementRoleHolder(CallerIdentity caller) {
+        return doesCallerHoldRole(caller, RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT);
+    }
+
+    private boolean isCallerSystemSupervisionRoleHolder(CallerIdentity caller) {
+        return doesCallerHoldRole(caller, RoleManager.ROLE_SYSTEM_SUPERVISION);
+    }
+
+    /**
+     * Check if the caller is holding the given role on the calling user.
+     *
+     * @param caller the caller you wish to check
+     * @param role the name of the role to check for.
+     * @return {@code true} if the caller holds the role, {@code false} otherwise.
+     */
+    private boolean doesCallerHoldRole(CallerIdentity caller, String role) {
         int callerUid = caller.getUid();
-        String devicePolicyManagementRoleHolderPackageName =
-                getDevicePolicyManagementRoleHolderPackageName(mContext);
+        String roleHolderPackageName =
+                getRoleHolderPackageNameOnUser(role, caller.getUserId());
         int roleHolderUid = mInjector.getPackageManagerInternal().getPackageUid(
-                devicePolicyManagementRoleHolderPackageName, 0, caller.getUserId());
+                roleHolderPackageName, 0, caller.getUserId());
 
         return callerUid == roleHolderUid;
     }
 
+    /**
+     * Return the package name of the role holder on the given user.
+     *
+     * <p>If the userId passed in is {@link UserHandle.USER_ALL} then every user will be checked and
+     * the package name of the role holder on the first user where there is a role holder is
+     * returned.
+     *
+     * @param role the name of the role to check for.
+     * @param userId the userId to check for the role holder on.
+     * @return the package name of the role holder
+     */
+    @Nullable
+    private String getRoleHolderPackageNameOnUser(String role, int userId) {
+        RoleManager roleManager = mContext.getSystemService(RoleManager.class);
+
+        // Clear calling identity as the RoleManager APIs require privileged permissions.
+        return mInjector.binderWithCleanCallingIdentity(() -> {
+            List<UserInfo> users;
+            // Interpret USER_ALL as meaning "any" user.
+            if (userId == UserHandle.USER_ALL) {
+                users = mInjector.getUserManagerInternal().getUsers(/*excludeDying=*/ true);
+            } else {
+                users = List.of(new UserInfo(userId, /*name=*/ null, /*flags=*/ 0));
+            }
+            for (UserInfo user : users) {
+                List<String> roleHolders =
+                        roleManager.getRoleHoldersAsUser(role, user.getUserHandle());
+                if (!roleHolders.isEmpty()) {
+                    return roleHolders.get(0);
+                }
+            }
+            return null;
+        });
+    }
+
     private void resetInteractAcrossProfilesAppOps(@UserIdInt int userId) {
         mInjector.getCrossProfileApps(userId).clearInteractAcrossProfilesAppOps();
         pregrantDefaultInteractAcrossProfilesAppOps(userId);
@@ -20995,7 +21031,7 @@
 
         final CallerIdentity caller = getCallerIdentity();
         Preconditions.checkCallAuthorization(
-                hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)
+                hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
                         || (hasCallingOrSelfPermission(permission.PROVISION_DEMO_DEVICE)
                         && provisioningParams.isDemoDevice()));
 
@@ -21183,7 +21219,7 @@
     @Override
     public void resetDefaultCrossProfileIntentFilters(@UserIdInt int userId) {
         Preconditions.checkCallAuthorization(
-                hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+                hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
 
         mInjector.binderWithCleanCallingIdentity(() -> {
             try {
@@ -21322,7 +21358,7 @@
     public void setDeviceOwnerType(@NonNull ComponentName admin,
             @DeviceOwnerType int deviceOwnerType) {
         Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
-                permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+                MANAGE_PROFILE_AND_DEVICE_OWNERS));
 
         synchronized (getLockObject()) {
             setDeviceOwnerTypeLocked(admin, deviceOwnerType);
@@ -21710,7 +21746,7 @@
 
     public boolean isDpcDownloaded() {
         Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
-                android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+                MANAGE_PROFILE_AND_DEVICE_OWNERS));
 
         ContentResolver cr = mContext.getContentResolver();
 
@@ -21722,7 +21758,7 @@
 
     public void setDpcDownloaded(boolean downloaded) {
         Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
-                android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+                MANAGE_PROFILE_AND_DEVICE_OWNERS));
 
         int setTo = downloaded ? 1 : 0;
 
@@ -21830,15 +21866,23 @@
         }
 
         public void register() {
-            mRm.addOnRoleHoldersChangedListenerAsUser(mExecutor, this, UserHandle.SYSTEM);
+            mRm.addOnRoleHoldersChangedListenerAsUser(mExecutor, this, UserHandle.ALL);
         }
 
         @Override
         public void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) {
-            if (!RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT.equals(roleName)) {
+            if (RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT.equals(roleName)) {
+                handleDevicePolicyManagementRoleChange(user);
                 return;
             }
-            String newRoleHolder = getRoleHolder();
+            if (RoleManager.ROLE_FINANCED_DEVICE_KIOSK.equals(roleName)) {
+                handleFinancedDeviceKioskRoleChange();
+                return;
+            }
+        }
+
+        private void handleDevicePolicyManagementRoleChange(UserHandle user) {
+            String newRoleHolder = getDeviceManagementRoleHolder(user);
             if (isDefaultRoleHolder(newRoleHolder)) {
                 Slogf.i(LOG_TAG,
                         "onRoleHoldersChanged: Default role holder is set, returning early");
@@ -21873,9 +21917,44 @@
             }
         }
 
-        private String getRoleHolder() {
-            return DevicePolicyManagerService.this.getDevicePolicyManagementRoleHolderPackageName(
-                    mContext);
+        private void handleFinancedDeviceKioskRoleChange() {
+            if (!isDevicePolicyEngineEnabled()) {
+                return;
+            }
+            Slog.i(LOG_TAG, "Handling action " + ACTION_DEVICE_FINANCING_STATE_CHANGED);
+            Intent intent = new Intent(ACTION_DEVICE_FINANCING_STATE_CHANGED);
+            mInjector.binderWithCleanCallingIdentity(() -> {
+                for (UserInfo userInfo : mUserManager.getUsers()) {
+                    UserHandle user = userInfo.getUserHandle();
+                    broadcastExplicitIntentToRoleHolder(
+                            intent, RoleManager.ROLE_SYSTEM_SUPERVISION, user);
+                    broadcastExplicitIntentToRoleHolder(
+                            intent, RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT, user);
+                    ActiveAdmin admin = getDeviceOrProfileOwnerAdminLocked(user.getIdentifier());
+                    if (admin == null) {
+                        continue;
+                    }
+                    if (!isProfileOwnerOfOrganizationOwnedDevice(
+                            admin.info.getComponent(), user.getIdentifier())
+                            && !isDeviceOwner(admin)
+                            && !(isProfileOwner(admin.info.getComponent(), user.getIdentifier())
+                            && admin.getUserHandle().isSystem())) {
+                        continue;
+                    }
+                    // Don't send the broadcast twice if the DPC is the same package as the
+                    // DMRH
+                    if (admin.info.getPackageName().equals(getDeviceManagementRoleHolder(user))) {
+                        continue;
+                    }
+                    broadcastExplicitIntentToPackage(
+                            intent, admin.info.getPackageName(), admin.getUserHandle());
+                }
+            });
+        }
+
+        private String getDeviceManagementRoleHolder(UserHandle user) {
+            return DevicePolicyManagerService.this.getRoleHolderPackageNameOnUser(
+                    mContext, RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT, user);
         }
 
         private boolean isDefaultRoleHolder(String packageName) {
@@ -21935,10 +22014,44 @@
         }
     }
 
+    private void broadcastExplicitIntentToRoleHolder(
+            Intent intent, String role, UserHandle userHandle) {
+        String packageName = getRoleHolderPackageNameOnUser(mContext, role, userHandle);
+        if (packageName == null) {
+            return;
+        }
+        broadcastExplicitIntentToPackage(intent, packageName, userHandle);
+    }
+
+    private void broadcastExplicitIntentToPackage(
+            Intent intent, String packageName, UserHandle userHandle) {
+        int userId = userHandle.getIdentifier();
+        if (packageName == null) {
+            return;
+        }
+        Intent packageIntent = new Intent(intent)
+                .setPackage(packageName);
+        List<ResolveInfo> receivers = mContext.getPackageManager().queryBroadcastReceiversAsUser(
+                packageIntent,
+                PackageManager.ResolveInfoFlags.of(PackageManager.GET_RECEIVERS),
+                userId);
+        if (receivers.isEmpty()) {
+            Slog.i(LOG_TAG, "Found no receivers to handle intent " + intent
+                    + " in package " + packageName);
+            return;
+        }
+        for (ResolveInfo receiver : receivers) {
+            Intent componentIntent = new Intent(packageIntent)
+                    .setComponent(receiver.getComponentInfo().getComponentName())
+                    .addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+            mContext.sendBroadcastAsUser(componentIntent, userHandle);
+        }
+    }
+
     @Override
     public List<UserHandle> getPolicyManagedProfiles(@NonNull UserHandle user) {
         Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
-                android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+                MANAGE_PROFILE_AND_DEVICE_OWNERS));
         int userId = user.getIdentifier();
         return mInjector.binderWithCleanCallingIdentity(() -> {
             List<UserInfo> userProfiles = mUserManager.getProfiles(userId);
@@ -22600,7 +22713,7 @@
     @Override
     public void setOverrideKeepProfilesRunning(boolean enabled) {
         Preconditions.checkCallAuthorization(
-                hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+                hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
         mKeepProfilesRunning = enabled;
         Slog.i(LOG_TAG, "Keep profiles running overridden to: " + enabled);
     }
@@ -22867,14 +22980,14 @@
     @Override
     public DevicePolicyState getDevicePolicyState() {
         Preconditions.checkCallAuthorization(
-                hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+                hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
         return mInjector.binderWithCleanCallingIdentity(mDevicePolicyEngine::getDevicePolicyState);
     }
 
     @Override
     public boolean triggerDevicePolicyEngineMigration(boolean forceMigration) {
         Preconditions.checkCallAuthorization(
-                hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+                hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
         return mInjector.binderWithCleanCallingIdentity(() -> {
             boolean canForceMigration = forceMigration && !hasNonTestOnlyActiveAdmins();
             if (!canForceMigration && !shouldMigrateToDevicePolicyEngine()) {
@@ -23233,4 +23346,28 @@
         // if the policy engine was ever used?
         return !mDevicePolicyEngine.hasActivePolicies();
     }
+
+    @Override
+    public boolean isDeviceFinanced(String callerPackageName) {
+        CallerIdentity caller = getCallerIdentity(callerPackageName);
+        Preconditions.checkCallAuthorization(isDeviceOwner(caller)
+                || isProfileOwnerOfOrganizationOwnedDevice(caller)
+                || isProfileOwnerOnUser0(caller)
+                || isCallerDevicePolicyManagementRoleHolder(caller)
+                || isCallerSystemSupervisionRoleHolder(caller));
+        return getFinancedDeviceKioskRoleHolderOnAnyUser() != null;
+    };
+
+    @Override
+    public String getFinancedDeviceKioskRoleHolder(String callerPackageName) {
+        CallerIdentity caller = getCallerIdentity(callerPackageName);
+        enforcePermission(MANAGE_PROFILE_AND_DEVICE_OWNERS, caller.getPackageName(),
+                caller.getUserId());
+        return getFinancedDeviceKioskRoleHolderOnAnyUser();
+    }
+
+    private String getFinancedDeviceKioskRoleHolderOnAnyUser() {
+        return getRoleHolderPackageNameOnUser(
+                RoleManager.ROLE_FINANCED_DEVICE_KIOSK, UserHandle.USER_ALL);
+    }
 }
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
index 7e44049..7d4f87d 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
@@ -17,6 +17,7 @@
 package com.android.server.inputmethod;
 
 import static android.inputmethodservice.InputMethodService.IME_ACTIVE;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
 
 import static com.android.internal.inputmethod.SoftInputShowHideReason.HIDE_SOFT_INPUT;
 import static com.android.internal.inputmethod.SoftInputShowHideReason.SHOW_SOFT_INPUT;
@@ -35,11 +36,16 @@
 import static org.mockito.Mockito.verify;
 
 import android.os.Binder;
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.view.inputmethod.InputMethodManager;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
+import com.android.internal.inputmethod.InputBindResult;
+import com.android.internal.inputmethod.StartInputFlags;
+import com.android.internal.inputmethod.StartInputReason;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -60,8 +66,8 @@
         super.setUp();
         mVisibilityApplier =
                 (DefaultImeVisibilityApplier) mInputMethodManagerService.getVisibilityApplier();
-        mInputMethodManagerService.mCurFocusedWindowClient = mock(
-                InputMethodManagerService.ClientState.class);
+        mInputMethodManagerService.setAttachedClientForTesting(
+                mock(InputMethodManagerService.ClientState.class));
     }
 
     @Test
@@ -119,4 +125,38 @@
         mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_SHOW_IME_IMPLICIT);
         verifyShowSoftInput(true, true, InputMethodManager.SHOW_IMPLICIT);
     }
+
+    @Test
+    public void testApplyImeVisibility_hideImeFromTargetOnSecondaryDisplay() {
+        // Init a IME target client on the secondary display to show IME.
+        mInputMethodManagerService.addClient(mMockInputMethodClient, mMockRemoteInputConnection,
+                10 /* selfReportedDisplayId */);
+        mInputMethodManagerService.setAttachedClientForTesting(null);
+        startInputOrWindowGainedFocus(mWindowToken, SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+
+        synchronized (ImfLock.class) {
+            final int displayIdToShowIme = mInputMethodManagerService.getDisplayIdToShowImeLocked();
+            // Verify hideIme will apply the expected displayId when the default IME
+            // visibility applier app STATE_HIDE_IME.
+            mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_HIDE_IME);
+            verify(mInputMethodManagerService.mWindowManagerInternal).hideIme(
+                    eq(mWindowToken), eq(displayIdToShowIme), eq(null));
+        }
+    }
+
+    private InputBindResult startInputOrWindowGainedFocus(IBinder windowToken, int softInputMode) {
+        return mInputMethodManagerService.startInputOrWindowGainedFocus(
+                StartInputReason.WINDOW_FOCUS_GAIN /* startInputReason */,
+                mMockInputMethodClient /* client */,
+                windowToken /* windowToken */,
+                StartInputFlags.VIEW_HAS_FOCUS | StartInputFlags.IS_TEXT_EDITOR,
+                softInputMode /* softInputMode */,
+                0 /* windowFlags */,
+                mEditorInfo /* editorInfo */,
+                mMockRemoteInputConnection /* inputConnection */,
+                mMockRemoteAccessibilityInputConnection /* remoteAccessibilityInputConnection */,
+                mTargetSdkVersion /* unverifiedTargetSdkVersion */,
+                mCallingUserId /* userId */,
+                mMockImeOnBackInvokedDispatcher /* imeDispatcher */);
+    }
 }
diff --git a/services/tests/mockingservicestests/src/android/service/dreams/DreamOverlayConnectionHandlerTest.java b/services/tests/mockingservicestests/src/android/service/dreams/DreamOverlayConnectionHandlerTest.java
new file mode 100644
index 0000000..22d7e73
--- /dev/null
+++ b/services/tests/mockingservicestests/src/android/service/dreams/DreamOverlayConnectionHandlerTest.java
@@ -0,0 +1,245 @@
+/*
+ * 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.service.dreams;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.test.TestLooper;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.util.ObservableServiceConnection;
+import com.android.internal.util.PersistentServiceConnection;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DreamOverlayConnectionHandlerTest {
+    private static final int MIN_CONNECTION_DURATION_MS = 100;
+    private static final int MAX_RECONNECT_ATTEMPTS = 3;
+    private static final int BASE_RECONNECT_DELAY_MS = 50;
+
+    @Mock
+    private Context mContext;
+    @Mock
+    private PersistentServiceConnection<IDreamOverlay> mConnection;
+    @Mock
+    private Intent mServiceIntent;
+    @Mock
+    private IDreamOverlay mOverlayService;
+    @Mock
+    private IDreamOverlayClient mOverlayClient;
+
+    private TestLooper mTestLooper;
+    private DreamOverlayConnectionHandler mDreamOverlayConnectionHandler;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mTestLooper = new TestLooper();
+        mDreamOverlayConnectionHandler = new DreamOverlayConnectionHandler(
+                mContext,
+                mTestLooper.getLooper(),
+                mServiceIntent,
+                MIN_CONNECTION_DURATION_MS,
+                MAX_RECONNECT_ATTEMPTS,
+                BASE_RECONNECT_DELAY_MS,
+                new TestInjector(mConnection));
+    }
+
+    @Test
+    public void consumerShouldRunImmediatelyWhenClientAvailable() throws RemoteException {
+        mDreamOverlayConnectionHandler.bind();
+        connectService();
+        provideClient();
+
+        final Consumer<IDreamOverlayClient> consumer = Mockito.mock(Consumer.class);
+        mDreamOverlayConnectionHandler.addConsumer(consumer);
+        mTestLooper.dispatchAll();
+        verify(consumer).accept(mOverlayClient);
+    }
+
+    @Test
+    public void consumerShouldRunAfterClientAvailable() throws RemoteException {
+        mDreamOverlayConnectionHandler.bind();
+        connectService();
+
+        final Consumer<IDreamOverlayClient> consumer = Mockito.mock(Consumer.class);
+        mDreamOverlayConnectionHandler.addConsumer(consumer);
+        mTestLooper.dispatchAll();
+        // No client yet, so we shouldn't have executed
+        verify(consumer, never()).accept(mOverlayClient);
+
+        provideClient();
+        mTestLooper.dispatchAll();
+        verify(consumer).accept(mOverlayClient);
+    }
+
+    @Test
+    public void consumerShouldNeverRunIfClientConnectsAndDisconnects() throws RemoteException {
+        mDreamOverlayConnectionHandler.bind();
+        connectService();
+
+        final Consumer<IDreamOverlayClient> consumer = Mockito.mock(Consumer.class);
+        mDreamOverlayConnectionHandler.addConsumer(consumer);
+        mTestLooper.dispatchAll();
+        // No client yet, so we shouldn't have executed
+        verify(consumer, never()).accept(mOverlayClient);
+
+        provideClient();
+        // Service disconnected before looper could handle the message.
+        disconnectService();
+        mTestLooper.dispatchAll();
+        verify(consumer, never()).accept(mOverlayClient);
+    }
+
+    @Test
+    public void consumerShouldNeverRunIfUnbindCalled() throws RemoteException {
+        mDreamOverlayConnectionHandler.bind();
+        connectService();
+        provideClient();
+
+        final Consumer<IDreamOverlayClient> consumer = Mockito.mock(Consumer.class);
+        mDreamOverlayConnectionHandler.addConsumer(consumer);
+        mDreamOverlayConnectionHandler.unbind();
+        mTestLooper.dispatchAll();
+        // We unbinded immediately after adding consumer, so should never have run.
+        verify(consumer, never()).accept(mOverlayClient);
+    }
+
+    @Test
+    public void consumersOnlyRunOnceIfUnbound() throws RemoteException {
+        mDreamOverlayConnectionHandler.bind();
+        connectService();
+        provideClient();
+
+        AtomicInteger counter = new AtomicInteger();
+        // Add 10 consumers in a row which call unbind within the consumer.
+        for (int i = 0; i < 10; i++) {
+            mDreamOverlayConnectionHandler.addConsumer(client -> {
+                counter.getAndIncrement();
+                mDreamOverlayConnectionHandler.unbind();
+            });
+        }
+        mTestLooper.dispatchAll();
+        // Only the first consumer should have run, since we unbinded.
+        assertThat(counter.get()).isEqualTo(1);
+    }
+
+    @Test
+    public void consumerShouldRunAgainAfterReconnect() throws RemoteException {
+        mDreamOverlayConnectionHandler.bind();
+        connectService();
+        provideClient();
+
+        final Consumer<IDreamOverlayClient> consumer = Mockito.mock(Consumer.class);
+        mDreamOverlayConnectionHandler.addConsumer(consumer);
+        mTestLooper.dispatchAll();
+        verify(consumer, times(1)).accept(mOverlayClient);
+
+        disconnectService();
+        mTestLooper.dispatchAll();
+        // No new calls should happen when service disconnected.
+        verify(consumer, times(1)).accept(mOverlayClient);
+
+        connectService();
+        provideClient();
+        mTestLooper.dispatchAll();
+        // We should trigger the consumer again once the server reconnects.
+        verify(consumer, times(2)).accept(mOverlayClient);
+    }
+
+    @Test
+    public void consumerShouldNeverRunIfRemovedImmediately() throws RemoteException {
+        mDreamOverlayConnectionHandler.bind();
+        connectService();
+        provideClient();
+
+        final Consumer<IDreamOverlayClient> consumer = Mockito.mock(Consumer.class);
+        mDreamOverlayConnectionHandler.addConsumer(consumer);
+        mDreamOverlayConnectionHandler.removeConsumer(consumer);
+        mTestLooper.dispatchAll();
+        verify(consumer, never()).accept(mOverlayClient);
+    }
+
+    private void connectService() {
+        final ObservableServiceConnection.Callback<IDreamOverlay> callback =
+                captureConnectionCallback();
+        callback.onConnected(mConnection, mOverlayService);
+    }
+
+    private void disconnectService() {
+        final ObservableServiceConnection.Callback<IDreamOverlay> callback =
+                captureConnectionCallback();
+        callback.onDisconnected(mConnection, /* reason= */ 0);
+    }
+
+    private void provideClient() throws RemoteException {
+        final IDreamOverlayClientCallback callback = captureClientCallback();
+        callback.onDreamOverlayClient(mOverlayClient);
+    }
+
+    private ObservableServiceConnection.Callback<IDreamOverlay> captureConnectionCallback() {
+        ArgumentCaptor<ObservableServiceConnection.Callback<IDreamOverlay>>
+                callbackCaptor =
+                ArgumentCaptor.forClass(ObservableServiceConnection.Callback.class);
+        verify(mConnection).addCallback(callbackCaptor.capture());
+        return callbackCaptor.getValue();
+    }
+
+    private IDreamOverlayClientCallback captureClientCallback() throws RemoteException {
+        ArgumentCaptor<IDreamOverlayClientCallback> callbackCaptor =
+                ArgumentCaptor.forClass(IDreamOverlayClientCallback.class);
+        verify(mOverlayService, atLeastOnce()).getClient(callbackCaptor.capture());
+        return callbackCaptor.getValue();
+    }
+
+    static class TestInjector extends DreamOverlayConnectionHandler.Injector {
+        private final PersistentServiceConnection<IDreamOverlay> mConnection;
+
+        TestInjector(PersistentServiceConnection<IDreamOverlay> connection) {
+            mConnection = connection;
+        }
+
+        @Override
+        public PersistentServiceConnection<IDreamOverlay> buildConnection(Context context,
+                Handler handler, Intent serviceIntent, int minConnectionDurationMs,
+                int maxReconnectAttempts, int baseReconnectDelayMs) {
+            return mConnection;
+        }
+    }
+}
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/CachedAppOptimizerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
index ec9e5b5..1fbb8dd 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
@@ -19,7 +19,6 @@
 import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
 
 import static com.android.server.am.ActivityManagerService.Injector;
-import static com.android.server.am.CachedAppOptimizer.compactActionIntToAction;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -155,12 +154,6 @@
         synchronized (mCachedAppOptimizerUnderTest.mPhenotypeFlagLock) {
             assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isEqualTo(
                     CachedAppOptimizer.DEFAULT_USE_COMPACTION);
-            assertThat(mCachedAppOptimizerUnderTest.mCompactActionSome)
-                    .isEqualTo(
-                            compactActionIntToAction(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1));
-            assertThat(mCachedAppOptimizerUnderTest.mCompactActionFull)
-                    .isEqualTo(
-                            compactActionIntToAction(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2));
             assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
                     CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1);
             assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
@@ -210,12 +203,6 @@
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                 CachedAppOptimizer.KEY_USE_COMPACTION, "true", false);
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                CachedAppOptimizer.KEY_COMPACT_ACTION_1,
-                Integer.toString((CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1 + 1 % 4) + 1), false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                CachedAppOptimizer.KEY_COMPACT_ACTION_2,
-                Integer.toString((CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2 + 1 % 4) + 1), false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                 CachedAppOptimizer.KEY_COMPACT_THROTTLE_1,
                 Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1 + 1), false);
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -266,12 +253,6 @@
         assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isTrue();
         assertThat(mCachedAppOptimizerUnderTest.mCachedAppOptimizerThread.isAlive()).isTrue();
 
-        assertThat(mCachedAppOptimizerUnderTest.mCompactActionSome)
-                .isEqualTo(compactActionIntToAction(
-                        (CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1 + 1 % 4) + 1));
-        assertThat(mCachedAppOptimizerUnderTest.mCompactActionFull)
-                .isEqualTo(compactActionIntToAction(
-                        (CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2 + 1 % 4) + 1));
         assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
                 CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1 + 1);
         assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
@@ -404,72 +385,6 @@
     }
 
     @Test
-    public void compactAction_listensToDeviceConfigChanges() throws InterruptedException {
-        mCachedAppOptimizerUnderTest.init();
-
-        // When we override new values for the compaction action with reasonable values...
-
-        // There are four possible values for compactAction[Some|Full].
-        for (int i = 1; i < 5; i++) {
-            mCountDown = new CountDownLatch(2);
-            int expectedSome = (CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1 + i) % 4 + 1;
-            DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                    CachedAppOptimizer.KEY_COMPACT_ACTION_1, Integer.toString(expectedSome), false);
-            int expectedFull = (CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2 + i) % 4 + 1;
-            DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                    CachedAppOptimizer.KEY_COMPACT_ACTION_2, Integer.toString(expectedFull), false);
-            assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
-            // Then the updates are reflected in the flags.
-            synchronized (mCachedAppOptimizerUnderTest.mPhenotypeFlagLock) {
-                assertThat(mCachedAppOptimizerUnderTest.mCompactActionSome)
-                        .isEqualTo(compactActionIntToAction(expectedSome));
-                assertThat(mCachedAppOptimizerUnderTest.mCompactActionFull)
-                        .isEqualTo(compactActionIntToAction(expectedFull));
-            }
-        }
-    }
-
-    @Test
-    public void compactAction_listensToDeviceConfigChangesBadValues() throws InterruptedException {
-        mCachedAppOptimizerUnderTest.init();
-
-        // When we override new values for the compaction action with bad values ...
-        mCountDown = new CountDownLatch(2);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                CachedAppOptimizer.KEY_COMPACT_ACTION_1, "foo", false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                CachedAppOptimizer.KEY_COMPACT_ACTION_2, "foo", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
-        synchronized (mCachedAppOptimizerUnderTest.mPhenotypeFlagLock) {
-            // Then the default values are reflected in the flag
-            assertThat(mCachedAppOptimizerUnderTest.mCompactActionSome)
-                    .isEqualTo(
-                            compactActionIntToAction(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1));
-            assertThat(mCachedAppOptimizerUnderTest.mCompactActionFull)
-                    .isEqualTo(
-                            compactActionIntToAction(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2));
-        }
-
-        mCountDown = new CountDownLatch(2);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                CachedAppOptimizer.KEY_COMPACT_ACTION_1, "", false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                CachedAppOptimizer.KEY_COMPACT_ACTION_2, "", false);
-        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
-        synchronized (mCachedAppOptimizerUnderTest.mPhenotypeFlagLock) {
-            assertThat(mCachedAppOptimizerUnderTest.mCompactActionSome)
-                    .isEqualTo(
-                            compactActionIntToAction(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1));
-            assertThat(mCachedAppOptimizerUnderTest.mCompactActionFull)
-                    .isEqualTo(
-                            compactActionIntToAction(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2));
-        }
-    }
-
-    @Test
     public void compactThrottle_listensToDeviceConfigChanges() throws InterruptedException {
         mCachedAppOptimizerUnderTest.init();
 
@@ -1108,14 +1023,17 @@
 
         mCachedAppOptimizerUnderTest.mLastCompactionStats.clear();
 
-        // We force a some compaction
-        mCachedAppOptimizerUnderTest.compactApp(processRecord,
-                CachedAppOptimizer.CompactProfile.SOME, CachedAppOptimizer.CompactSource.APP, true);
-        waitForHandler();
-        // then process is compacted.
-        CachedAppOptimizer.CompactProfile executedCompactProfile =
-                processRecord.mOptRecord.getLastCompactProfile();
-        assertThat(executedCompactProfile).isEqualTo(CachedAppOptimizer.CompactProfile.SOME);
+        if (CachedAppOptimizer.ENABLE_FILE_COMPACT) {
+            // We force a some compaction
+            mCachedAppOptimizerUnderTest.compactApp(processRecord,
+                    CachedAppOptimizer.CompactProfile.SOME, CachedAppOptimizer.CompactSource.APP,
+                    true);
+            waitForHandler();
+            // then process is compacted.
+            CachedAppOptimizer.CompactProfile executedCompactProfile =
+                    processRecord.mOptRecord.getLastCompactProfile();
+            assertThat(executedCompactProfile).isEqualTo(CachedAppOptimizer.CompactProfile.SOME);
+        }
     }
 
     private void setFlag(String key, String value, boolean defaultValue) throws Exception {
@@ -1192,7 +1110,7 @@
         }
 
         @Override
-        public void performCompaction(CachedAppOptimizer.CompactAction action, int pid)
+        public void performCompaction(CachedAppOptimizer.CompactProfile profile, int pid)
                 throws IOException {
             mRss = mRssAfterCompaction;
         }
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/cpu/CpuInfoReaderTest.java b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java
index b214787..04f6f8b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java
@@ -92,6 +92,7 @@
         expectedCpuInfos.append(0, new CpuInfoReader.CpuInfo(/* cpuCore= */ 0,
                 FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_230_000,
                 /* maxCpuFreqKHz= */ 2_500_000, /* avgTimeInStateCpuFreqKHz= */ 488_095,
+                /* normalizedAvailableCpuFreqKHz= */ 2_402_267,
                 new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 32_249_610,
                         /* niceTimeMillis= */ 7_950_930, /* systemTimeMillis= */ 52_227_050,
                         /* idleTimeMillis= */ 409_036_950, /* iowaitTimeMillis= */ 1_322_810,
@@ -101,6 +102,7 @@
         expectedCpuInfos.append(1, new CpuInfoReader.CpuInfo(/* cpuCore= */ 1,
                 FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_450_000,
                 /* maxCpuFreqKHz= */ 2_800_000, /* avgTimeInStateCpuFreqKHz= */ 502_380,
+                /* normalizedAvailableCpuFreqKHz= */ 2_693_525,
                 new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_949_280,
                         /* niceTimeMillis= */ 7_799_450, /* systemTimeMillis= */ 54_004_020,
                         /* idleTimeMillis= */ 402_707_120, /* iowaitTimeMillis= */ 1_186_960,
@@ -111,6 +113,7 @@
                 FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
                 /* isOnline= */ true, /* curCpuFreqKHz= */ 1_000_000,
                 /* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ 464_285,
+                /* normalizedAvailableCpuFreqKHz= */ 1_901_608,
                 new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_959_280,
                         /* niceTimeMillis= */ 7_789_450, /* systemTimeMillis= */ 54_014_020,
                         /* idleTimeMillis= */ 402_717_120, /* iowaitTimeMillis= */ 1_166_960,
@@ -121,6 +124,7 @@
                 FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
                 /* isOnline= */ true, /* curCpuFreqKHz= */ 1_000_000,
                 /* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ 464_285,
+                /* normalizedAvailableCpuFreqKHz= */ 1_907_125,
                 new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 32_349_610,
                         /* niceTimeMillis= */ 7_850_930, /* systemTimeMillis= */ 52_127_050,
                         /* idleTimeMillis= */ 409_136_950, /* iowaitTimeMillis= */ 1_332_810,
@@ -139,6 +143,7 @@
         expectedCpuInfos.append(0, new CpuInfoReader.CpuInfo(/* cpuCore= */ 0,
                 FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_000_000,
                 /* maxCpuFreqKHz= */ 2_500_000, /* avgTimeInStateCpuFreqKHz= */ 419_354,
+                /* normalizedAvailableCpuFreqKHz= */ 2_425_919,
                 new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 10_000_000,
                         /* niceTimeMillis= */ 1_000_000, /* systemTimeMillis= */ 10_000_000,
                         /* idleTimeMillis= */ 110_000_000, /* iowaitTimeMillis= */ 1_100_000,
@@ -148,6 +153,7 @@
         expectedCpuInfos.append(1, new CpuInfoReader.CpuInfo(/* cpuCore= */ 1,
                 FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 2_800_000,
                 /* maxCpuFreqKHz= */ 2_800_000, /* avgTimeInStateCpuFreqKHz= */ 429_032,
+                /* normalizedAvailableCpuFreqKHz= */ 2_403_009,
                 new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 900_000,
                         /* niceTimeMillis= */ 1_000_000, /* systemTimeMillis= */ 10_000_000,
                         /* idleTimeMillis= */ 1_000_000, /* iowaitTimeMillis= */ 90_000,
@@ -158,6 +164,7 @@
                 FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
                 /* isOnline= */ true, /* curCpuFreqKHz= */ 2_000_000,
                 /* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ 403_225,
+                /* normalizedAvailableCpuFreqKHz= */ 1_688_209,
                 new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 10_000_000,
                         /* niceTimeMillis= */ 2_000_000, /* systemTimeMillis= */ 0,
                         /* idleTimeMillis= */ 10_000_000, /* iowaitTimeMillis= */ 1_000_000,
@@ -168,6 +175,7 @@
                 FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
                 /* isOnline= */ false, /* curCpuFreqKHz= */ MISSING_FREQUENCY,
                 /* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
+                /* normalizedAvailableCpuFreqKHz= */ MISSING_FREQUENCY,
                 new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 2_000_000,
                         /* niceTimeMillis= */ 1_000_000, /* systemTimeMillis= */ 1_000_000,
                         /* idleTimeMillis= */ 100_000, /* iowaitTimeMillis= */ 100_000,
@@ -189,6 +197,7 @@
         expectedCpuInfos.append(0, new CpuInfoReader.CpuInfo(/* cpuCore= */ 0,
                 FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_230_000,
                 /* maxCpuFreqKHz= */ 2_500_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
+                /* normalizedAvailableCpuFreqKHz= */ 2_253_713,
                 new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 32_249_610,
                         /* niceTimeMillis= */ 7_950_930, /* systemTimeMillis= */ 52_227_050,
                         /* idleTimeMillis= */ 409_036_950, /* iowaitTimeMillis= */ 1_322_810,
@@ -198,6 +207,7 @@
         expectedCpuInfos.append(1, new CpuInfoReader.CpuInfo(/* cpuCore= */ 1,
                 FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_450_000,
                 /* maxCpuFreqKHz= */ 2_800_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
+                /* normalizedAvailableCpuFreqKHz= */ 2_492_687,
                 new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_949_280,
                         /* niceTimeMillis= */ 7_799_450, /* systemTimeMillis= */ 54_004_020,
                         /* idleTimeMillis= */ 402_707_120, /* iowaitTimeMillis= */ 1_186_960,
@@ -208,6 +218,7 @@
                 FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
                 /* isOnline= */ true, /* curCpuFreqKHz= */ 1_000_000,
                 /* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
+                /* normalizedAvailableCpuFreqKHz= */ 1_788_079,
                 new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_959_280,
                         /* niceTimeMillis= */ 7_789_450, /* systemTimeMillis= */ 54_014_020,
                         /* idleTimeMillis= */ 402_717_120, /* iowaitTimeMillis= */ 1_166_960,
@@ -218,6 +229,7 @@
                 FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
                 /* isOnline= */ true, /* curCpuFreqKHz= */ 1_000_000,
                 /* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
+                /* normalizedAvailableCpuFreqKHz= */ 1_799_962,
                 new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 32_349_610,
                         /* niceTimeMillis= */ 7_850_930, /* systemTimeMillis= */ 52_127_050,
                         /* idleTimeMillis= */ 409_136_950, /* iowaitTimeMillis= */ 1_332_810,
@@ -237,6 +249,7 @@
         expectedCpuInfos.append(0, new CpuInfoReader.CpuInfo(/* cpuCore= */ 0,
                 FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_000_000,
                 /* maxCpuFreqKHz= */ 2_500_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
+                /* normalizedAvailableCpuFreqKHz= */ 2323347,
                 new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 10_000_000,
                         /* niceTimeMillis= */ 1_000_000, /* systemTimeMillis= */ 10_000_000,
                         /* idleTimeMillis= */ 110_000_000, /* iowaitTimeMillis= */ 1_100_000,
@@ -246,6 +259,7 @@
         expectedCpuInfos.append(1, new CpuInfoReader.CpuInfo(/* cpuCore= */ 1,
                 FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 2_800_000,
                 /* maxCpuFreqKHz= */ 2_800_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
+                /* normalizedAvailableCpuFreqKHz= */ 209111,
                 new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 900_000,
                         /* niceTimeMillis= */ 1_000_000, /* systemTimeMillis= */ 10_000_000,
                         /* idleTimeMillis= */ 1_000_000, /* iowaitTimeMillis= */ 90_000,
@@ -256,6 +270,7 @@
                 FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
                 /* isOnline= */ true, /* curCpuFreqKHz= */ 2_000_000,
                 /* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
+                /* normalizedAvailableCpuFreqKHz= */ 453514,
                 new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 10_000_000,
                         /* niceTimeMillis= */ 2_000_000, /* systemTimeMillis= */ 0,
                         /* idleTimeMillis= */ 10_000_000, /* iowaitTimeMillis= */ 1_000_000,
@@ -266,6 +281,7 @@
                 FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
                 /* isOnline= */ true, /* curCpuFreqKHz= */ 2_000_000,
                 /* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
+                /* normalizedAvailableCpuFreqKHz= */ 37728,
                 new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 2_000_000,
                         /* niceTimeMillis= */ 1_000_000, /* systemTimeMillis= */ 1_000_000,
                         /* idleTimeMillis= */ 100_000, /* iowaitTimeMillis= */ 100_000,
@@ -323,38 +339,8 @@
 
         SparseArray<CpuInfoReader.CpuInfo> actualCpuInfos = cpuInfoReader.readCpuInfos();
         SparseArray<CpuInfoReader.CpuInfo> expectedCpuInfos = new SparseArray<>();
-        expectedCpuInfos.append(1, new CpuInfoReader.CpuInfo(/* cpuCore= */ 1,
-                FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 3_000_000,
-                /* maxCpuFreqKHz= */ 1_000_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
-                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_949_280,
-                        /* niceTimeMillis= */ 7_799_450, /* systemTimeMillis= */ 54_004_020,
-                        /* idleTimeMillis= */ 402_707_120, /* iowaitTimeMillis= */ 1_186_960,
-                        /* irqTimeMillis= */ 14_786_940, /* softirqTimeMillis= */ 1_498_130,
-                        /* stealTimeMillis= */ 78_780, /* guestTimeMillis= */ 0,
-                        /* guestNiceTimeMillis= */ 0)));
-        expectedCpuInfos.append(2, new CpuInfoReader.CpuInfo(/* cpuCore= */ 2,
-                FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
-                /* isOnline= */ true, /* curCpuFreqKHz= */ 9, /* maxCpuFreqKHz= */ 2,
-                /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
-                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_959_280,
-                        /* niceTimeMillis= */ 7_789_450, /* systemTimeMillis= */ 54_014_020,
-                        /* idleTimeMillis= */ 402_717_120, /* iowaitTimeMillis= */ 1_166_960,
-                        /* irqTimeMillis= */ 14_796_940, /* softirqTimeMillis= */ 1_478_130,
-                        /* stealTimeMillis= */ 88_780, /* guestTimeMillis= */ 0,
-                        /* guestNiceTimeMillis= */ 0)));
-        expectedCpuInfos.append(3, new CpuInfoReader.CpuInfo(/* cpuCore= */ 3,
-                FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
-                /* isOnline= */ true, /* curCpuFreqKHz= */ 9, /* maxCpuFreqKHz= */ 2,
-                /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
-                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 32_349_610,
-                        /* niceTimeMillis= */ 7_850_930, /* systemTimeMillis= */ 52_127_050,
-                        /* idleTimeMillis= */ 409_136_950, /* iowaitTimeMillis= */ 1_332_810,
-                        /* irqTimeMillis= */ 8_136_740, /* softirqTimeMillis= */ 438_970,
-                        /* stealTimeMillis= */ 71_950, /* guestTimeMillis= */ 0,
-                        /* guestNiceTimeMillis= */ 0)));
 
-        compareCpuInfos("CPU infos with corrupted CPU frequency", expectedCpuInfos,
-                actualCpuInfos);
+        compareCpuInfos("CPU infos with corrupted CPU frequency", expectedCpuInfos, actualCpuInfos);
     }
 
     @Test
@@ -368,6 +354,7 @@
         expectedCpuInfos.append(0, new CpuInfoReader.CpuInfo(/* cpuCore= */ 0,
                 FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_230_000,
                 /* maxCpuFreqKHz= */ 2_500_000, /* avgTimeInStateCpuFreqKHz= */ 488_095,
+                /* normalizedAvailableCpuFreqKHz= */ 2_402_267,
                 new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 32_249_610,
                         /* niceTimeMillis= */ 7_950_930, /* systemTimeMillis= */ 52_227_050,
                         /* idleTimeMillis= */ 409_036_950, /* iowaitTimeMillis= */ 1_322_810,
@@ -377,6 +364,7 @@
         expectedCpuInfos.append(1, new CpuInfoReader.CpuInfo(/* cpuCore= */ 1,
                 FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_450_000,
                 /* maxCpuFreqKHz= */ 2_800_000, /* avgTimeInStateCpuFreqKHz= */ 502_380,
+                /* normalizedAvailableCpuFreqKHz= */ 2_693_525,
                 new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_949_280,
                         /* niceTimeMillis= */ 7_799_450, /* systemTimeMillis= */ 54_004_020,
                         /* idleTimeMillis= */ 402_707_120, /* iowaitTimeMillis= */ 1_186_960,
@@ -393,7 +381,7 @@
         assertWithMessage("Make empty dir %s", emptyDir).that(emptyDir.mkdir()).isTrue();
         CpuInfoReader cpuInfoReader = new CpuInfoReader(emptyDir, getCacheFile(
                 VALID_CPUFREQ_WITH_TIME_IN_STATE_DIR),
-                getCacheFile(VALID_PROC_STAT));
+                getCacheFile(VALID_PROC_STAT), /* minReadIntervalMillis= */0);
 
         assertWithMessage("Init CPU reader info").that(cpuInfoReader.init()).isFalse();
 
@@ -406,7 +394,7 @@
         File emptyDir = getCacheFile(EMPTY_DIR);
         assertWithMessage("Make empty dir %s", emptyDir).that(emptyDir.mkdir()).isTrue();
         CpuInfoReader cpuInfoReader = new CpuInfoReader(getCacheFile(VALID_CPUSET_DIR), emptyDir,
-                getCacheFile(VALID_PROC_STAT));
+                getCacheFile(VALID_PROC_STAT), /* minReadIntervalMillis= */0);
 
         assertWithMessage("Init CPU reader info").that(cpuInfoReader.init()).isFalse();
 
@@ -420,12 +408,32 @@
         assertWithMessage("Create empty file %s", emptyFile).that(emptyFile.createNewFile())
                 .isTrue();
         CpuInfoReader cpuInfoReader = new CpuInfoReader(getCacheFile(VALID_CPUSET_DIR),
-                getCacheFile(VALID_CPUFREQ_WITH_TIME_IN_STATE_DIR), getCacheFile(EMPTY_FILE));
+                getCacheFile(VALID_CPUFREQ_WITH_TIME_IN_STATE_DIR), getCacheFile(EMPTY_FILE),
+                /* minReadIntervalMillis= */0);
 
         assertWithMessage("Cpu infos with empty proc stat").that(cpuInfoReader.readCpuInfos())
                 .isNull();
     }
 
+    @Test
+    public void testReadingTooFrequentlyReturnsLastReadCpuInfos() throws Exception {
+        CpuInfoReader cpuInfoReader = new CpuInfoReader(getCacheFile(VALID_CPUSET_DIR),
+                getCacheFile(VALID_CPUFREQ_WITH_TIME_IN_STATE_DIR), getCacheFile(VALID_PROC_STAT),
+                /* minReadIntervalMillis= */ 60_000);
+        assertWithMessage("Initialize CPU info reader").that(cpuInfoReader.init()).isTrue();
+
+        SparseArray<CpuInfoReader.CpuInfo> firstCpuInfos = cpuInfoReader.readCpuInfos();
+        assertWithMessage("CPU infos first snapshot").that(firstCpuInfos).isNotNull();
+        assertWithMessage("CPU infos first snapshot size").that(firstCpuInfos.size())
+                .isGreaterThan(0);
+
+        SparseArray<CpuInfoReader.CpuInfo> secondCpuInfos = cpuInfoReader.readCpuInfos();
+        compareCpuInfos("CPU infos second snapshot", firstCpuInfos, secondCpuInfos);
+
+        SparseArray<CpuInfoReader.CpuInfo> thirdCpuInfos = cpuInfoReader.readCpuInfos();
+        compareCpuInfos("CPU infos third snapshot", firstCpuInfos, thirdCpuInfos);
+    }
+
     private void compareCpuInfos(String message,
             SparseArray<CpuInfoReader.CpuInfo> expected,
             SparseArray<CpuInfoReader.CpuInfo> actual) {
@@ -462,7 +470,8 @@
 
     private static CpuInfoReader newCpuInfoReader(File cpusetDir, File cpuFreqDir,
             File procStatFile) {
-        CpuInfoReader cpuInfoReader = new CpuInfoReader(cpusetDir, cpuFreqDir, procStatFile);
+        CpuInfoReader cpuInfoReader = new CpuInfoReader(cpusetDir, cpuFreqDir, procStatFile,
+                /* minReadIntervalMillis= */ 0);
         assertWithMessage("Initialize CPU info reader").that(cpuInfoReader.init()).isTrue();
         return cpuInfoReader;
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java
index 49a2cc6..5a5f525 100644
--- a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java
@@ -17,105 +17,659 @@
 package com.android.server.cpu;
 
 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.verify;
+import static com.android.server.cpu.CpuAvailabilityInfo.MISSING_CPU_AVAILABILITY_PERCENT;
 import static com.android.server.cpu.CpuAvailabilityMonitoringConfig.CPUSET_ALL;
+import static com.android.server.cpu.CpuAvailabilityMonitoringConfig.CPUSET_BACKGROUND;
+import static com.android.server.cpu.CpuInfoReader.CpuInfo.MISSING_FREQUENCY;
+import static com.android.server.cpu.CpuInfoReader.FLAG_CPUSET_CATEGORY_BACKGROUND;
+import static com.android.server.cpu.CpuInfoReader.FLAG_CPUSET_CATEGORY_TOP_APP;
+import static com.android.server.cpu.CpuMonitorService.DEFAULT_MONITORING_INTERVAL_MILLISECONDS;
+
+import static com.google.common.truth.Truth.assertWithMessage;
 
 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;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.HandlerExecutor;
+import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.ServiceManager;
+import android.util.ArraySet;
+import android.util.SparseArray;
 
 import com.android.server.ExtendedMockitoRule;
 import com.android.server.LocalServices;
+import com.android.server.Watchdog;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
+import org.mockito.stubbing.OngoingStubbing;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 public final class CpuMonitorServiceTest {
-    private static final CpuAvailabilityMonitoringConfig TEST_CPU_AVAILABILITY_MONITORING_CONFIG =
+    private static final String TAG = CpuMonitorServiceTest.class.getSimpleName();
+    private static final String USER_BUILD_TAG = TAG + "UserBuild";
+    private static final long ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS =
+            TimeUnit.SECONDS.toMillis(1);
+    private static final long HANDLER_THREAD_SYNC_TIMEOUT_MILLISECONDS =
+            TimeUnit.SECONDS.toMillis(5);
+    private static final long TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS = 100;
+    private static final long TEST_DEBUG_MONITORING_INTERVAL_MILLISECONDS = 150;
+    private static final long TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS = 300;
+    private static final CpuAvailabilityMonitoringConfig TEST_MONITORING_CONFIG_ALL_CPUSET =
             new CpuAvailabilityMonitoringConfig.Builder(CPUSET_ALL)
                     .addThreshold(30).addThreshold(70).build();
-
-    private static final CpuAvailabilityMonitoringConfig TEST_CPU_AVAILABILITY_MONITORING_CONFIG_2 =
-            new CpuAvailabilityMonitoringConfig.Builder(CPUSET_ALL)
-                    .addThreshold(10).addThreshold(90).build();
+    private static final CpuAvailabilityMonitoringConfig TEST_MONITORING_CONFIG_BG_CPUSET =
+            new CpuAvailabilityMonitoringConfig.Builder(CPUSET_BACKGROUND)
+                    .addThreshold(50).addThreshold(90).build();
+    private static final List<StaticCpuInfo> STATIC_CPU_INFOS = List.of(
+            new StaticCpuInfo(/* cpuCore= */ 0,
+                    /* cpusetCategories= */ FLAG_CPUSET_CATEGORY_TOP_APP,
+                    /* maxCpuFreqKHz= */ 4000),
+            new StaticCpuInfo(/* cpuCore= */ 1,
+                    /* cpusetCategories= */ FLAG_CPUSET_CATEGORY_TOP_APP,
+                    /* maxCpuFreqKHz= */ 3000),
+            new StaticCpuInfo(/* cpuCore= */ 2, /* cpusetCategories= */ FLAG_CPUSET_CATEGORY_TOP_APP
+                    | FLAG_CPUSET_CATEGORY_BACKGROUND, /* maxCpuFreqKHz= */ 3000),
+            new StaticCpuInfo(/* cpuCore= */ 3, /* cpusetCategories= */ FLAG_CPUSET_CATEGORY_TOP_APP
+                    | FLAG_CPUSET_CATEGORY_BACKGROUND, /* maxCpuFreqKHz= */ 3000),
+            new StaticCpuInfo(/* cpuCore= */ 4, /* cpusetCategories= */ FLAG_CPUSET_CATEGORY_TOP_APP
+                    | FLAG_CPUSET_CATEGORY_BACKGROUND, /* maxCpuFreqKHz= */ 2000));
+    private static final ArraySet<Integer> NO_OFFLINE_CORES = new ArraySet<>();
 
     @Mock
-    private Context mContext;
+    private Context mMockContext;
+    @Mock
+    private CpuInfoReader mMockCpuInfoReader;
+    @Captor
+    private ArgumentCaptor<CpuAvailabilityInfo> mCpuAvailabilityInfoCaptor;
+    private HandlerThread mServiceHandlerThread;
+    private Handler mServiceHandler;
     private CpuMonitorService mService;
-    private HandlerExecutor mHandlerExecutor;
     private CpuMonitorInternal mLocalService;
 
     @Rule
     public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
             .mockStatic(ServiceManager.class)
+            .mockStatic(Watchdog.class)
             .build();
 
     @Before
-    public void setUp() {
-        mService = new CpuMonitorService(mContext);
-        mHandlerExecutor = new HandlerExecutor(new Handler(Looper.getMainLooper()));
+    public void setUp() throws Exception {
+        mServiceHandlerThread = new HandlerThread(TAG);
+        mService = new CpuMonitorService(mMockContext, mMockCpuInfoReader, mServiceHandlerThread,
+                /* shouldDebugMonitor= */ true, TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS,
+                TEST_DEBUG_MONITORING_INTERVAL_MILLISECONDS,
+                TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS);
+
         doNothing().when(() -> ServiceManager.addService(eq("cpu_monitor"), any(Binder.class),
                 anyBoolean(), anyInt()));
-        mService.onStart();
-        mLocalService = LocalServices.getService(CpuMonitorInternal.class);
+        doReturn(mock(Watchdog.class)).when(Watchdog::getInstance);
+        when(mMockCpuInfoReader.init()).thenReturn(true);
+        when(mMockCpuInfoReader.readCpuInfos()).thenReturn(new SparseArray<>());
+
+        startService();
     }
 
     @After
-    public void tearDown() {
-        // The CpuMonitorInternal.class service is added by the mService.onStart call.
-        // Remove the service to ensure the setUp procedure can add this service again.
+    public void tearDown() throws Exception {
+        terminateService();
+    }
+
+    @Test
+    public void testAddRemoveCpuAvailabilityCallbackOnDebugBuild() throws Exception {
+        CpuMonitorInternal.CpuAvailabilityCallback mockCallback = mock(
+                CpuMonitorInternal.CpuAvailabilityCallback.class);
+
+        mLocalService.addCpuAvailabilityCallback(/* executor= */ null,
+                TEST_MONITORING_CONFIG_ALL_CPUSET, mockCallback);
+
+        assertWithMessage("Monitoring interval after adding a client callback")
+                .that(mService.getCurrentMonitoringIntervalMillis())
+                .isEqualTo(TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS);
+
+        // Monitoring interval changed notification is sent asynchronously from the handler thread.
+        // So, sync with this thread before verifying the client call.
+        syncWithHandler(mServiceHandler, /* delayMillis= */ 0);
+
+        verify(mockCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS))
+                .onMonitoringIntervalChanged(TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS);
+
+        verify(mockCallback, never()).onAvailabilityChanged(any());
+
+        mLocalService.removeCpuAvailabilityCallback(mockCallback);
+
+        assertWithMessage("Monitoring interval after removing all client callbacks")
+                .that(mService.getCurrentMonitoringIntervalMillis())
+                .isEqualTo(TEST_DEBUG_MONITORING_INTERVAL_MILLISECONDS);
+    }
+
+    @Test
+    public void testAddRemoveCpuAvailabilityCallbackOnUserBuild() throws Exception {
+        // The default service instantiated during test setUp has the debug monitoring enabled.
+        // But on a user build, debug monitoring is disabled. So, replace the default service with
+        // an equivalent user build service.
+        replaceServiceWithUserBuildService();
+
+        CpuMonitorInternal.CpuAvailabilityCallback mockCallback = mock(
+                CpuMonitorInternal.CpuAvailabilityCallback.class);
+
+        mLocalService.addCpuAvailabilityCallback(/* executor= */ null,
+                TEST_MONITORING_CONFIG_ALL_CPUSET, mockCallback);
+
+        assertWithMessage("Monitoring interval after adding a client callback")
+                .that(mService.getCurrentMonitoringIntervalMillis())
+                .isEqualTo(TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS);
+
+        // Monitoring interval changed notification is sent asynchronously from the handler thread.
+        // So, sync with this thread before verifying the client call.
+        syncWithHandler(mServiceHandler, /* delayMillis= */ 0);
+
+        verify(mockCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS))
+                .onMonitoringIntervalChanged(TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS);
+
+        verify(mockCallback, never()).onAvailabilityChanged(any());
+
+        mLocalService.removeCpuAvailabilityCallback(mockCallback);
+
+        assertWithMessage("Monitoring interval after removing all client callbacks")
+                .that(mService.getCurrentMonitoringIntervalMillis())
+                .isEqualTo(DEFAULT_MONITORING_INTERVAL_MILLISECONDS);
+    }
+
+    @Test
+    public void testRemoveInvalidCpuAvailabilityCallback() throws Exception {
+        CpuMonitorInternal.CpuAvailabilityCallback mockCallback = mock(
+                CpuMonitorInternal.CpuAvailabilityCallback.class);
+
+        mLocalService.removeCpuAvailabilityCallback(mockCallback);
+    }
+
+    @Test
+    public void testReceiveCpuAvailabilityCallbackOnAddingFirstCallback() throws Exception {
+        // Debug monitoring is in progress but the default {@link CpuInfoReader.CpuInfo} returned by
+        // the {@link CpuInfoReader.readCpuInfos} is empty, so the client won't be notified when
+        // adding a callback. Inject {@link CpuInfoReader.CpuInfo}, so the client callback is
+        // notified on adding a callback.
+        injectCpuInfosAndWait(List.of(
+                generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 10.0f,
+                        NO_OFFLINE_CORES)));
+
+        CpuMonitorInternal.CpuAvailabilityCallback mockCallback =
+                addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_ALL_CPUSET);
+
+        verify(mockCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS))
+                .onAvailabilityChanged(mCpuAvailabilityInfoCaptor.capture());
+
+        List<CpuAvailabilityInfo> actual = mCpuAvailabilityInfoCaptor.getAllValues();
+
+        List<CpuAvailabilityInfo> expected = List.of(
+                new CpuAvailabilityInfo(CPUSET_ALL, actual.get(0).dataTimestampUptimeMillis,
+                        /* latestAvgAvailabilityPercent= */ 10, MISSING_CPU_AVAILABILITY_PERCENT,
+                        TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS));
+
+        assertWithMessage("CPU availability infos").that(actual).isEqualTo(expected);
+    }
+
+    @Test
+    public void testReceiveCpuAvailabilityCallbackOnAddingMultipleCallbacks() throws Exception {
+        addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_BG_CPUSET);
+
+        injectCpuInfosAndWait(List.of(
+                generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 10.0f,
+                        NO_OFFLINE_CORES)));
+
+        CpuMonitorInternal.CpuAvailabilityCallback mockCallback =
+                addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_ALL_CPUSET);
+
+        verify(mockCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS))
+                .onAvailabilityChanged(mCpuAvailabilityInfoCaptor.capture());
+
+        List<CpuAvailabilityInfo> actual = mCpuAvailabilityInfoCaptor.getAllValues();
+
+        List<CpuAvailabilityInfo> expected = List.of(
+                new CpuAvailabilityInfo(CPUSET_ALL, actual.get(0).dataTimestampUptimeMillis,
+                        /* latestAvgAvailabilityPercent= */ 10, MISSING_CPU_AVAILABILITY_PERCENT,
+                        TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS));
+
+        assertWithMessage("CPU availability infos").that(actual).isEqualTo(expected);
+    }
+
+    @Test
+    public void testCrossCpuAvailabilityThresholdsWithSingleCallback() throws Exception {
+        CpuMonitorInternal.CpuAvailabilityCallback mockCallback =
+                addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_ALL_CPUSET);
+
+        injectCpuInfosAndWait(List.of(
+                generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 10.0f,
+                        NO_OFFLINE_CORES),
+                generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 90.0f,
+                        NO_OFFLINE_CORES),
+                generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 15.0f,
+                        NO_OFFLINE_CORES),
+                generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 30.0f,
+                        NO_OFFLINE_CORES),
+                generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 60.0f,
+                        NO_OFFLINE_CORES),
+                generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 82.0f,
+                        NO_OFFLINE_CORES)));
+
+        verify(mockCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS).times(4))
+                .onAvailabilityChanged(mCpuAvailabilityInfoCaptor.capture());
+
+        List<CpuAvailabilityInfo> actual = mCpuAvailabilityInfoCaptor.getAllValues();
+
+        List<CpuAvailabilityInfo> expected = List.of(
+                new CpuAvailabilityInfo(CPUSET_ALL, actual.get(0).dataTimestampUptimeMillis,
+                        /* latestAvgAvailabilityPercent= */ 90, MISSING_CPU_AVAILABILITY_PERCENT,
+                        TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
+                new CpuAvailabilityInfo(CPUSET_ALL, actual.get(1).dataTimestampUptimeMillis,
+                        /* latestAvgAvailabilityPercent= */ 15, MISSING_CPU_AVAILABILITY_PERCENT,
+                        TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
+                new CpuAvailabilityInfo(CPUSET_ALL, actual.get(2).dataTimestampUptimeMillis,
+                        /* latestAvgAvailabilityPercent= */ 30,
+                        /* pastNMillisAvgAvailabilityPercent= */ 45,
+                        TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
+                new CpuAvailabilityInfo(CPUSET_ALL, actual.get(3).dataTimestampUptimeMillis,
+                        /* latestAvgAvailabilityPercent= */ 82,
+                        /* pastNMillisAvgAvailabilityPercent= */ 57,
+                        TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS));
+
+        assertWithMessage("CPU availability infos").that(actual).isEqualTo(expected);
+    }
+
+    @Test
+    public void testCrossCpuAvailabilityThresholdsWithMultipleCallbacks() throws Exception {
+        CpuMonitorInternal.CpuAvailabilityCallback mockAllCpusetCallback =
+                addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_ALL_CPUSET);
+
+        CpuMonitorInternal.CpuAvailabilityCallback mockBgCpusetCallback =
+                addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_BG_CPUSET);
+
+        injectCpuInfosAndWait(List.of(
+                generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 5.0f,
+                        NO_OFFLINE_CORES),
+                generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 20.0f,
+                        NO_OFFLINE_CORES),
+                generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 30.0f,
+                        NO_OFFLINE_CORES),
+                generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 60.0f,
+                        NO_OFFLINE_CORES),
+                generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 75.0f,
+                        NO_OFFLINE_CORES),
+                generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 90.0f,
+                        NO_OFFLINE_CORES),
+                generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 15.0f,
+                        NO_OFFLINE_CORES)));
+
+        verify(mockAllCpusetCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS).times(3))
+                .onAvailabilityChanged(mCpuAvailabilityInfoCaptor.capture());
+
+        List<CpuAvailabilityInfo> actual = mCpuAvailabilityInfoCaptor.getAllValues();
+        List<CpuAvailabilityInfo> expected = List.of(
+                new CpuAvailabilityInfo(CPUSET_ALL, actual.get(0).dataTimestampUptimeMillis,
+                        /* latestAvgAvailabilityPercent= */ 30, MISSING_CPU_AVAILABILITY_PERCENT,
+                        TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
+                new CpuAvailabilityInfo(CPUSET_ALL, actual.get(1).dataTimestampUptimeMillis,
+                        /* latestAvgAvailabilityPercent= */ 75,
+                        /* pastNMillisAvgAvailabilityPercent= */ 55,
+                        TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
+                new CpuAvailabilityInfo(CPUSET_ALL, actual.get(2).dataTimestampUptimeMillis,
+                        /* latestAvgAvailabilityPercent= */ 15,
+                        /* pastNMillisAvgAvailabilityPercent= */ 60,
+                        TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS));
+
+        assertWithMessage("CPU availability infos for CPUSET_ALL callback").that(actual)
+                .isEqualTo(expected);
+
+        ArgumentCaptor<CpuAvailabilityInfo> bgCpusetAvailabilityInfoCaptor =
+                ArgumentCaptor.forClass(CpuAvailabilityInfo.class);
+
+        verify(mockBgCpusetCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS).times(3))
+                .onAvailabilityChanged(bgCpusetAvailabilityInfoCaptor.capture());
+
+        actual = bgCpusetAvailabilityInfoCaptor.getAllValues();
+        expected = List.of(
+                new CpuAvailabilityInfo(CPUSET_BACKGROUND, actual.get(0).dataTimestampUptimeMillis,
+                        /* latestAvgAvailabilityPercent= */ 60,
+                        /* pastNMillisAvgAvailabilityPercent= */ 36,
+                        TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
+                new CpuAvailabilityInfo(CPUSET_BACKGROUND, actual.get(1).dataTimestampUptimeMillis,
+                        /* latestAvgAvailabilityPercent= */ 90,
+                        /* pastNMillisAvgAvailabilityPercent= */ 75,
+                        TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
+                new CpuAvailabilityInfo(CPUSET_BACKGROUND, actual.get(2).dataTimestampUptimeMillis,
+                        /* latestAvgAvailabilityPercent= */ 15,
+                        /* pastNMillisAvgAvailabilityPercent= */ 60,
+                        TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS));
+
+        assertWithMessage("CPU availability infos for CPUSET_BACKGROUND callback").that(actual)
+                .isEqualTo(expected);
+    }
+
+    @Test
+    public void testCrossCpuAvailabilityThresholdsWithOfflineCores() throws Exception {
+        CpuMonitorInternal.CpuAvailabilityCallback mockAllCpusetCallback =
+                addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_ALL_CPUSET);
+
+        CpuMonitorInternal.CpuAvailabilityCallback mockBgCpusetCallback =
+                addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_BG_CPUSET);
+
+        // Disable one top-app and one all cpuset core.
+        ArraySet<Integer> offlineCoresA = new ArraySet<>();
+        offlineCoresA.add(1);
+        offlineCoresA.add(3);
+
+        // Disable two all cpuset cores.
+        ArraySet<Integer> offlineCoresB = new ArraySet<>();
+        offlineCoresB.add(2);
+        offlineCoresB.add(4);
+
+        injectCpuInfosAndWait(List.of(
+                generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 5.0f, offlineCoresA),
+                generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 20.0f, offlineCoresB),
+                generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 30.0f, offlineCoresA),
+                generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 60.0f, offlineCoresB),
+                generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 75.0f, offlineCoresA),
+                generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 90.0f, offlineCoresB),
+                generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 15.0f,
+                        offlineCoresA)));
+
+        verify(mockAllCpusetCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS).times(3))
+                .onAvailabilityChanged(mCpuAvailabilityInfoCaptor.capture());
+
+        List<CpuAvailabilityInfo> actual = mCpuAvailabilityInfoCaptor.getAllValues();
+        List<CpuAvailabilityInfo> expected = List.of(
+                new CpuAvailabilityInfo(CPUSET_ALL, actual.get(0).dataTimestampUptimeMillis,
+                        /* latestAvgAvailabilityPercent= */ 30, MISSING_CPU_AVAILABILITY_PERCENT,
+                        TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
+                new CpuAvailabilityInfo(CPUSET_ALL, actual.get(1).dataTimestampUptimeMillis,
+                        /* latestAvgAvailabilityPercent= */ 75,
+                        /* pastNMillisAvgAvailabilityPercent= */ 55,
+                        TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
+                new CpuAvailabilityInfo(CPUSET_ALL, actual.get(2).dataTimestampUptimeMillis,
+                        /* latestAvgAvailabilityPercent= */ 15,
+                        /* pastNMillisAvgAvailabilityPercent= */ 61,
+                        TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS));
+
+        assertWithMessage("CPU availability infos for CPUSET_ALL callback").that(actual)
+                .isEqualTo(expected);
+
+        ArgumentCaptor<CpuAvailabilityInfo> bgCpusetAvailabilityInfoCaptor =
+                ArgumentCaptor.forClass(CpuAvailabilityInfo.class);
+
+        verify(mockBgCpusetCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS).times(3))
+                .onAvailabilityChanged(bgCpusetAvailabilityInfoCaptor.capture());
+
+        actual = bgCpusetAvailabilityInfoCaptor.getAllValues();
+        expected = List.of(
+                new CpuAvailabilityInfo(CPUSET_BACKGROUND, actual.get(0).dataTimestampUptimeMillis,
+                        /* latestAvgAvailabilityPercent= */ 60,
+                        /* pastNMillisAvgAvailabilityPercent= */ 35,
+                        TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
+                new CpuAvailabilityInfo(CPUSET_BACKGROUND, actual.get(1).dataTimestampUptimeMillis,
+                        /* latestAvgAvailabilityPercent= */ 90,
+                        /* pastNMillisAvgAvailabilityPercent= */ 75,
+                        TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
+                new CpuAvailabilityInfo(CPUSET_BACKGROUND, actual.get(2).dataTimestampUptimeMillis,
+                        /* latestAvgAvailabilityPercent= */ 15,
+                        /* pastNMillisAvgAvailabilityPercent= */ 55,
+                        TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS));
+
+        assertWithMessage("CPU availability infos for CPUSET_BACKGROUND callback").that(actual)
+                .isEqualTo(expected);
+    }
+
+    @Test
+    public void testReceiveCpuAvailabilityCallbacksOnExecutorThread() throws Exception {
+        Handler testHandler = new Handler(Looper.getMainLooper());
+
+        assertWithMessage("Test main handler").that(testHandler).isNotNull();
+
+        HandlerExecutor testExecutor = new HandlerExecutor(testHandler);
+
+        assertWithMessage("Test main executor").that(testExecutor).isNotNull();
+
+        CpuMonitorInternal.CpuAvailabilityCallback mockCallback =
+                addCpuAvailabilityCallback(testHandler, testExecutor,
+                        TEST_MONITORING_CONFIG_ALL_CPUSET);
+
+        // CPU monitoring is started on the service handler thread. Sync with this thread before
+        // proceeding. Otherwise, debug monitoring may consume the injected CPU infos and cause
+        // the test to be flaky. Because the {@link addCpuAvailabilityCallback} syncs only with
+        // the passed handler, the test must explicitly sync with the service handler.
+        syncWithHandler(mServiceHandler, /* delayMillis= */ 0);
+
+        injectCpuInfosAndWait(testHandler, List.of(
+                generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 10.0f,
+                        NO_OFFLINE_CORES),
+                generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 90.0f,
+                        NO_OFFLINE_CORES),
+                generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 15.0f,
+                        NO_OFFLINE_CORES),
+                generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 30.0f,
+                        NO_OFFLINE_CORES),
+                generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 60.0f,
+                        NO_OFFLINE_CORES),
+                generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 82.0f,
+                        NO_OFFLINE_CORES)));
+
+        verify(mockCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS).times(4))
+                .onAvailabilityChanged(mCpuAvailabilityInfoCaptor.capture());
+
+        List<CpuAvailabilityInfo> actual = mCpuAvailabilityInfoCaptor.getAllValues();
+
+        List<CpuAvailabilityInfo> expected = List.of(
+                new CpuAvailabilityInfo(CPUSET_ALL, actual.get(0).dataTimestampUptimeMillis,
+                        /* latestAvgAvailabilityPercent= */ 90, MISSING_CPU_AVAILABILITY_PERCENT,
+                        TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
+                new CpuAvailabilityInfo(CPUSET_ALL, actual.get(1).dataTimestampUptimeMillis,
+                        /* latestAvgAvailabilityPercent= */ 15, MISSING_CPU_AVAILABILITY_PERCENT,
+                        TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
+                new CpuAvailabilityInfo(CPUSET_ALL, actual.get(2).dataTimestampUptimeMillis,
+                        /* latestAvgAvailabilityPercent= */ 30,
+                        /* pastNMillisAvgAvailabilityPercent= */ 45,
+                        TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
+                new CpuAvailabilityInfo(CPUSET_ALL, actual.get(3).dataTimestampUptimeMillis,
+                        /* latestAvgAvailabilityPercent= */ 82,
+                        /* pastNMillisAvgAvailabilityPercent= */ 57,
+                        TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS));
+
+        assertWithMessage("CPU availability infos").that(actual).isEqualTo(expected);
+    }
+
+    @Test
+    public void testDuplicateAddCpuAvailabilityCallback() throws Exception {
+        addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_ALL_CPUSET);
+
+        CpuMonitorInternal.CpuAvailabilityCallback mockCallback =
+                addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_BG_CPUSET);
+
+        injectCpuInfosAndWait(List.of(
+                generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 10.0f,
+                        NO_OFFLINE_CORES),
+                generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 40.0f,
+                        NO_OFFLINE_CORES),
+                generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 60.0f,
+                        NO_OFFLINE_CORES),
+                generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 80.0f,
+                        NO_OFFLINE_CORES),
+                generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 95.0f,
+                        NO_OFFLINE_CORES)));
+
+        verify(mockCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS).times(2))
+                .onAvailabilityChanged(mCpuAvailabilityInfoCaptor.capture());
+
+        List<CpuAvailabilityInfo> actual = mCpuAvailabilityInfoCaptor.getAllValues();
+
+        // Verify that the callback is called for the last added monitoring config.
+        List<CpuAvailabilityInfo> expected = List.of(
+                new CpuAvailabilityInfo(CPUSET_BACKGROUND, actual.get(0).dataTimestampUptimeMillis,
+                        /* latestAvgAvailabilityPercent= */ 60, MISSING_CPU_AVAILABILITY_PERCENT,
+                        TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS),
+                new CpuAvailabilityInfo(CPUSET_BACKGROUND, actual.get(1).dataTimestampUptimeMillis,
+                        /* latestAvgAvailabilityPercent= */ 95,
+                        /* pastNMillisAvgAvailabilityPercent= */ 78,
+                        TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS));
+
+        assertWithMessage("CPU availability infos").that(actual).isEqualTo(expected);
+    }
+
+    @Test
+    public void testHeavyCpuLoadMonitoring() throws Exception {
+        // TODO(b/267500110): Once heavy CPU load detection logic is added, add unittest.
+    }
+
+    private void startService() {
+        mService.onStart();
+        mServiceHandler = mServiceHandlerThread.getThreadHandler();
+
+        assertWithMessage("Service thread handler").that(mServiceHandler).isNotNull();
+
+        mLocalService = LocalServices.getService(CpuMonitorInternal.class);
+
+        assertWithMessage("CpuMonitorInternal local service").that(mLocalService).isNotNull();
+    }
+
+    private void terminateService() {
+        // The CpuMonitorInternal.class service is added by the {@link CpuMonitorService#onStart}
+        // call. Remove the service to ensure this service can be added again during
+        // the {@link CpuMonitorService#onStart} call.
         LocalServices.removeServiceForTest(CpuMonitorInternal.class);
+        if (mServiceHandlerThread != null && mServiceHandlerThread.isAlive()) {
+            mServiceHandlerThread.quitSafely();
+        }
     }
 
-    @Test
-    public void testAddRemoveCpuAvailabilityCallback() {
+    private void replaceServiceWithUserBuildService() {
+        terminateService();
+        mServiceHandlerThread = new HandlerThread(USER_BUILD_TAG);
+        mService = new CpuMonitorService(mMockContext, mMockCpuInfoReader,
+                mServiceHandlerThread, /* shouldDebugMonitor= */ false,
+                TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS,
+                TEST_DEBUG_MONITORING_INTERVAL_MILLISECONDS,
+                TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS);
+
+        startService();
+    }
+
+    private CpuMonitorInternal.CpuAvailabilityCallback addCpuAvailabilityCallback(
+            CpuAvailabilityMonitoringConfig config) throws Exception {
+        return addCpuAvailabilityCallback(mServiceHandler, /* executor= */ null, config);
+    }
+
+    private CpuMonitorInternal.CpuAvailabilityCallback addCpuAvailabilityCallback(Handler handler,
+            HandlerExecutor executor, CpuAvailabilityMonitoringConfig config) throws Exception {
         CpuMonitorInternal.CpuAvailabilityCallback mockCallback = mock(
                 CpuMonitorInternal.CpuAvailabilityCallback.class);
 
-        mLocalService.addCpuAvailabilityCallback(mHandlerExecutor,
-                TEST_CPU_AVAILABILITY_MONITORING_CONFIG, mockCallback);
+        mLocalService.addCpuAvailabilityCallback(executor, config, mockCallback);
 
-        // TODO(b/242722241): Verify that {@link mockCallback.onAvailabilityChanged} and
-        //  {@link mockCallback.onMonitoringIntervalChanged} are called when the callback is added.
+        // Monitoring interval changed notification is sent asynchronously from the given handler.
+        // So, sync with this thread before verifying the client call.
+        syncWithHandler(handler, /* delayMillis= */ 0);
 
-        mLocalService.removeCpuAvailabilityCallback(mockCallback);
+        verify(mockCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS))
+                .onMonitoringIntervalChanged(TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS);
+
+        return mockCallback;
     }
 
-
-    @Test
-    public void testDuplicateAddCpuAvailabilityCallback() {
-        CpuMonitorInternal.CpuAvailabilityCallback mockCallback = mock(
-                CpuMonitorInternal.CpuAvailabilityCallback.class);
-
-        mLocalService.addCpuAvailabilityCallback(mHandlerExecutor,
-                TEST_CPU_AVAILABILITY_MONITORING_CONFIG, mockCallback);
-
-        mLocalService.addCpuAvailabilityCallback(mHandlerExecutor,
-                TEST_CPU_AVAILABILITY_MONITORING_CONFIG_2, mockCallback);
-
-        // TODO(b/242722241): Verify that {@link mockCallback} is called only when CPU availability
-        //  thresholds cross the bounds specified in the
-        //  {@link TEST_CPU_AVAILABILITY_MONITORING_CONFIG_2} config.
-
-        mLocalService.removeCpuAvailabilityCallback(mockCallback);
+    private void injectCpuInfosAndWait(List<SparseArray<CpuInfoReader.CpuInfo>> cpuInfos)
+            throws Exception {
+        injectCpuInfosAndWait(mServiceHandler, cpuInfos);
     }
 
-    @Test
-    public void testRemoveInvalidCpuAvailabilityCallback() {
-        CpuMonitorInternal.CpuAvailabilityCallback mockCallback = mock(
-                CpuMonitorInternal.CpuAvailabilityCallback.class);
+    private void injectCpuInfosAndWait(Handler handler,
+            List<SparseArray<CpuInfoReader.CpuInfo>> cpuInfos) throws Exception {
+        assertWithMessage("CPU info configs").that(cpuInfos).isNotEmpty();
 
-        mLocalService.removeCpuAvailabilityCallback(mockCallback);
+        OngoingStubbing<SparseArray<CpuInfoReader.CpuInfo>> ongoingStubbing =
+                when(mMockCpuInfoReader.readCpuInfos());
+        for (SparseArray<CpuInfoReader.CpuInfo> cpuInfo : cpuInfos) {
+            ongoingStubbing = ongoingStubbing.thenReturn(cpuInfo);
+        }
+
+        // CPU infos are read asynchronously on a separate handler thread. So, wait based on
+        // the current monitoring interval and the number of CPU infos were injected.
+        syncWithHandler(handler,
+                /* delayMillis= */ mService.getCurrentMonitoringIntervalMillis() * cpuInfos.size());
+    }
+
+    private void syncWithHandler(Handler handler, long delayMillis) throws Exception {
+        AtomicBoolean didRun = new AtomicBoolean(false);
+        handler.postDelayed(() -> {
+            synchronized (didRun) {
+                didRun.set(true);
+                didRun.notifyAll();
+            }
+        }, delayMillis);
+        synchronized (didRun) {
+            while (!didRun.get()) {
+                didRun.wait(HANDLER_THREAD_SYNC_TIMEOUT_MILLISECONDS);
+            }
+        }
+    }
+
+    private static SparseArray<CpuInfoReader.CpuInfo> generateCpuInfosForAvailability(
+            double cpuAvailabilityPercent, ArraySet<Integer> offlineCores) {
+        SparseArray<CpuInfoReader.CpuInfo> cpuInfos = new SparseArray<>(STATIC_CPU_INFOS.size());
+        for (StaticCpuInfo staticCpuInfo : STATIC_CPU_INFOS) {
+            boolean isOnline = !offlineCores.contains(staticCpuInfo.cpuCore);
+            cpuInfos.append(staticCpuInfo.cpuCore, constructCpuInfo(staticCpuInfo.cpuCore,
+                    staticCpuInfo.cpusetCategories, isOnline, staticCpuInfo.maxCpuFreqKHz,
+                    cpuAvailabilityPercent));
+        }
+        return cpuInfos;
+    }
+
+    private static CpuInfoReader.CpuInfo constructCpuInfo(int cpuCore,
+            @CpuInfoReader.CpusetCategory int cpusetCategories, boolean isOnline,
+            long maxCpuFreqKHz, double cpuAvailabilityPercent) {
+        long availCpuFreqKHz = (long) (maxCpuFreqKHz * (cpuAvailabilityPercent / 100.0));
+        long curCpuFreqKHz = maxCpuFreqKHz - availCpuFreqKHz;
+        return new CpuInfoReader.CpuInfo(cpuCore, cpusetCategories, isOnline,
+                isOnline ? curCpuFreqKHz : MISSING_FREQUENCY, maxCpuFreqKHz,
+                /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
+                isOnline ? availCpuFreqKHz : MISSING_FREQUENCY,
+                /* latestCpuUsageStats= */ null);
+    }
+
+    private static final class StaticCpuInfo {
+        public final int cpuCore;
+        public final int cpusetCategories;
+        public final int maxCpuFreqKHz;
+
+        StaticCpuInfo(int cpuCore, @CpuInfoReader.CpusetCategory int cpusetCategories,
+                int maxCpuFreqKHz) {
+            this.cpuCore = cpuCore;
+            this.cpusetCategories = cpusetCategories;
+            this.maxCpuFreqKHz = maxCpuFreqKHz;
+        }
+
+        @Override
+        public String toString() {
+            return "StaticCpuInfo{cpuCore=" + cpuCore + ", cpusetCategories=" + cpusetCategories
+                    + ", maxCpuFreqKHz=" + maxCpuFreqKHz + '}';
+        }
     }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
index 1ced95b..51dcc03 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -148,6 +148,8 @@
                 mCdsiMock).when(() -> LocalServices.getService(
                 ColorDisplayService.ColorDisplayServiceInternal.class));
         doAnswer((Answer<Void>) invocationOnMock -> null).when(BatteryStatsService::getService);
+        doAnswer((Answer<Boolean>) invocationOnMock -> true).when(() ->
+                Settings.System.putFloatForUser(any(), any(), anyFloat(), anyInt()));
 
         setUpSensors();
         mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
@@ -287,73 +289,6 @@
                 eq(mProxSensor), anyInt(), any(Handler.class));
     }
 
-    /**
-     * Creates a mock and registers it to {@link LocalServices}.
-     */
-    private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
-        LocalServices.removeServiceForTest(clazz);
-        LocalServices.addService(clazz, mock);
-    }
-
-    private void advanceTime(long timeMs) {
-        mClock.fastForward(timeMs);
-        mTestLooper.dispatchAll();
-    }
-
-    private void setUpSensors() throws Exception {
-        mProxSensor = TestUtils.createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY,
-                PROX_SENSOR_MAX_RANGE);
-        Sensor screenOffBrightnessSensor = TestUtils.createSensor(
-                Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
-        when(mSensorManagerMock.getSensorList(eq(Sensor.TYPE_ALL)))
-                .thenReturn(List.of(mProxSensor, screenOffBrightnessSensor));
-    }
-
-    private SensorEventListener getSensorEventListener(Sensor sensor) {
-        verify(mSensorManagerMock).registerListener(mSensorEventListenerCaptor.capture(),
-                eq(sensor), eq(SensorManager.SENSOR_DELAY_NORMAL), isA(Handler.class));
-        return mSensorEventListenerCaptor.getValue();
-    }
-
-    private void setUpDisplay(int displayId, String uniqueId, LogicalDisplay logicalDisplayMock,
-            DisplayDevice displayDeviceMock, DisplayDeviceConfig displayDeviceConfigMock,
-            boolean isEnabled) {
-        DisplayInfo info = new DisplayInfo();
-        DisplayDeviceInfo deviceInfo = new DisplayDeviceInfo();
-        deviceInfo.uniqueId = uniqueId;
-
-        when(logicalDisplayMock.getDisplayIdLocked()).thenReturn(displayId);
-        when(logicalDisplayMock.getPrimaryDisplayDeviceLocked()).thenReturn(displayDeviceMock);
-        when(logicalDisplayMock.getDisplayInfoLocked()).thenReturn(info);
-        when(logicalDisplayMock.isEnabledLocked()).thenReturn(isEnabled);
-        when(logicalDisplayMock.isInTransitionLocked()).thenReturn(false);
-        when(logicalDisplayMock.getBrightnessThrottlingDataIdLocked()).thenReturn(
-                DisplayDeviceConfig.DEFAULT_ID);
-        when(displayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo);
-        when(displayDeviceMock.getUniqueId()).thenReturn(uniqueId);
-        when(displayDeviceMock.getDisplayDeviceConfig()).thenReturn(displayDeviceConfigMock);
-        when(displayDeviceConfigMock.getProximitySensor()).thenReturn(
-                new DisplayDeviceConfig.SensorData() {
-                    {
-                        type = Sensor.STRING_TYPE_PROXIMITY;
-                        name = null;
-                    }
-                });
-        when(displayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500});
-        when(displayDeviceConfigMock.isAutoBrightnessAvailable()).thenReturn(true);
-        when(displayDeviceConfigMock.getAmbientLightSensor()).thenReturn(
-                new DisplayDeviceConfig.SensorData());
-        when(displayDeviceConfigMock.getScreenOffBrightnessSensor()).thenReturn(
-                new DisplayDeviceConfig.SensorData() {
-                    {
-                        type = Sensor.STRING_TYPE_LIGHT;
-                        name = null;
-                    }
-                });
-        when(displayDeviceConfigMock.getScreenOffBrightnessSensorValueToLux())
-                .thenReturn(new int[0]);
-    }
-
     @Test
     public void testDisplayBrightnessFollowers_BothDpcsSupportNits() {
         DisplayPowerControllerHolder followerDpc =
@@ -482,6 +417,32 @@
         verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
     }
 
+    @Test
+    public void testDisplayBrightnessFollowers_AutomaticBrightness() {
+        doAnswer((Answer<Integer>) invocationOnMock ->
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC)
+                .when(() -> Settings.System.getIntForUser(any(ContentResolver.class),
+                        eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
+                        eq(UserHandle.USER_CURRENT)));
+        final float brightness = 0.4f;
+        final float nits = 300;
+        final float ambientLux = 3000;
+        when(mHolder.automaticBrightnessController.getRawAutomaticScreenBrightness())
+                .thenReturn(brightness);
+        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness())
+                .thenReturn(0.3f);
+        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+        when(mHolder.automaticBrightnessController.getAmbientLux()).thenReturn(ambientLux);
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        DisplayPowerController followerDpc = mock(DisplayPowerController.class);
+
+        mHolder.dpc.addDisplayBrightnessFollower(followerDpc);
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(followerDpc).setBrightnessToFollow(brightness, nits, ambientLux);
+    }
 
     @Test
     public void testDisplayBrightnessFollowersRemoval() {
@@ -750,6 +711,73 @@
         verify(mHolder.animator, times(2)).animateTo(eq(newBrightness), anyFloat(), anyFloat());
     }
 
+    /**
+     * Creates a mock and registers it to {@link LocalServices}.
+     */
+    private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
+        LocalServices.removeServiceForTest(clazz);
+        LocalServices.addService(clazz, mock);
+    }
+
+    private void advanceTime(long timeMs) {
+        mClock.fastForward(timeMs);
+        mTestLooper.dispatchAll();
+    }
+
+    private void setUpSensors() throws Exception {
+        mProxSensor = TestUtils.createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY,
+                PROX_SENSOR_MAX_RANGE);
+        Sensor screenOffBrightnessSensor = TestUtils.createSensor(
+                Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
+        when(mSensorManagerMock.getSensorList(eq(Sensor.TYPE_ALL)))
+                .thenReturn(List.of(mProxSensor, screenOffBrightnessSensor));
+    }
+
+    private SensorEventListener getSensorEventListener(Sensor sensor) {
+        verify(mSensorManagerMock).registerListener(mSensorEventListenerCaptor.capture(),
+                eq(sensor), eq(SensorManager.SENSOR_DELAY_NORMAL), isA(Handler.class));
+        return mSensorEventListenerCaptor.getValue();
+    }
+
+    private void setUpDisplay(int displayId, String uniqueId, LogicalDisplay logicalDisplayMock,
+            DisplayDevice displayDeviceMock, DisplayDeviceConfig displayDeviceConfigMock,
+            boolean isEnabled) {
+        DisplayInfo info = new DisplayInfo();
+        DisplayDeviceInfo deviceInfo = new DisplayDeviceInfo();
+        deviceInfo.uniqueId = uniqueId;
+
+        when(logicalDisplayMock.getDisplayIdLocked()).thenReturn(displayId);
+        when(logicalDisplayMock.getPrimaryDisplayDeviceLocked()).thenReturn(displayDeviceMock);
+        when(logicalDisplayMock.getDisplayInfoLocked()).thenReturn(info);
+        when(logicalDisplayMock.isEnabledLocked()).thenReturn(isEnabled);
+        when(logicalDisplayMock.isInTransitionLocked()).thenReturn(false);
+        when(logicalDisplayMock.getBrightnessThrottlingDataIdLocked()).thenReturn(
+                DisplayDeviceConfig.DEFAULT_ID);
+        when(displayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo);
+        when(displayDeviceMock.getUniqueId()).thenReturn(uniqueId);
+        when(displayDeviceMock.getDisplayDeviceConfig()).thenReturn(displayDeviceConfigMock);
+        when(displayDeviceConfigMock.getProximitySensor()).thenReturn(
+                new DisplayDeviceConfig.SensorData() {
+                    {
+                        type = Sensor.STRING_TYPE_PROXIMITY;
+                        name = null;
+                    }
+                });
+        when(displayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500});
+        when(displayDeviceConfigMock.isAutoBrightnessAvailable()).thenReturn(true);
+        when(displayDeviceConfigMock.getAmbientLightSensor()).thenReturn(
+                new DisplayDeviceConfig.SensorData());
+        when(displayDeviceConfigMock.getScreenOffBrightnessSensor()).thenReturn(
+                new DisplayDeviceConfig.SensorData() {
+                    {
+                        type = Sensor.STRING_TYPE_LIGHT;
+                        name = null;
+                    }
+                });
+        when(displayDeviceConfigMock.getScreenOffBrightnessSensorValueToLux())
+                .thenReturn(new int[0]);
+    }
+
     private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
             String uniqueId) {
         return createDisplayPowerController(displayId, uniqueId, /* isEnabled= */ true);
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
index 53fcdad..0a1bf1c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -149,6 +149,8 @@
                 mCdsiMock).when(() -> LocalServices.getService(
                 ColorDisplayService.ColorDisplayServiceInternal.class));
         doAnswer((Answer<Void>) invocationOnMock -> null).when(BatteryStatsService::getService);
+        doAnswer((Answer<Boolean>) invocationOnMock -> true).when(() ->
+                Settings.System.putFloatForUser(any(), any(), anyFloat(), anyInt()));
 
         setUpSensors();
         mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
@@ -290,73 +292,6 @@
                 eq(mProxSensor), anyInt(), any(Handler.class));
     }
 
-    /**
-     * Creates a mock and registers it to {@link LocalServices}.
-     */
-    private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
-        LocalServices.removeServiceForTest(clazz);
-        LocalServices.addService(clazz, mock);
-    }
-
-    private void advanceTime(long timeMs) {
-        mClock.fastForward(timeMs);
-        mTestLooper.dispatchAll();
-    }
-
-    private void setUpSensors() throws Exception {
-        mProxSensor = TestUtils.createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY,
-                PROX_SENSOR_MAX_RANGE);
-        Sensor screenOffBrightnessSensor = TestUtils.createSensor(
-                Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
-        when(mSensorManagerMock.getSensorList(eq(Sensor.TYPE_ALL)))
-                .thenReturn(List.of(mProxSensor, screenOffBrightnessSensor));
-    }
-
-    private SensorEventListener getSensorEventListener(Sensor sensor) {
-        verify(mSensorManagerMock).registerListener(mSensorEventListenerCaptor.capture(),
-                eq(sensor), eq(SensorManager.SENSOR_DELAY_NORMAL), isA(Handler.class));
-        return mSensorEventListenerCaptor.getValue();
-    }
-
-    private void setUpDisplay(int displayId, String uniqueId, LogicalDisplay logicalDisplayMock,
-            DisplayDevice displayDeviceMock, DisplayDeviceConfig displayDeviceConfigMock,
-            boolean isEnabled) {
-        DisplayInfo info = new DisplayInfo();
-        DisplayDeviceInfo deviceInfo = new DisplayDeviceInfo();
-        deviceInfo.uniqueId = uniqueId;
-
-        when(logicalDisplayMock.getDisplayIdLocked()).thenReturn(displayId);
-        when(logicalDisplayMock.getPrimaryDisplayDeviceLocked()).thenReturn(displayDeviceMock);
-        when(logicalDisplayMock.getDisplayInfoLocked()).thenReturn(info);
-        when(logicalDisplayMock.isEnabledLocked()).thenReturn(isEnabled);
-        when(logicalDisplayMock.isInTransitionLocked()).thenReturn(false);
-        when(logicalDisplayMock.getBrightnessThrottlingDataIdLocked()).thenReturn(
-                DisplayDeviceConfig.DEFAULT_ID);
-        when(displayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo);
-        when(displayDeviceMock.getUniqueId()).thenReturn(uniqueId);
-        when(displayDeviceMock.getDisplayDeviceConfig()).thenReturn(displayDeviceConfigMock);
-        when(displayDeviceConfigMock.getProximitySensor()).thenReturn(
-                new DisplayDeviceConfig.SensorData() {
-                    {
-                        type = Sensor.STRING_TYPE_PROXIMITY;
-                        name = null;
-                    }
-                });
-        when(displayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500});
-        when(displayDeviceConfigMock.isAutoBrightnessAvailable()).thenReturn(true);
-        when(displayDeviceConfigMock.getAmbientLightSensor()).thenReturn(
-                new DisplayDeviceConfig.SensorData());
-        when(displayDeviceConfigMock.getScreenOffBrightnessSensor()).thenReturn(
-                new DisplayDeviceConfig.SensorData() {
-                    {
-                        type = Sensor.STRING_TYPE_LIGHT;
-                        name = null;
-                    }
-                });
-        when(displayDeviceConfigMock.getScreenOffBrightnessSensorValueToLux())
-                .thenReturn(new int[0]);
-    }
-
     @Test
     public void testDisplayBrightnessFollowers_BothDpcsSupportNits() {
         DisplayPowerControllerHolder followerDpc =
@@ -486,6 +421,33 @@
     }
 
     @Test
+    public void testDisplayBrightnessFollowers_AutomaticBrightness() {
+        doAnswer((Answer<Integer>) invocationOnMock ->
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC)
+                .when(() -> Settings.System.getIntForUser(any(ContentResolver.class),
+                        eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
+                        eq(UserHandle.USER_CURRENT)));
+        final float brightness = 0.4f;
+        final float nits = 300;
+        final float ambientLux = 3000;
+        when(mHolder.automaticBrightnessController.getRawAutomaticScreenBrightness())
+                .thenReturn(brightness);
+        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness())
+                .thenReturn(0.3f);
+        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+        when(mHolder.automaticBrightnessController.getAmbientLux()).thenReturn(ambientLux);
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        DisplayPowerController followerDpc = mock(DisplayPowerController.class);
+
+        mHolder.dpc.addDisplayBrightnessFollower(followerDpc);
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(followerDpc).setBrightnessToFollow(brightness, nits, ambientLux);
+    }
+
+    @Test
     public void testDisplayBrightnessFollowersRemoval() {
         DisplayPowerControllerHolder followerHolder =
                 createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_DISPLAY_ID);
@@ -517,7 +479,6 @@
         verify(followerHolder.animator).animateTo(eq(initialFollowerBrightness),
                 anyFloat(), anyFloat());
 
-
         mHolder.dpc.addDisplayBrightnessFollower(followerHolder.dpc);
         mHolder.dpc.addDisplayBrightnessFollower(secondFollowerHolder.dpc);
         clearInvocations(followerHolder.animator);
@@ -754,6 +715,73 @@
         verify(mHolder.animator, times(2)).animateTo(eq(newBrightness), anyFloat(), anyFloat());
     }
 
+    /**
+     * Creates a mock and registers it to {@link LocalServices}.
+     */
+    private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
+        LocalServices.removeServiceForTest(clazz);
+        LocalServices.addService(clazz, mock);
+    }
+
+    private void advanceTime(long timeMs) {
+        mClock.fastForward(timeMs);
+        mTestLooper.dispatchAll();
+    }
+
+    private void setUpSensors() throws Exception {
+        mProxSensor = TestUtils.createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY,
+                PROX_SENSOR_MAX_RANGE);
+        Sensor screenOffBrightnessSensor = TestUtils.createSensor(
+                Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
+        when(mSensorManagerMock.getSensorList(eq(Sensor.TYPE_ALL)))
+                .thenReturn(List.of(mProxSensor, screenOffBrightnessSensor));
+    }
+
+    private SensorEventListener getSensorEventListener(Sensor sensor) {
+        verify(mSensorManagerMock).registerListener(mSensorEventListenerCaptor.capture(),
+                eq(sensor), eq(SensorManager.SENSOR_DELAY_NORMAL), isA(Handler.class));
+        return mSensorEventListenerCaptor.getValue();
+    }
+
+    private void setUpDisplay(int displayId, String uniqueId, LogicalDisplay logicalDisplayMock,
+            DisplayDevice displayDeviceMock, DisplayDeviceConfig displayDeviceConfigMock,
+            boolean isEnabled) {
+        DisplayInfo info = new DisplayInfo();
+        DisplayDeviceInfo deviceInfo = new DisplayDeviceInfo();
+        deviceInfo.uniqueId = uniqueId;
+
+        when(logicalDisplayMock.getDisplayIdLocked()).thenReturn(displayId);
+        when(logicalDisplayMock.getPrimaryDisplayDeviceLocked()).thenReturn(displayDeviceMock);
+        when(logicalDisplayMock.getDisplayInfoLocked()).thenReturn(info);
+        when(logicalDisplayMock.isEnabledLocked()).thenReturn(isEnabled);
+        when(logicalDisplayMock.isInTransitionLocked()).thenReturn(false);
+        when(logicalDisplayMock.getBrightnessThrottlingDataIdLocked()).thenReturn(
+                DisplayDeviceConfig.DEFAULT_ID);
+        when(displayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo);
+        when(displayDeviceMock.getUniqueId()).thenReturn(uniqueId);
+        when(displayDeviceMock.getDisplayDeviceConfig()).thenReturn(displayDeviceConfigMock);
+        when(displayDeviceConfigMock.getProximitySensor()).thenReturn(
+                new DisplayDeviceConfig.SensorData() {
+                    {
+                        type = Sensor.STRING_TYPE_PROXIMITY;
+                        name = null;
+                    }
+                });
+        when(displayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500});
+        when(displayDeviceConfigMock.isAutoBrightnessAvailable()).thenReturn(true);
+        when(displayDeviceConfigMock.getAmbientLightSensor()).thenReturn(
+                new DisplayDeviceConfig.SensorData());
+        when(displayDeviceConfigMock.getScreenOffBrightnessSensor()).thenReturn(
+                new DisplayDeviceConfig.SensorData() {
+                    {
+                        type = Sensor.STRING_TYPE_LIGHT;
+                        name = null;
+                    }
+                });
+        when(displayDeviceConfigMock.getScreenOffBrightnessSensorValueToLux())
+                .thenReturn(new int[0]);
+    }
+
     private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
             String uniqueId) {
         return createDisplayPowerController(displayId, uniqueId, /* isEnabled= */ true);
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..ba70c584 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,10 @@
         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_GUARANTEE_MS = 2 * 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_LIMIT_MS = 6 * HOUR_IN_MILLIS;
 
         assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
                 mService.getMinJobExecutionGuaranteeMs(ejMax));
@@ -278,27 +284,31 @@
         // Permission isn't granted, so it should just be treated as a regular job.
         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,
-                mService.getMinJobExecutionGuaranteeMs(jobUIDT));
+        mService.mConstants.RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS = true;
         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)
+        final long estimatedTransferTimeMs =
+                mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS * 2;
+        doReturn(estimatedTransferTimeMs)
                 .when(connectivityController).getEstimatedTransferTimeMs(any());
-        assertEquals(
-                (long) (mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS
-                        * 2 * mService.mConstants
-                                .RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR),
+        assertEquals((long) (estimatedTransferTimeMs
+                        * mService.mConstants.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_LIMIT_MS * 2)
                 .when(connectivityController).getEstimatedTransferTimeMs(any());
-        assertEquals(mService.mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS,
+        assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobUIDT));
+
+        mService.mConstants.RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS = false;
+        assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
                 mService.getMinJobExecutionGuaranteeMs(jobUIDT));
     }
 
@@ -320,7 +330,7 @@
                 .when(quotaController).getMaxJobExecutionTimeMsLocked(any());
 
         grantRunUserInitiatedJobsPermission(true);
-        assertEquals(mService.mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS,
+        assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
                 mService.getMaxJobExecutionTimeMs(jobUIDT));
         grantRunUserInitiatedJobsPermission(false);
         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
@@ -1170,7 +1180,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 +1201,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 +1222,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 +1246,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/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
index 32e5c83..a3ae834 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
@@ -840,8 +840,9 @@
         final JobStatus blue = createJobStatus(createJob()
                 .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
                 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY), UID_BLUE);
+        // Unmetered preference is disabled for now.
         assertFalse(red.getPreferUnmetered());
-        assertTrue(blue.getPreferUnmetered());
+        assertFalse(blue.getPreferUnmetered());
 
         controller.maybeStartTrackingJobLocked(red, null);
         controller.maybeStartTrackingJobLocked(blue, null);
@@ -895,7 +896,7 @@
             generalCallback.onLost(meteredNet);
 
             assertTrue(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
-            assertFalse(red.getHasAccessToUnmetered());
+            assertTrue(red.getHasAccessToUnmetered());
 
             assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
             assertTrue(blue.getHasAccessToUnmetered());
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
index 6bc552c..4b19bbb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
@@ -189,7 +189,10 @@
     }
 
     private static JobInfo.Builder createJob(int id) {
-        return new JobInfo.Builder(id, new ComponentName("foo", "bar"));
+        return new JobInfo.Builder(id, new ComponentName("foo", "bar"))
+                .setPrefersBatteryNotLow(true)
+                .setPrefersCharging(true)
+                .setPrefersDeviceIdle(true);
     }
 
     private JobStatus createJobStatus(String testTag, JobInfo.Builder job) {
@@ -533,12 +536,15 @@
             jb = createJob(i);
             if (i > 0) {
                 jb.setRequiresDeviceIdle(true);
+                jb.setPrefersDeviceIdle(false);
             }
             if (i > 1) {
                 jb.setRequiresBatteryNotLow(true);
+                jb.setPrefersBatteryNotLow(false);
             }
             if (i > 2) {
                 jb.setRequiresCharging(true);
+                jb.setPrefersCharging(false);
             }
             jobs[i] = createJobStatus("", jb);
             flexTracker.add(jobs[i]);
@@ -547,53 +553,55 @@
         synchronized (mFlexibilityController.mLock) {
             ArrayList<ArraySet<JobStatus>> trackedJobs = flexTracker.getArrayList();
             assertEquals(1, trackedJobs.get(0).size());
-            assertEquals(0, trackedJobs.get(1).size());
-            assertEquals(0, trackedJobs.get(2).size());
-            assertEquals(3, trackedJobs.get(3).size());
-            assertEquals(0, trackedJobs.get(4).size());
-
-            flexTracker.adjustJobsRequiredConstraints(jobs[0], -1, FROZEN_TIME);
-            assertEquals(1, trackedJobs.get(0).size());
-            assertEquals(0, trackedJobs.get(1).size());
+            assertEquals(1, trackedJobs.get(1).size());
             assertEquals(1, trackedJobs.get(2).size());
-            assertEquals(2, trackedJobs.get(3).size());
+            assertEquals(1, trackedJobs.get(3).size());
             assertEquals(0, trackedJobs.get(4).size());
 
             flexTracker.adjustJobsRequiredConstraints(jobs[0], -1, FROZEN_TIME);
             assertEquals(1, trackedJobs.get(0).size());
             assertEquals(1, trackedJobs.get(1).size());
-            assertEquals(0, trackedJobs.get(2).size());
-            assertEquals(2, trackedJobs.get(3).size());
+            assertEquals(2, trackedJobs.get(2).size());
+            assertEquals(0, trackedJobs.get(3).size());
+            assertEquals(0, trackedJobs.get(4).size());
+
+            flexTracker.adjustJobsRequiredConstraints(jobs[0], -1, FROZEN_TIME);
+            assertEquals(1, trackedJobs.get(0).size());
+            assertEquals(2, trackedJobs.get(1).size());
+            assertEquals(1, trackedJobs.get(2).size());
+            assertEquals(0, trackedJobs.get(3).size());
             assertEquals(0, trackedJobs.get(4).size());
 
             flexTracker.adjustJobsRequiredConstraints(jobs[0], -1, FROZEN_TIME);
             assertEquals(2, trackedJobs.get(0).size());
-            assertEquals(0, trackedJobs.get(1).size());
-            assertEquals(0, trackedJobs.get(2).size());
-            assertEquals(2, trackedJobs.get(3).size());
+            assertEquals(1, trackedJobs.get(1).size());
+            assertEquals(1, trackedJobs.get(2).size());
+            assertEquals(0, trackedJobs.get(3).size());
             assertEquals(0, trackedJobs.get(4).size());
 
             flexTracker.remove(jobs[1]);
             assertEquals(2, trackedJobs.get(0).size());
-            assertEquals(0, trackedJobs.get(1).size());
+            assertEquals(1, trackedJobs.get(1).size());
             assertEquals(0, trackedJobs.get(2).size());
-            assertEquals(1, trackedJobs.get(3).size());
+            assertEquals(0, trackedJobs.get(3).size());
             assertEquals(0, trackedJobs.get(4).size());
 
             flexTracker.resetJobNumDroppedConstraints(jobs[0], FROZEN_TIME);
             assertEquals(1, trackedJobs.get(0).size());
-            assertEquals(0, trackedJobs.get(1).size());
-            assertEquals(0, trackedJobs.get(2).size());
-            assertEquals(2, trackedJobs.get(3).size());
-            assertEquals(0, trackedJobs.get(4).size());
-
-            flexTracker.adjustJobsRequiredConstraints(jobs[0], -2, FROZEN_TIME);
-            assertEquals(1, trackedJobs.get(0).size());
             assertEquals(1, trackedJobs.get(1).size());
             assertEquals(0, trackedJobs.get(2).size());
             assertEquals(1, trackedJobs.get(3).size());
             assertEquals(0, trackedJobs.get(4).size());
 
+            flexTracker.adjustJobsRequiredConstraints(jobs[0], -2, FROZEN_TIME);
+            assertEquals(1, trackedJobs.get(0).size());
+            assertEquals(2, trackedJobs.get(1).size());
+            assertEquals(0, trackedJobs.get(2).size());
+            assertEquals(0, trackedJobs.get(3).size());
+            assertEquals(0, trackedJobs.get(4).size());
+
+            // Over halfway through the flex window. The job that prefers all flex constraints
+            // should have its first flex constraint dropped.
             final long nowElapsed = ((DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS / 2)
                     + HOUR_IN_MILLIS);
             JobSchedulerService.sElapsedRealtimeClock =
@@ -601,9 +609,9 @@
 
             flexTracker.resetJobNumDroppedConstraints(jobs[0], nowElapsed);
             assertEquals(1, trackedJobs.get(0).size());
-            assertEquals(0, trackedJobs.get(1).size());
+            assertEquals(1, trackedJobs.get(1).size());
             assertEquals(1, trackedJobs.get(2).size());
-            assertEquals(1, trackedJobs.get(3).size());
+            assertEquals(0, trackedJobs.get(3).size());
             assertEquals(0, trackedJobs.get(4).size());
         }
     }
@@ -618,8 +626,13 @@
 
     @Test
     public void testExceptions_UserInitiated() {
-        JobInfo.Builder jb = createJob(0);
-        jb.setUserInitiated(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
+        JobInfo.Builder jb = createJob(0)
+                .setUserInitiated(true)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+                // Attempt to add flex constraints to the job. For now, we will ignore them.
+                .setPrefersBatteryNotLow(true)
+                .setPrefersCharging(true)
+                .setPrefersDeviceIdle(false);
         JobStatus js = createJobStatus("testExceptions_UserInitiated", jb);
         assertFalse(js.hasFlexibilityConstraint());
     }
@@ -635,10 +648,10 @@
 
     @Test
     public void testExceptions_NoFlexibleConstraints() {
-        JobInfo.Builder jb = createJob(0);
-        jb.setRequiresDeviceIdle(true);
-        jb.setRequiresCharging(true);
-        jb.setRequiresBatteryNotLow(true);
+        JobInfo.Builder jb = createJob(0)
+                .setPrefersBatteryNotLow(false)
+                .setPrefersCharging(false)
+                .setPrefersDeviceIdle(false);
         JobStatus js = createJobStatus("testExceptions_NoFlexibleConstraints", jb);
         assertFalse(js.hasFlexibilityConstraint());
     }
@@ -697,15 +710,50 @@
         JobStatus js = createJobStatus("testTopAppBypass", jb);
         synchronized (mFlexibilityController.mLock) {
             js.setHasAccessToUnmetered(false);
-            assertEquals(0, mFlexibilityController.getNumSatisfiedRequiredConstraintsLocked(js));
+            assertEquals(0, mFlexibilityController.getNumSatisfiedFlexibleConstraintsLocked(js));
             js.setHasAccessToUnmetered(true);
-            assertEquals(1, mFlexibilityController.getNumSatisfiedRequiredConstraintsLocked(js));
+            assertEquals(1, mFlexibilityController.getNumSatisfiedFlexibleConstraintsLocked(js));
             js.setHasAccessToUnmetered(false);
-            assertEquals(0, mFlexibilityController.getNumSatisfiedRequiredConstraintsLocked(js));
+            assertEquals(0, mFlexibilityController.getNumSatisfiedFlexibleConstraintsLocked(js));
         }
     }
 
     @Test
+    public void testGetNumSatisfiedFlexibleConstraints() {
+        long nowElapsed = FROZEN_TIME;
+        mFlexibilityController.setConstraintSatisfied(CONSTRAINT_BATTERY_NOT_LOW, true, nowElapsed);
+        mFlexibilityController.setConstraintSatisfied(CONSTRAINT_CHARGING, true, nowElapsed);
+        mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, true, nowElapsed);
+        JobInfo.Builder jb = createJob(0)
+                .setPrefersBatteryNotLow(false)
+                .setPrefersCharging(false)
+                .setPrefersDeviceIdle(false);
+        JobStatus js = createJobStatus("testGetNumSatisfiedFlexibleConstraints", jb);
+        assertEquals(0, mFlexibilityController.getNumSatisfiedFlexibleConstraintsLocked(js));
+
+        jb = createJob(0)
+                .setPrefersBatteryNotLow(true)
+                .setPrefersCharging(false)
+                .setPrefersDeviceIdle(false);
+        js = createJobStatus("testGetNumSatisfiedFlexibleConstraints", jb);
+        assertEquals(1, mFlexibilityController.getNumSatisfiedFlexibleConstraintsLocked(js));
+
+        jb = createJob(0)
+                .setPrefersBatteryNotLow(true)
+                .setPrefersCharging(false)
+                .setPrefersDeviceIdle(true);
+        js = createJobStatus("testGetNumSatisfiedFlexibleConstraints", jb);
+        assertEquals(2, mFlexibilityController.getNumSatisfiedFlexibleConstraintsLocked(js));
+
+        jb = createJob(0)
+                .setPrefersBatteryNotLow(true)
+                .setPrefersCharging(true)
+                .setPrefersDeviceIdle(true);
+        js = createJobStatus("testGetNumSatisfiedFlexibleConstraints", jb);
+        assertEquals(3, mFlexibilityController.getNumSatisfiedFlexibleConstraintsLocked(js));
+    }
+
+    @Test
     public void testSetConstraintSatisfied_Constraints() {
         mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, false, FROZEN_TIME);
         assertFalse(mFlexibilityController.isConstraintSatisfied(CONSTRAINT_IDLE));
@@ -736,8 +784,11 @@
             jb = createJob(i);
             constraints = constraintCombinations[i];
             jb.setRequiresDeviceIdle((constraints & CONSTRAINT_IDLE) != 0);
+            jb.setPrefersDeviceIdle((constraints & CONSTRAINT_IDLE) == 0);
             jb.setRequiresBatteryNotLow((constraints & CONSTRAINT_BATTERY_NOT_LOW) != 0);
+            jb.setPrefersBatteryNotLow((constraints & CONSTRAINT_BATTERY_NOT_LOW) == 0);
             jb.setRequiresCharging((constraints & CONSTRAINT_CHARGING) != 0);
+            jb.setPrefersCharging((constraints & CONSTRAINT_CHARGING) == 0);
             synchronized (mFlexibilityController.mLock) {
                 mFlexibilityController.maybeStartTrackingJobLocked(
                         createJobStatus(String.valueOf(i), jb), null);
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
index 5dc8ed5..b076ab4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
@@ -1039,7 +1039,9 @@
     @Test
     public void testReadinessStatusWithConstraint_FlexibilityConstraint() {
         final JobStatus job = createJobStatus(
-                new JobInfo.Builder(101, new ComponentName("foo", "bar")).build());
+                new JobInfo.Builder(101, new ComponentName("foo", "bar"))
+                        .setPrefersCharging(true)
+                        .build());
         job.setConstraintSatisfied(CONSTRAINT_FLEXIBLE, sElapsedRealtimeClock.millis(), false);
         markImplicitConstraintsSatisfied(job, true);
         assertTrue(job.readinessStatusWithConstraint(CONSTRAINT_FLEXIBLE, true));
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java
new file mode 100644
index 0000000..fd9dfe8
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.gnss;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.location.GnssMeasurementRequest;
+import android.location.IGnssMeasurementsListener;
+import android.location.LocationManager;
+import android.location.LocationManagerInternal;
+import android.location.util.identity.CallerIdentity;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+import com.android.server.location.gnss.hal.FakeGnssHal;
+import com.android.server.location.gnss.hal.GnssNative;
+import com.android.server.location.injector.FakeUserInfoHelper;
+import com.android.server.location.injector.Injector;
+import com.android.server.location.injector.TestInjector;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Objects;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class GnssMeasurementsProviderTest {
+    private static final int CURRENT_USER = FakeUserInfoHelper.DEFAULT_USERID;
+    private static final CallerIdentity IDENTITY = CallerIdentity.forTest(CURRENT_USER, 1000,
+            "mypackage", "attribution", "listener");
+    private static final GnssConfiguration.HalInterfaceVersion HIDL_V2_1 =
+            new GnssConfiguration.HalInterfaceVersion(
+                    2, 1);
+    private static final GnssConfiguration.HalInterfaceVersion AIDL_V3 =
+            new GnssConfiguration.HalInterfaceVersion(
+                    GnssConfiguration.HalInterfaceVersion.AIDL_INTERFACE, 3);
+    private static final GnssMeasurementRequest ACTIVE_REQUEST =
+            new GnssMeasurementRequest.Builder().build();
+    private static final GnssMeasurementRequest PASSIVE_REQUEST =
+            new GnssMeasurementRequest.Builder().setIntervalMillis(
+                    GnssMeasurementRequest.PASSIVE_INTERVAL).build();
+    private @Mock Context mContext;
+    private @Mock LocationManagerInternal mInternal;
+    private @Mock GnssConfiguration mMockConfiguration;
+    private @Mock GnssNative.GeofenceCallbacks mGeofenceCallbacks;
+    private @Mock IGnssMeasurementsListener mListener1;
+    private @Mock IGnssMeasurementsListener mListener2;
+    private @Mock IBinder mBinder1;
+    private @Mock IBinder mBinder2;
+
+    private GnssNative mGnssNative;
+
+    private GnssMeasurementsProvider mTestProvider;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        doReturn(mBinder1).when(mListener1).asBinder();
+        doReturn(mBinder2).when(mListener2).asBinder();
+        doReturn(true).when(mInternal).isProviderEnabledForUser(eq(LocationManager.GPS_PROVIDER),
+                anyInt());
+        LocalServices.addService(LocationManagerInternal.class, mInternal);
+        FakeGnssHal fakeGnssHal = new FakeGnssHal();
+        GnssNative.setGnssHalForTest(fakeGnssHal);
+        Injector injector = new TestInjector(mContext);
+        mGnssNative = spy(Objects.requireNonNull(
+                GnssNative.create(injector, mMockConfiguration)));
+        mGnssNative.setGeofenceCallbacks(mGeofenceCallbacks);
+        mTestProvider = new GnssMeasurementsProvider(injector, mGnssNative);
+        mGnssNative.register();
+    }
+
+    @After
+    public void tearDown() {
+        LocalServices.removeServiceForTest(LocationManagerInternal.class);
+    }
+
+    @Test
+    public void testAddListener_active() {
+        // add the active request
+        mTestProvider.addListener(ACTIVE_REQUEST, IDENTITY, mListener1);
+        verify(mGnssNative, times(1)).startMeasurementCollection(
+                eq(ACTIVE_REQUEST.isFullTracking()),
+                eq(ACTIVE_REQUEST.isCorrelationVectorOutputsEnabled()),
+                eq(ACTIVE_REQUEST.getIntervalMillis()));
+
+        // remove the active request
+        mTestProvider.removeListener(mListener1);
+        verify(mGnssNative, times(1)).stopMeasurementCollection();
+    }
+
+    @Test
+    public void testAddListener_passive() {
+        // add the passive request
+        mTestProvider.addListener(PASSIVE_REQUEST, IDENTITY, mListener1);
+        verify(mGnssNative, never()).startMeasurementCollection(anyBoolean(), anyBoolean(),
+                anyInt());
+
+        // remove the passive request
+        mTestProvider.removeListener(mListener1);
+        verify(mGnssNative, times(1)).stopMeasurementCollection();
+    }
+
+    @Test
+    public void testReregister_aidlV3Plus() {
+        doReturn(AIDL_V3).when(mMockConfiguration).getHalInterfaceVersion();
+
+        // add the passive request
+        mTestProvider.addListener(PASSIVE_REQUEST, IDENTITY, mListener1);
+        verify(mGnssNative, never()).startMeasurementCollection(anyBoolean(), anyBoolean(),
+                anyInt());
+
+        // add the active request, reregister with the active request
+        mTestProvider.addListener(ACTIVE_REQUEST, IDENTITY, mListener2);
+        verify(mGnssNative, never()).stopMeasurementCollection();
+        verify(mGnssNative, times(1)).startMeasurementCollection(
+                eq(ACTIVE_REQUEST.isFullTracking()),
+                eq(ACTIVE_REQUEST.isCorrelationVectorOutputsEnabled()),
+                eq(ACTIVE_REQUEST.getIntervalMillis()));
+
+        // remove the active request, reregister with the passive request
+        mTestProvider.removeListener(mListener2);
+        verify(mGnssNative, times(1)).stopMeasurementCollection();
+
+        // remove the passive request
+        mTestProvider.removeListener(mListener1);
+        verify(mGnssNative, times(2)).stopMeasurementCollection();
+    }
+
+    @Test
+    public void testReregister_preAidlV3() {
+        doReturn(HIDL_V2_1).when(mMockConfiguration).getHalInterfaceVersion();
+
+        // add the passive request
+        mTestProvider.addListener(PASSIVE_REQUEST, IDENTITY, mListener1);
+        verify(mGnssNative, never()).startMeasurementCollection(anyBoolean(), anyBoolean(),
+                anyInt());
+
+        // add the active request, reregister with the active request
+        mTestProvider.addListener(ACTIVE_REQUEST, IDENTITY, mListener2);
+        verify(mGnssNative, times(1)).stopMeasurementCollection();
+        verify(mGnssNative, times(1)).startMeasurementCollection(
+                eq(ACTIVE_REQUEST.isFullTracking()),
+                eq(ACTIVE_REQUEST.isCorrelationVectorOutputsEnabled()),
+                eq(ACTIVE_REQUEST.getIntervalMillis()));
+
+        // remove the active request, reregister with the passive request
+        mTestProvider.removeListener(mListener2);
+        verify(mGnssNative, times(2)).stopMeasurementCollection();
+
+        // remove the passive request
+        mTestProvider.removeListener(mListener1);
+        verify(mGnssNative, times(3)).stopMeasurementCollection();
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
index 71280ce..e81b63c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
@@ -403,6 +403,7 @@
         ApplicationInfo applicationInfo = new ApplicationInfo();
         applicationInfo.uid = UserHandle.getUid(userId, Math.abs(pkgName.hashCode()));
         pkgInfo.applicationInfo = applicationInfo;
-        mInstalledPackages.add(userId, pkgName, new InstalledPackageInfo(getContext(), pkgInfo));
+        mInstalledPackages.add(userId, pkgName, new InstalledPackageInfo(getContext(), userId,
+                pkgInfo));
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateNotificationControllerTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateNotificationControllerTest.java
index 8196d6a..e396263 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateNotificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateNotificationControllerTest.java
@@ -52,7 +52,7 @@
 
     private static final int STATE_WITHOUT_NOTIFICATION = 1;
     private static final int STATE_WITH_ACTIVE_NOTIFICATION = 2;
-    private static final int STATE_WITH_ACTIVE_AND_THERMAL_NOTIFICATION = 3;
+    private static final int STATE_WITH_ALL_NOTIFICATION = 3;
 
     private static final int VALID_APP_UID = 1000;
     private static final int INVALID_APP_UID = 2000;
@@ -68,6 +68,8 @@
     private static final String CONTENT_2 = "content2:%1$s";
     private static final String THERMAL_TITLE_2 = "thermal_title2";
     private static final String THERMAL_CONTENT_2 = "thermal_content2";
+    private static final String POWER_SAVE_TITLE_2 = "power_save_title2";
+    private static final String POWER_SAVE_CONTENT_2 = "power_save_content2";
 
     private DeviceStateNotificationController mController;
 
@@ -88,11 +90,12 @@
         notificationInfos.put(STATE_WITH_ACTIVE_NOTIFICATION,
                 new DeviceStateNotificationController.NotificationInfo(
                         NAME_1, TITLE_1, CONTENT_1,
-                        "", ""));
-        notificationInfos.put(STATE_WITH_ACTIVE_AND_THERMAL_NOTIFICATION,
+                        "", "", "", ""));
+        notificationInfos.put(STATE_WITH_ALL_NOTIFICATION,
                 new DeviceStateNotificationController.NotificationInfo(
                         NAME_2, TITLE_2, CONTENT_2,
-                        THERMAL_TITLE_2, THERMAL_CONTENT_2));
+                        THERMAL_TITLE_2, THERMAL_CONTENT_2,
+                        POWER_SAVE_TITLE_2, POWER_SAVE_CONTENT_2));
 
         when(packageManager.getNameForUid(VALID_APP_UID)).thenReturn(VALID_APP_NAME);
         when(packageManager.getNameForUid(INVALID_APP_UID)).thenReturn(INVALID_APP_NAME);
@@ -139,10 +142,46 @@
     }
 
     @Test
+    public void test_powerSaveNotification() {
+        // Verify that the active notification is created.
+        mController.showStateActiveNotificationIfNeeded(
+                STATE_WITH_ALL_NOTIFICATION, VALID_APP_UID);
+        verify(mNotificationManager).notify(
+                eq(DeviceStateNotificationController.NOTIFICATION_TAG),
+                eq(DeviceStateNotificationController.NOTIFICATION_ID),
+                mNotificationCaptor.capture());
+        Notification notification = mNotificationCaptor.getValue();
+        assertEquals(TITLE_2, notification.extras.getString(Notification.EXTRA_TITLE));
+        assertEquals(String.format(CONTENT_2, VALID_APP_LABEL),
+                notification.extras.getString(Notification.EXTRA_TEXT));
+        assertEquals(Notification.FLAG_ONGOING_EVENT,
+                notification.flags & Notification.FLAG_ONGOING_EVENT);
+        Mockito.clearInvocations(mNotificationManager);
+
+        // Verify that the thermal critical notification is created.
+        mController.showPowerSaveNotificationIfNeeded(
+                STATE_WITH_ALL_NOTIFICATION);
+        verify(mNotificationManager).notify(
+                eq(DeviceStateNotificationController.NOTIFICATION_TAG),
+                eq(DeviceStateNotificationController.NOTIFICATION_ID),
+                mNotificationCaptor.capture());
+        notification = mNotificationCaptor.getValue();
+        assertEquals(POWER_SAVE_TITLE_2, notification.extras.getString(Notification.EXTRA_TITLE));
+        assertEquals(POWER_SAVE_CONTENT_2, notification.extras.getString(Notification.EXTRA_TEXT));
+        assertEquals(0, notification.flags & Notification.FLAG_ONGOING_EVENT);
+
+        // Verify that the notification is canceled.
+        mController.cancelNotification(STATE_WITH_ALL_NOTIFICATION);
+        verify(mNotificationManager).cancel(
+                DeviceStateNotificationController.NOTIFICATION_TAG,
+                DeviceStateNotificationController.NOTIFICATION_ID);
+    }
+
+    @Test
     public void test_thermalNotification() {
         // Verify that the active notification is created.
         mController.showStateActiveNotificationIfNeeded(
-                STATE_WITH_ACTIVE_AND_THERMAL_NOTIFICATION, VALID_APP_UID);
+                STATE_WITH_ALL_NOTIFICATION, VALID_APP_UID);
         verify(mNotificationManager).notify(
                 eq(DeviceStateNotificationController.NOTIFICATION_TAG),
                 eq(DeviceStateNotificationController.NOTIFICATION_ID),
@@ -157,7 +196,7 @@
 
         // Verify that the thermal critical notification is created.
         mController.showThermalCriticalNotificationIfNeeded(
-                STATE_WITH_ACTIVE_AND_THERMAL_NOTIFICATION);
+                STATE_WITH_ALL_NOTIFICATION);
         verify(mNotificationManager).notify(
                 eq(DeviceStateNotificationController.NOTIFICATION_TAG),
                 eq(DeviceStateNotificationController.NOTIFICATION_ID),
@@ -168,7 +207,7 @@
         assertEquals(0, notification.flags & Notification.FLAG_ONGOING_EVENT);
 
         // Verify that the notification is canceled.
-        mController.cancelNotification(STATE_WITH_ACTIVE_NOTIFICATION);
+        mController.cancelNotification(STATE_WITH_ALL_NOTIFICATION);
         verify(mNotificationManager).cancel(
                 DeviceStateNotificationController.NOTIFICATION_TAG,
                 DeviceStateNotificationController.NOTIFICATION_ID);
diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
index 5560b41..236c74f 100644
--- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
@@ -728,6 +728,66 @@
     }
 
     @Test
+    public void testPersistedPreferredBatteryNotLowConstraint() throws Exception {
+        JobInfo.Builder b = new Builder(8, mComponent)
+                .setPrefersBatteryNotLow(true)
+                .setPersisted(true);
+        JobStatus taskStatus =
+                JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null, null);
+
+        mTaskStoreUnderTest.add(taskStatus);
+        waitForPendingIo();
+
+        final JobSet jobStatusSet = new JobSet();
+        mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
+        assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size());
+        JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
+        assertEquals("Battery-not-low constraint not persisted correctly.",
+                taskStatus.getJob().isPreferBatteryNotLow(),
+                loaded.getJob().isPreferBatteryNotLow());
+    }
+
+    @Test
+    public void testPersistedPreferredChargingConstraint() throws Exception {
+        JobInfo.Builder b = new Builder(8, mComponent)
+                .setPrefersCharging(true)
+                .setPersisted(true);
+        JobStatus taskStatus =
+                JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null, null);
+
+        mTaskStoreUnderTest.add(taskStatus);
+        waitForPendingIo();
+
+        final JobSet jobStatusSet = new JobSet();
+        mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
+        assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size());
+        JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
+        assertEquals("Charging constraint not persisted correctly.",
+                taskStatus.getJob().isPreferCharging(),
+                loaded.getJob().isPreferCharging());
+    }
+
+    @Test
+    public void testPersistedPreferredDeviceIdleConstraint() throws Exception {
+        JobInfo.Builder b = new Builder(8, mComponent)
+                .setPrefersDeviceIdle(true)
+                .setPersisted(true);
+        JobStatus taskStatus =
+                JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null, null);
+
+        mTaskStoreUnderTest.add(taskStatus);
+        waitForPendingIo();
+
+        final JobSet jobStatusSet = new JobSet();
+        mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
+        assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size());
+        JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
+        assertEquals("Idle constraint not persisted correctly.",
+                taskStatus.getJob().isPreferDeviceIdle(),
+                loaded.getJob().isPreferDeviceIdle());
+    }
+
+    @Test
     public void testJobWorkItems() throws Exception {
         JobWorkItem item1 = new JobWorkItem.Builder().build();
         item1.bumpDeliveryCount();
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/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
index 9570ff6..86878c53 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -160,6 +160,9 @@
     private static final String ADMIN_PKG2 = "com.android.admin2";
     private static final String ADMIN_PKG3 = "com.android.admin3";
 
+    private static final String ADMIN_PROTECTED_PKG = "com.android.admin.protected";
+    private static final String ADMIN_PROTECTED_PKG2 = "com.android.admin.protected2";
+
     private static final long MINUTE_MS = 60 * 1000;
     private static final long HOUR_MS = 60 * MINUTE_MS;
     private static final long DAY_MS = 24 * HOUR_MS;
@@ -1758,6 +1761,19 @@
     }
 
     @Test
+    public void testSetAdminProtectedPackages() {
+        assertAdminProtectedPackagesForTest(USER_ID, (String[]) null);
+        assertAdminProtectedPackagesForTest(USER_ID2, (String[]) null);
+
+        setAdminProtectedPackages(USER_ID, ADMIN_PROTECTED_PKG, ADMIN_PROTECTED_PKG2);
+        assertAdminProtectedPackagesForTest(USER_ID, ADMIN_PROTECTED_PKG, ADMIN_PROTECTED_PKG2);
+        assertAdminProtectedPackagesForTest(USER_ID2, (String[]) null);
+
+        setAdminProtectedPackages(USER_ID, (String[]) null);
+        assertAdminProtectedPackagesForTest(USER_ID, (String[]) null);
+    }
+
+    @Test
     @FlakyTest(bugId = 185169504)
     public void testUserInteraction_CrossProfile() throws Exception {
         mInjector.mRunningUsers = new int[] {USER_ID, USER_ID2, USER_ID3};
@@ -2195,6 +2211,28 @@
         mController.setActiveAdminApps(new ArraySet<>(Arrays.asList(admins)), userId);
     }
 
+    private void setAdminProtectedPackages(int userId, String... packageNames) {
+        Set<String> adminProtectedPackages = packageNames != null ? new ArraySet<>(
+                Arrays.asList(packageNames)) : null;
+        mController.setAdminProtectedPackages(adminProtectedPackages, userId);
+    }
+
+    private void assertAdminProtectedPackagesForTest(int userId, String... packageNames) {
+        final Set<String> actualAdminProtectedPackages =
+                mController.getAdminProtectedPackagesForTest(userId);
+        if (packageNames == null) {
+            if (actualAdminProtectedPackages != null && !actualAdminProtectedPackages.isEmpty()) {
+                fail("Admin protected packages should be null; " + getAdminAppsStr(userId,
+                        actualAdminProtectedPackages));
+            }
+            return;
+        }
+        assertEquals(packageNames.length, actualAdminProtectedPackages.size());
+        for (String adminProtectedPackage : packageNames) {
+            assertTrue(actualAdminProtectedPackages.contains(adminProtectedPackage));
+        }
+    }
+
     private void setAndAssertBucket(String pkg, int user, int bucket, int reason) throws Exception {
         rearmLatch(pkg);
         mController.setAppStandbyBucket(pkg, user, bucket, reason);
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/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
index 2f7a5f4..7a55143 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
@@ -21,6 +21,8 @@
 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
@@ -35,10 +37,12 @@
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.PendingIntent;
+import android.app.Person;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.ParceledListSlice;
 import android.content.pm.ShortcutInfo;
 import android.graphics.Bitmap;
 import android.graphics.drawable.Icon;
@@ -46,11 +50,14 @@
 import android.os.Build;
 import android.os.IBinder;
 import android.os.Parcel;
+import android.os.RemoteException;
+import android.os.UserHandle;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationListenerService.Ranking;
 import android.service.notification.NotificationListenerService.RankingMap;
 import android.service.notification.NotificationRankingUpdate;
 import android.service.notification.SnoozeCriterion;
+import android.service.notification.StatusBarNotification;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import androidx.test.runner.AndroidJUnit4;
@@ -63,6 +70,7 @@
 import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 @SmallTest
@@ -95,6 +103,31 @@
     }
 
     @Test
+    public void testGetActiveNotifications_preP_mapsExtraPeople() throws RemoteException {
+        TestListenerService service = new TestListenerService();
+        service.attachBaseContext(mContext);
+        service.targetSdk = Build.VERSION_CODES.O_MR1;
+
+        Notification notification = new Notification();
+        ArrayList<Person> people = new ArrayList<>();
+        people.add(new Person.Builder().setUri("uri1").setName("P1").build());
+        people.add(new Person.Builder().setUri("uri2").setName("P2").build());
+        notification.extras.putParcelableArrayList(Notification.EXTRA_PEOPLE_LIST, people);
+        when(service.getNoMan().getActiveNotificationsFromListener(any(), any(), anyInt()))
+                .thenReturn(new ParceledListSlice<StatusBarNotification>(Arrays.asList(
+                        new StatusBarNotification("pkg", "opPkg", 1, "tag", 123, 1234,
+                                notification, UserHandle.of(0), null, 0))));
+
+        StatusBarNotification[] sbns = service.getActiveNotifications();
+
+        assertThat(sbns).hasLength(1);
+        String[] mappedPeople = sbns[0].getNotification().extras.getStringArray(
+                Notification.EXTRA_PEOPLE);
+        assertThat(mappedPeople).isNotNull();
+        assertThat(mappedPeople).asList().containsExactly("uri1", "uri2");
+    }
+
+    @Test
     public void testRanking() {
         TestListenerService service = new TestListenerService();
         service.applyUpdateLocked(generateUpdate());
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/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 06bcb91..50e5bbf 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -679,10 +679,6 @@
         compareChannels(ido, mHelper.getNotificationChannel(PKG_O, UID_O, ido.getId(), false));
         compareChannels(idp, mHelper.getNotificationChannel(PKG_P, UID_P, idp.getId(), false));
 
-        verify(mPermissionHelper).setNotificationPermission(nMr1Expected);
-        verify(mPermissionHelper).setNotificationPermission(oExpected);
-        verify(mPermissionHelper).setNotificationPermission(pExpected);
-
         // verify that we also write a state for review_permissions_notification to eventually
         // show a notification
         assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW,
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/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index c73237e..0ae579b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -376,46 +376,6 @@
     }
 
     @Test
-    public void testGetAnimationTargets_windowsAreBeingReplaced() {
-        // [DisplayContent] -+- [Task1] - [ActivityRecord1] (opening, visible)
-        //                                       +- [AppWindow1] (being-replaced)
-        //                   +- [Task2] - [ActivityRecord2] (closing, invisible)
-        //                                       +- [AppWindow2] (being-replaced)
-        final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
-        final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
-                TYPE_BASE_APPLICATION);
-        attrs.setTitle("AppWindow1");
-        final TestWindowState appWindow1 = createWindowState(attrs, activity1);
-        appWindow1.mWillReplaceWindow = true;
-        activity1.addWindow(appWindow1);
-
-        final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
-        activity2.setVisible(false);
-        activity2.setVisibleRequested(false);
-        attrs.setTitle("AppWindow2");
-        final TestWindowState appWindow2 = createWindowState(attrs, activity2);
-        appWindow2.mWillReplaceWindow = true;
-        activity2.addWindow(appWindow2);
-
-        final ArraySet<ActivityRecord> opening = new ArraySet<>();
-        opening.add(activity1);
-        final ArraySet<ActivityRecord> closing = new ArraySet<>();
-        closing.add(activity2);
-
-        // Animate opening apps even if it's already visible in case its windows are being replaced.
-        // Don't animate closing apps if it's already invisible even though its windows are being
-        // replaced.
-        assertEquals(
-                new ArraySet<>(new WindowContainer[]{activity1.getRootTask()}),
-                AppTransitionController.getAnimationTargets(
-                        opening, closing, true /* visible */));
-        assertEquals(
-                new ArraySet<>(new WindowContainer[]{}),
-                AppTransitionController.getAnimationTargets(
-                        opening, closing, false /* visible */));
-    }
-
-    @Test
     public void testGetAnimationTargets_openingClosingInDifferentTask() {
         // [DisplayContent] -+- [Task1] -+- [ActivityRecord1] (opening, invisible)
         //                   |           +- [ActivityRecord2] (invisible)
diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
index 7cb7c79d..43fc1c4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
@@ -111,6 +111,7 @@
         mDisplayUniqueId = "test:" + sNextUniqueId++;
         mTestDisplay = new TestDisplayContent.Builder(mAtm, 1000, 1500)
                 .setUniqueId(mDisplayUniqueId).build();
+        mTestDisplay.getDefaultTaskDisplayArea().setWindowingMode(TEST_WINDOWING_MODE);
         when(mRootWindowContainer.getDisplayContent(eq(mDisplayUniqueId)))
                 .thenReturn(mTestDisplay);
 
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..9b4cb13 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -55,6 +55,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.verify;
 
 import android.annotation.Nullable;
 import android.compat.testing.PlatformCompatChangeRule;
@@ -459,8 +460,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 +505,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();
@@ -516,6 +527,16 @@
     // overrideOrientationIfNeeded
 
     @Test
+    public void testOverrideOrientationIfNeeded_mapInvokedOnRequest() throws Exception {
+        mController = new LetterboxUiController(mWm, mActivity);
+        spyOn(mWm);
+
+        mController.overrideOrientationIfNeeded(SCREEN_ORIENTATION_PORTRAIT);
+
+        verify(mWm).mapOrientationRequest(SCREEN_ORIENTATION_PORTRAIT);
+    }
+
+    @Test
     @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT})
     public void testOverrideOrientationIfNeeded_portraitOverrideEnabled_returnsPortrait()
             throws Exception {
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/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 5208e5a..28241d3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -1328,17 +1328,16 @@
         spyOn(persister);
 
         final Task task = getTestTask();
-        task.setHasBeenVisible(false);
+        task.setHasBeenVisible(true);
         task.getDisplayContent()
                 .getDefaultTaskDisplayArea()
-                .setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
-        task.getRootTask().setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+                .setWindowingMode(WINDOWING_MODE_FREEFORM);
+        task.getRootTask().setWindowingMode(WINDOWING_MODE_FREEFORM);
         final DisplayContent oldDisplay = task.getDisplayContent();
 
         LaunchParamsController.LaunchParams params = new LaunchParamsController.LaunchParams();
-        params.mWindowingMode = WINDOWING_MODE_UNDEFINED;
         persister.getLaunchParams(task, null, params);
-        assertEquals(WINDOWING_MODE_UNDEFINED, params.mWindowingMode);
+        assertEquals(WINDOWING_MODE_FREEFORM, params.mWindowingMode);
 
         task.setHasBeenVisible(true);
         task.removeImmediately();
@@ -1346,7 +1345,7 @@
         verify(persister).saveTask(task, oldDisplay);
 
         persister.getLaunchParams(task, null, params);
-        assertEquals(WINDOWING_MODE_FULLSCREEN, params.mWindowingMode);
+        assertEquals(WINDOWING_MODE_FREEFORM, params.mWindowingMode);
     }
 
     @Test
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/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 7d6cf4a..ba6b3b6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -26,7 +26,10 @@
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.FLAG_OWN_FOCUS;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SPY;
 import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
@@ -49,11 +52,13 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
@@ -67,12 +72,16 @@
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.InputConfig;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
 import android.util.DisplayMetrics;
 import android.util.MergedConfiguration;
+import android.view.IWindow;
 import android.view.IWindowSessionCallback;
+import android.view.InputChannel;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
 import android.view.Surface;
@@ -108,6 +117,20 @@
             ADD_TRUSTED_DISPLAY);
 
     @Test
+    public void testIsRequestedOrientationMapped() {
+        mWm.setOrientationRequestPolicy(/* isIgnoreOrientationRequestDisabled*/ true,
+                /* fromOrientations */ new int[]{1}, /* toOrientations */ new int[]{2});
+        assertThat(mWm.mapOrientationRequest(1)).isEqualTo(2);
+        assertThat(mWm.mapOrientationRequest(3)).isEqualTo(3);
+
+        // Mapping disabled
+        mWm.setOrientationRequestPolicy(/* isIgnoreOrientationRequestDisabled*/ false,
+                /* fromOrientations */ null, /* toOrientations */ null);
+        assertThat(mWm.mapOrientationRequest(1)).isEqualTo(1);
+        assertThat(mWm.mapOrientationRequest(3)).isEqualTo(3);
+    }
+
+    @Test
     public void testAddWindowToken() {
         IBinder token = mock(IBinder.class);
         mWm.addWindowToken(token, TYPE_TOAST, mDisplayContent.getDisplayId(), null /* options */);
@@ -690,6 +713,102 @@
         assertEquals(validRect, resultingArgs.mSourceCrop);
     }
 
+    @Test
+    public void testGrantInputChannel_sanitizeSpyWindowForApplications() {
+        final Session session = mock(Session.class);
+        final int callingUid = Process.FIRST_APPLICATION_UID;
+        final int callingPid = 1234;
+        final SurfaceControl surfaceControl = mock(SurfaceControl.class);
+        final IWindow window = mock(IWindow.class);
+        final IBinder windowToken = mock(IBinder.class);
+        when(window.asBinder()).thenReturn(windowToken);
+        final IBinder focusGrantToken = mock(IBinder.class);
+
+        final InputChannel inputChannel = new InputChannel();
+        assertThrows(IllegalArgumentException.class, () ->
+                mWm.grantInputChannel(session, callingUid, callingPid, DEFAULT_DISPLAY,
+                        surfaceControl, window, null /* hostInputToken */, FLAG_NOT_FOCUSABLE,
+                        PRIVATE_FLAG_TRUSTED_OVERLAY, INPUT_FEATURE_SPY, TYPE_APPLICATION,
+                        null /* windowToken */, focusGrantToken, "TestInputChannel",
+                        inputChannel));
+    }
+
+    @Test
+    public void testGrantInputChannel_allowSpyWindowForInputMonitorPermission() {
+        final Session session = mock(Session.class);
+        final int callingUid = Process.SYSTEM_UID;
+        final int callingPid = 1234;
+        final SurfaceControl surfaceControl = mock(SurfaceControl.class);
+        final IWindow window = mock(IWindow.class);
+        final IBinder windowToken = mock(IBinder.class);
+        when(window.asBinder()).thenReturn(windowToken);
+        final IBinder focusGrantToken = mock(IBinder.class);
+
+        final InputChannel inputChannel = new InputChannel();
+        mWm.grantInputChannel(session, callingUid, callingPid, DEFAULT_DISPLAY, surfaceControl,
+                window, null /* hostInputToken */, FLAG_NOT_FOCUSABLE, PRIVATE_FLAG_TRUSTED_OVERLAY,
+                INPUT_FEATURE_SPY, TYPE_APPLICATION, null /* windowToken */, focusGrantToken,
+                "TestInputChannel", inputChannel);
+
+        verify(mTransaction).setInputWindowInfo(
+                eq(surfaceControl),
+                argThat(h -> (h.inputConfig & InputConfig.SPY) == InputConfig.SPY));
+    }
+
+    @Test
+    public void testUpdateInputChannel_sanitizeSpyWindowForApplications() {
+        final Session session = mock(Session.class);
+        final int callingUid = Process.FIRST_APPLICATION_UID;
+        final int callingPid = 1234;
+        final SurfaceControl surfaceControl = mock(SurfaceControl.class);
+        final IWindow window = mock(IWindow.class);
+        final IBinder windowToken = mock(IBinder.class);
+        when(window.asBinder()).thenReturn(windowToken);
+        final IBinder focusGrantToken = mock(IBinder.class);
+
+        final InputChannel inputChannel = new InputChannel();
+        mWm.grantInputChannel(session, callingUid, callingPid, DEFAULT_DISPLAY, surfaceControl,
+                window, null /* hostInputToken */, FLAG_NOT_FOCUSABLE, PRIVATE_FLAG_TRUSTED_OVERLAY,
+                0 /* inputFeatures */, TYPE_APPLICATION, null /* windowToken */, focusGrantToken,
+                "TestInputChannel", inputChannel);
+        verify(mTransaction).setInputWindowInfo(
+                eq(surfaceControl),
+                argThat(h -> (h.inputConfig & InputConfig.SPY) == 0));
+
+        assertThrows(IllegalArgumentException.class, () ->
+                mWm.updateInputChannel(inputChannel.getToken(), DEFAULT_DISPLAY, surfaceControl,
+                        FLAG_NOT_FOCUSABLE, PRIVATE_FLAG_TRUSTED_OVERLAY, INPUT_FEATURE_SPY,
+                        null /* region */));
+    }
+
+    @Test
+    public void testUpdateInputChannel_allowSpyWindowForInputMonitorPermission() {
+        final Session session = mock(Session.class);
+        final int callingUid = Process.SYSTEM_UID;
+        final int callingPid = 1234;
+        final SurfaceControl surfaceControl = mock(SurfaceControl.class);
+        final IWindow window = mock(IWindow.class);
+        final IBinder windowToken = mock(IBinder.class);
+        when(window.asBinder()).thenReturn(windowToken);
+        final IBinder focusGrantToken = mock(IBinder.class);
+
+        final InputChannel inputChannel = new InputChannel();
+        mWm.grantInputChannel(session, callingUid, callingPid, DEFAULT_DISPLAY, surfaceControl,
+                window, null /* hostInputToken */, FLAG_NOT_FOCUSABLE, PRIVATE_FLAG_TRUSTED_OVERLAY,
+                0 /* inputFeatures */, TYPE_APPLICATION, null /* windowToken */, focusGrantToken,
+                "TestInputChannel", inputChannel);
+        verify(mTransaction).setInputWindowInfo(
+                eq(surfaceControl),
+                argThat(h -> (h.inputConfig & InputConfig.SPY) == 0));
+
+        mWm.updateInputChannel(inputChannel.getToken(), DEFAULT_DISPLAY, surfaceControl,
+                FLAG_NOT_FOCUSABLE, PRIVATE_FLAG_TRUSTED_OVERLAY, INPUT_FEATURE_SPY,
+                null /* region */);
+        verify(mTransaction).setInputWindowInfo(
+                eq(surfaceControl),
+                argThat(h -> (h.inputConfig & InputConfig.SPY) == InputConfig.SPY));
+    }
+
     private void setupActivityWithLaunchCookie(IBinder launchCookie, WindowContainerToken wct) {
         final WindowContainer.RemoteToken remoteToken = mock(WindowContainer.RemoteToken.class);
         when(remoteToken.toWindowContainerToken()).thenReturn(wct);
diff --git a/services/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/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/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
index 2d382a2..79046db 100644
--- a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
+++ b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
@@ -37,6 +37,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.IRemoteCallback;
@@ -160,11 +161,20 @@
                 return null;
             }
             final ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName);
-            if (!isServiceAvailableForUser(serviceComponent)) {
+            boolean isServiceAvailableForUser;
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                isServiceAvailableForUser = isServiceAvailableForUser(serviceComponent);
                 if (mMaster.verbose) {
-                    Slog.v(TAG, "ensureRemoteServiceLocked(): " + serviceComponent
-                            + " is not available,");
+                    Slog.v(TAG, "ensureRemoteServiceLocked(): isServiceAvailableForUser="
+                            + isServiceAvailableForUser);
                 }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+            if (!isServiceAvailableForUser) {
+                Slog.w(TAG, "ensureRemoteServiceLocked(): " + serviceComponent
+                        + " is not available,");
                 return null;
             }
             mRemoteTranslationService = new RemoteTranslationService(getContext(), serviceComponent,
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 7ff5b4a..8948e494 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -3129,6 +3129,11 @@
         }
 
         @Override
+        public void setAdminProtectedPackages(Set<String> packageNames, int userId) {
+            mAppStandby.setAdminProtectedPackages(packageNames, userId);
+        }
+
+        @Override
         public void onAdminDataAvailable() {
             mAppStandby.onAdminDataAvailable();
         }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
index 06fc416..dbc824c 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
@@ -68,6 +68,7 @@
 import android.os.PersistableBundle;
 import android.os.RemoteException;
 import android.os.SharedMemory;
+import android.service.voice.DetectorFailure;
 import android.service.voice.HotwordDetectedResult;
 import android.service.voice.HotwordDetectionService;
 import android.service.voice.HotwordDetectionServiceFailure;
@@ -623,11 +624,9 @@
         mRemoteDetectionService = remoteDetectionService;
     }
 
-    void reportErrorLocked(int errorCode, @NonNull String errorMessage) {
+    void reportErrorLocked(@NonNull DetectorFailure detectorFailure) {
         try {
-            // TODO: Use instanceof(this) to get different detector to set the right error source.
-            mCallback.onDetectionFailure(
-                    new HotwordDetectionServiceFailure(errorCode, errorMessage));
+            mCallback.onDetectionFailure(detectorFailure);
         } catch (RemoteException e) {
             Slog.w(TAG, "Failed to report onError status: " + e);
             if (getDetectorType() != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 025e1dc..4fd5979 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -51,11 +51,14 @@
 import android.os.SharedMemory;
 import android.provider.DeviceConfig;
 import android.service.voice.HotwordDetectionService;
+import android.service.voice.HotwordDetectionServiceFailure;
 import android.service.voice.HotwordDetector;
 import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
 import android.service.voice.ISandboxedDetectionService;
 import android.service.voice.IVisualQueryDetectionVoiceInteractionCallback;
+import android.service.voice.UnknownFailure;
 import android.service.voice.VisualQueryDetectionService;
+import android.service.voice.VisualQueryDetectionServiceFailure;
 import android.service.voice.VoiceInteractionManagerInternal.HotwordDetectionServiceIdentity;
 import android.speech.IRecognitionServiceManager;
 import android.util.Slog;
@@ -109,6 +112,16 @@
     private static final long RESET_DEBUG_HOTWORD_LOGGING_TIMEOUT_MILLIS = 60 * 60 * 1000; // 1 hour
     private static final int MAX_ISOLATED_PROCESS_NUMBER = 10;
 
+    /**
+     * Indicates the {@link HotwordDetectionService} is created.
+     */
+    private static final int DETECTION_SERVICE_TYPE_HOTWORD = 1;
+
+    /**
+     * Indicates the {@link VisualQueryDetectionService} is created.
+     */
+    private static final int DETECTION_SERVICE_TYPE_VISUAL_QUERY = 2;
+
     // TODO: This may need to be a Handler(looper)
     private final ScheduledExecutorService mScheduledExecutorService =
             Executors.newSingleThreadScheduledExecutor();
@@ -186,11 +199,11 @@
 
         mHotwordDetectionServiceConnectionFactory =
                 new ServiceConnectionFactory(hotwordDetectionServiceIntent,
-                        bindInstantServiceAllowed);
+                        bindInstantServiceAllowed, DETECTION_SERVICE_TYPE_HOTWORD);
 
         mVisualQueryDetectionServiceConnectionFactory =
                 new ServiceConnectionFactory(visualQueryDetectionServiceIntent,
-                        bindInstantServiceAllowed);
+                        bindInstantServiceAllowed, DETECTION_SERVICE_TYPE_VISUAL_QUERY);
 
 
         mLastRestartInstant = Instant.now();
@@ -604,17 +617,20 @@
     private class ServiceConnectionFactory {
         private final Intent mIntent;
         private final int mBindingFlags;
+        private final int mDetectionServiceType;
 
-        ServiceConnectionFactory(@NonNull Intent intent, boolean bindInstantServiceAllowed) {
+        ServiceConnectionFactory(@NonNull Intent intent, boolean bindInstantServiceAllowed,
+                int detectionServiceType) {
             mIntent = intent;
             mBindingFlags = bindInstantServiceAllowed ? Context.BIND_ALLOW_INSTANT : 0;
+            mDetectionServiceType = detectionServiceType;
         }
 
         ServiceConnection createLocked() {
             ServiceConnection connection =
                     new ServiceConnection(mContext, mIntent, mBindingFlags, mUser,
                             ISandboxedDetectionService.Stub::asInterface,
-                            mRestartCount % MAX_ISOLATED_PROCESS_NUMBER);
+                            mRestartCount % MAX_ISOLATED_PROCESS_NUMBER, mDetectionServiceType);
             connection.connect();
 
             updateAudioFlinger(connection, mAudioFlinger);
@@ -635,15 +651,17 @@
         private boolean mRespectServiceConnectionStatusChanged = true;
         private boolean mIsBound = false;
         private boolean mIsLoggedFirstConnect = false;
+        private final int mDetectionServiceType;
 
         ServiceConnection(@NonNull Context context,
                 @NonNull Intent serviceIntent, int bindingFlags, int userId,
                 @Nullable Function<IBinder, ISandboxedDetectionService> binderAsInterface,
-                int instanceNumber) {
+                int instanceNumber, int detectionServiceType) {
             super(context, serviceIntent, bindingFlags, userId, binderAsInterface);
             this.mIntent = serviceIntent;
             this.mBindingFlags = bindingFlags;
             this.mInstanceNumber = instanceNumber;
+            this.mDetectionServiceType = detectionServiceType;
         }
 
         @Override // from ServiceConnector.Impl
@@ -660,14 +678,14 @@
                 mIsBound = connected;
 
                 if (!connected) {
-                    if (mDetectorType != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+                    if (mDetectionServiceType != DETECTION_SERVICE_TYPE_VISUAL_QUERY) {
                         HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
                                 HOTWORD_DETECTOR_EVENTS__EVENT__ON_DISCONNECTED,
                                 mVoiceInteractionServiceUid);
                     }
                 } else if (!mIsLoggedFirstConnect) {
                     mIsLoggedFirstConnect = true;
-                    if (mDetectorType != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+                    if (mDetectionServiceType != DETECTION_SERVICE_TYPE_VISUAL_QUERY) {
                         HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
                                 HOTWORD_DETECTOR_EVENTS__EVENT__ON_CONNECTED,
                                 mVoiceInteractionServiceUid);
@@ -684,7 +702,7 @@
         @Override
         public void binderDied() {
             super.binderDied();
-            Slog.w(TAG, "binderDied");
+            Slog.w(TAG, "binderDied mDetectionServiceType = " + mDetectionServiceType);
             synchronized (mLock) {
                 if (!mRespectServiceConnectionStatusChanged) {
                     Slog.v(TAG, "Ignored #binderDied event");
@@ -693,13 +711,10 @@
             }
             //TODO(b265535257): report error to either service only.
             synchronized (HotwordDetectionConnection.this.mLock) {
-                runForEachDetectorSessionLocked((session) -> {
-                    session.reportErrorLocked(DetectorSession.HOTWORD_DETECTION_SERVICE_DIED,
-                            "Detection service is dead.");
-                });
+                runForEachDetectorSessionLocked(this::reportBinderDiedLocked);
             }
             // Can improve to log exit reason if needed
-            if (mDetectorType != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+            if (mDetectionServiceType != DETECTION_SERVICE_TYPE_VISUAL_QUERY) {
                 HotwordMetricsLogger.writeKeyphraseTriggerEvent(
                         mDetectorType,
                         HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__SERVICE_CRASH,
@@ -711,7 +726,7 @@
         protected boolean bindService(
                 @NonNull android.content.ServiceConnection serviceConnection) {
             try {
-                if (mDetectorType != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+                if (mDetectionServiceType != DETECTION_SERVICE_TYPE_VISUAL_QUERY) {
                     HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
                             HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE,
                             mVoiceInteractionServiceUid);
@@ -723,7 +738,12 @@
                         mExecutor,
                         serviceConnection);
                 if (!bindResult) {
-                    if (mDetectorType != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+                    Slog.w(TAG,
+                            "bindService failure mDetectionServiceType = " + mDetectionServiceType);
+                    synchronized (HotwordDetectionConnection.this.mLock) {
+                        runForEachDetectorSessionLocked(this::reportBindServiceFailureLocked);
+                    }
+                    if (mDetectionServiceType != DETECTION_SERVICE_TYPE_VISUAL_QUERY) {
                         HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
                                 HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE_FAIL,
                                 mVoiceInteractionServiceUid);
@@ -731,7 +751,7 @@
                 }
                 return bindResult;
             } catch (IllegalArgumentException e) {
-                if (mDetectorType != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
+                if (mDetectionServiceType != DETECTION_SERVICE_TYPE_VISUAL_QUERY) {
                     HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
                             HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE_FAIL,
                             mVoiceInteractionServiceUid);
@@ -752,6 +772,42 @@
                 mRespectServiceConnectionStatusChanged = false;
             }
         }
+
+        private void reportBinderDiedLocked(DetectorSession detectorSession) {
+            if (mDetectionServiceType == DETECTION_SERVICE_TYPE_HOTWORD && (
+                    detectorSession instanceof DspTrustedHotwordDetectorSession
+                            || detectorSession instanceof SoftwareTrustedHotwordDetectorSession)) {
+                detectorSession.reportErrorLocked(new HotwordDetectionServiceFailure(
+                        HotwordDetectionServiceFailure.ERROR_CODE_BINDING_DIED,
+                        "Detection service is dead."));
+            } else if (mDetectionServiceType == DETECTION_SERVICE_TYPE_VISUAL_QUERY
+                    && detectorSession instanceof VisualQueryDetectorSession) {
+                detectorSession.reportErrorLocked(new VisualQueryDetectionServiceFailure(
+                        VisualQueryDetectionServiceFailure.ERROR_CODE_BINDING_DIED,
+                        "Detection service is dead."));
+            } else {
+                detectorSession.reportErrorLocked(new UnknownFailure(
+                        "Detection service is dead with unknown detection service type."));
+            }
+        }
+
+        private void reportBindServiceFailureLocked(DetectorSession detectorSession) {
+            if (mDetectionServiceType == DETECTION_SERVICE_TYPE_HOTWORD && (
+                    detectorSession instanceof DspTrustedHotwordDetectorSession
+                            || detectorSession instanceof SoftwareTrustedHotwordDetectorSession)) {
+                detectorSession.reportErrorLocked(new HotwordDetectionServiceFailure(
+                        HotwordDetectionServiceFailure.ERROR_CODE_BIND_FAILURE,
+                        "Bind detection service failure."));
+            } else if (mDetectionServiceType == DETECTION_SERVICE_TYPE_VISUAL_QUERY
+                    && detectorSession instanceof VisualQueryDetectorSession) {
+                detectorSession.reportErrorLocked(new VisualQueryDetectionServiceFailure(
+                        VisualQueryDetectionServiceFailure.ERROR_CODE_BIND_FAILURE,
+                        "Bind detection service failure."));
+            } else {
+                detectorSession.reportErrorLocked(new UnknownFailure(
+                        "Bind detection service failure with unknown detection service type."));
+            }
+        }
     }
 
     @SuppressWarnings("GuardedBy")
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index d334d17..7aa1334 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1975,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
@@ -8649,6 +8654,16 @@
         public static final String KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY =
                 KEY_PREFIX + "epdg_address_priority_int_array";
 
+        /**
+         * A priority list of PLMN to be used in EPDG_ADDRESS_PLMN. Possible values are {@link
+         * #EPDG_PLMN_RPLMN}, {@link #EPDG_PLMN_HPLMN}, {@link #EPDG_PLMN_EHPLMN_ALL}, {@link
+         * #EPDG_PLMN_EHPLMN_FIRST}
+         *
+         * @hide
+         */
+        public static final String KEY_EPDG_PLMN_PRIORITY_INT_ARRAY =
+                KEY_PREFIX + "epdg_plmn_priority_int_array";
+
         /** Epdg static IP address or FQDN */
         public static final String KEY_EPDG_STATIC_ADDRESS_STRING =
                 KEY_PREFIX + "epdg_static_address_string";
@@ -8849,6 +8864,36 @@
         public static final int EPDG_ADDRESS_VISITED_COUNTRY = 4;
 
         /** @hide */
+        @IntDef({
+                EPDG_PLMN_RPLMN,
+                EPDG_PLMN_HPLMN,
+                EPDG_PLMN_EHPLMN_ALL,
+                EPDG_PLMN_EHPLMN_FIRST
+        })
+        public @interface EpdgAddressPlmnType {}
+
+        /**
+         * Use the Registered PLMN
+         * @hide
+         */
+        public static final int EPDG_PLMN_RPLMN = 0;
+        /**
+         * Use the PLMN derived from IMSI
+         * @hide
+         */
+        public static final int EPDG_PLMN_HPLMN = 1;
+        /**
+         * Use all EHPLMN from SIM EF files
+         * @hide
+         */
+        public static final int EPDG_PLMN_EHPLMN_ALL = 2;
+        /**
+         * Use the first EHPLMN from SIM EF files
+         * @hide
+         */
+        public static final int EPDG_PLMN_EHPLMN_FIRST = 3;
+
+        /** @hide */
         @IntDef({ID_TYPE_FQDN, ID_TYPE_RFC822_ADDR, ID_TYPE_KEY_ID})
         public @interface IkeIdType {}
 
@@ -8983,6 +9028,12 @@
             defaults.putIntArray(
                     KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY,
                     new int[] {EPDG_ADDRESS_PLMN, EPDG_ADDRESS_STATIC});
+            defaults.putIntArray(
+                    KEY_EPDG_PLMN_PRIORITY_INT_ARRAY,
+                    new int[]{
+                            EPDG_PLMN_RPLMN,
+                            EPDG_PLMN_HPLMN,
+                            EPDG_PLMN_EHPLMN_ALL});
             defaults.putStringArray(KEY_MCC_MNCS_STRING_ARRAY, new String[0]);
             defaults.putInt(KEY_IKE_LOCAL_ID_TYPE_INT, ID_TYPE_RFC822_ADDR);
             defaults.putInt(KEY_IKE_REMOTE_ID_TYPE_INT, ID_TYPE_FQDN);
@@ -9914,6 +9965,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/ISatelliteTransmissionUpdateCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl
index 2442083..a81444d 100644
--- a/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl
+++ b/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl
@@ -24,15 +24,22 @@
  */
 oneway interface ISatelliteTransmissionUpdateCallback {
     /**
-     * Called when satellite datagram transfer state changed.
+     * Called when satellite datagram send state changed.
      *
-     * @param state The new datagram transfer state.
+     * @param state The new send datagram transfer state.
      * @param sendPendingCount The number of datagrams that are currently being sent.
-     * @param receivePendingCount The number of datagrams that are currently being received.
      * @param errorCode If datagram transfer failed, the reason for failure.
      */
-    void onDatagramTransferStateChanged(in int state, in int sendPendingCount,
-            in int receivePendingCount, in int errorCode);
+    void onSendDatagramStateChanged(in int state, in int sendPendingCount, in int errorCode);
+
+    /**
+     * Called when satellite datagram receive state changed.
+     *
+     * @param state The new receive datagram transfer state.
+     * @param receivePendingCount The number of datagrams that are currently pending to be received.
+     * @param errorCode If datagram transfer failed, the reason for failure.
+     */
+    void onReceiveDatagramStateChanged(in int state, in int receivePendingCount, in int errorCode);
 
     /**
      * Called when the satellite position changed.
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index d0abfbf..e32566d 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -647,6 +647,7 @@
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface SatelliteDatagramTransferState {}
+    // TODO: Split into two enums for sending and receiving states
 
     /**
      * Satellite modem is in idle state.
@@ -750,20 +751,28 @@
                 };
                 ISatelliteTransmissionUpdateCallback internalCallback =
                         new ISatelliteTransmissionUpdateCallback.Stub() {
-                            @Override
-                            public void onDatagramTransferStateChanged(int state,
-                                    int sendPendingCount, int receivePendingCount, int errorCode) {
-                                executor.execute(() -> Binder.withCleanCallingIdentity(
-                                        () -> callback.onDatagramTransferStateChanged(
-                                                state, sendPendingCount, receivePendingCount,
-                                                errorCode)));
-                            }
 
                             @Override
                             public void onSatellitePositionChanged(PointingInfo pointingInfo) {
                                 executor.execute(() -> Binder.withCleanCallingIdentity(
                                         () -> callback.onSatellitePositionChanged(pointingInfo)));
                             }
+
+                            @Override
+                            public void onSendDatagramStateChanged(int state, int sendPendingCount,
+                                    int errorCode) {
+                                executor.execute(() -> Binder.withCleanCallingIdentity(
+                                        () -> callback.onSendDatagramStateChanged(
+                                                state, sendPendingCount, errorCode)));
+                            }
+
+                            @Override
+                            public void onReceiveDatagramStateChanged(int state,
+                                    int receivePendingCount, int errorCode) {
+                                executor.execute(() -> Binder.withCleanCallingIdentity(
+                                        () -> callback.onReceiveDatagramStateChanged(
+                                                state, receivePendingCount, errorCode)));
+                            }
                         };
                 sSatelliteTransmissionUpdateCallbackMap.put(callback, internalCallback);
                 telephony.startSatelliteTransmissionUpdates(mSubId, errorCallback,
diff --git a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
index 0efbd1f..d4fe57a 100644
--- a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
@@ -33,14 +33,24 @@
     void onSatellitePositionChanged(@NonNull PointingInfo pointingInfo);
 
     /**
-     * Called when satellite datagram transfer state changed.
+     * Called when satellite datagram send state changed.
      *
-     * @param state The new datagram transfer state.
+     * @param state The new send datagram transfer state.
      * @param sendPendingCount The number of datagrams that are currently being sent.
-     * @param receivePendingCount The number of datagrams that are currently being received.
      * @param errorCode If datagram transfer failed, the reason for failure.
      */
-    void onDatagramTransferStateChanged(@SatelliteManager.SatelliteDatagramTransferState int state,
-            int sendPendingCount, int receivePendingCount,
+    void onSendDatagramStateChanged(
+            @SatelliteManager.SatelliteDatagramTransferState int state, int sendPendingCount,
+            @SatelliteManager.SatelliteError int errorCode);
+
+    /**
+     * Called when satellite datagram receive state changed.
+     *
+     * @param state The new receive datagram transfer state.
+     * @param receivePendingCount The number of datagrams that are currently pending to be received.
+     * @param errorCode If datagram transfer failed, the reason for failure.
+     */
+    void onReceiveDatagramStateChanged(
+            @SatelliteManager.SatelliteDatagramTransferState int state, int receivePendingCount,
             @SatelliteManager.SatelliteError int errorCode);
 }
diff --git a/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java b/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java
index d2a6bf2..81efda1 100644
--- a/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java
+++ b/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java
@@ -16,6 +16,8 @@
 
 package com.android.server.pm;
 
+import static org.junit.Assume.assumeFalse;
+
 import android.app.AlarmManager;
 import android.content.Context;
 import android.os.Environment;
@@ -112,6 +114,7 @@
 
     @Before
     public void setUp() throws IOException {
+        assumeFalse(SystemProperties.getBoolean("dalvik.vm.useartservice", false));
         File dataDir = getContext().getDataDir();
         mBigFile = new File(dataDir, BIG_FILE);
     }
diff --git a/tests/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/test-app/Android.bp b/tests/EnforcePermission/test-app/Android.bp
index 305ed8f..cd53854 100644
--- a/tests/EnforcePermission/test-app/Android.bp
+++ b/tests/EnforcePermission/test-app/Android.bp
@@ -34,5 +34,5 @@
     ],
     platform_apis: true,
     certificate: "platform",
-    test_suites: ["general-tests"],
+    test_suites: ["device-tests"],
 }
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/Input/Android.bp b/tests/Input/Android.bp
index 83893ba..a4c48fd 100644
--- a/tests/Input/Android.bp
+++ b/tests/Input/Android.bp
@@ -13,6 +13,9 @@
         "src/**/*.java",
         "src/**/*.kt",
     ],
+    kotlincflags: [
+        "-Werror",
+    ],
     platform_apis: true,
     certificate: "platform",
     static_libs: [
diff --git a/tests/Input/src/com/android/test/input/AnrTest.kt b/tests/Input/src/com/android/test/input/AnrTest.kt
index 1d65cc3..0246426 100644
--- a/tests/Input/src/com/android/test/input/AnrTest.kt
+++ b/tests/Input/src/com/android/test/input/AnrTest.kt
@@ -73,7 +73,7 @@
         val contentResolver = instrumentation.targetContext.contentResolver
         hideErrorDialogs = Settings.Global.getInt(contentResolver, HIDE_ERROR_DIALOGS, 0)
         Settings.Global.putInt(contentResolver, HIDE_ERROR_DIALOGS, 0)
-        PACKAGE_NAME = UnresponsiveGestureMonitorActivity::class.java.getPackage().getName()
+        PACKAGE_NAME = UnresponsiveGestureMonitorActivity::class.java.getPackage()!!.getName()
     }
 
     @After
diff --git a/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt b/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt
index d83a457..3a24406 100644
--- a/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt
+++ b/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt
@@ -45,7 +45,8 @@
     private lateinit var mInputMonitor: InputMonitor
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-        mInputMonitor = InputManager.getInstance().monitorGestureInput(MONITOR_NAME, displayId)
+        val inputManager = getSystemService(InputManager::class.java)
+        mInputMonitor = inputManager.monitorGestureInput(MONITOR_NAME, displayId)
         mInputEventReceiver = UnresponsiveReceiver(
                 mInputMonitor.getInputChannel(), Looper.myLooper())
     }
diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
index f183a9b..b7f5b15 100644
--- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
+++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
@@ -20,7 +20,6 @@
 import android.service.voice.AlwaysOnHotwordDetector;
 import android.service.voice.AlwaysOnHotwordDetector.Callback;
 import android.service.voice.AlwaysOnHotwordDetector.EventPayload;
-import android.service.voice.HotwordDetector;
 import android.service.voice.VoiceInteractionService;
 import android.util.Log;
 
@@ -84,24 +83,16 @@
                 break;
             case AlwaysOnHotwordDetector.STATE_KEYPHRASE_UNENROLLED:
                 Log.i(TAG, "STATE_KEYPHRASE_UNENROLLED");
-                try {
-                    Intent enroll = mHotwordDetector.createEnrollIntent();
-                    Log.i(TAG, "Need to enroll with " + enroll);
-                } catch (HotwordDetector.IllegalDetectorStateException e) {
-                    Log.e(TAG, "createEnrollIntent failed", e);
-                }
+                Intent enroll = mHotwordDetector.createEnrollIntent();
+                Log.i(TAG, "Need to enroll with " + enroll);
                 break;
             case AlwaysOnHotwordDetector.STATE_KEYPHRASE_ENROLLED:
                 Log.i(TAG, "STATE_KEYPHRASE_ENROLLED - starting recognition");
-                try {
-                    if (mHotwordDetector.startRecognition(
-                            AlwaysOnHotwordDetector.RECOGNITION_FLAG_NONE)) {
-                        Log.i(TAG, "startRecognition succeeded");
-                    } else {
-                        Log.i(TAG, "startRecognition failed");
-                    }
-                } catch (HotwordDetector.IllegalDetectorStateException e) {
-                    Log.e(TAG, "startRecognition failed", e);
+                if (mHotwordDetector.startRecognition(
+                        AlwaysOnHotwordDetector.RECOGNITION_FLAG_NONE)) {
+                    Log.i(TAG, "startRecognition succeeded");
+                } else {
+                    Log.i(TAG, "startRecognition failed");
                 }
                 break;
         }
diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h
index 5fdfb66..2ce2167 100644
--- a/tools/aapt2/cmd/Link.h
+++ b/tools/aapt2/cmd/Link.h
@@ -209,6 +209,8 @@
     AddOptionalFlag("--compile-sdk-version-name",
         "Version name to inject into the AndroidManifest.xml if none is present.",
         &options_.manifest_fixer_options.compile_sdk_version_codename);
+    AddOptionalFlagList("--fingerprint-prefix", "Fingerprint prefix to add to install constraints.",
+                        &options_.manifest_fixer_options.fingerprint_prefixes);
     AddOptionalSwitch("--shared-lib", "Generates a shared Android runtime library.",
         &shared_lib_);
     AddOptionalSwitch("--static-lib", "Generate a static Android library.", &static_lib_);
diff --git a/tools/aapt2/dump/DumpManifest.cpp b/tools/aapt2/dump/DumpManifest.cpp
index c66f4e5..a43bf1b 100644
--- a/tools/aapt2/dump/DumpManifest.cpp
+++ b/tools/aapt2/dump/DumpManifest.cpp
@@ -2120,6 +2120,33 @@
   }
 };
 
+/** Represents <install-constraints> elements. **/
+class InstallConstraints : public ManifestExtractor::Element {
+ public:
+  InstallConstraints() = default;
+  std::vector<std::string> fingerprint_prefixes;
+
+  void Extract(xml::Element* element) override {
+    for (xml::Element* child : element->GetChildElements()) {
+      if (child->name == "fingerprint-prefix") {
+        xml::Attribute* attr = child->FindAttribute(kAndroidNamespace, "value");
+        if (attr) {
+          fingerprint_prefixes.push_back(attr->value);
+        }
+      }
+    }
+  }
+
+  void Print(text::Printer* printer) override {
+    if (!fingerprint_prefixes.empty()) {
+      printer->Print(StringPrintf("install-constraints:\n"));
+      for (const auto& prefix : fingerprint_prefixes) {
+        printer->Print(StringPrintf("  fingerprint-prefix='%s'\n", prefix.c_str()));
+      }
+    }
+  }
+};
+
 /** Represents <original-package> elements. **/
 class OriginalPackage : public ManifestExtractor::Element {
  public:
@@ -2869,7 +2896,7 @@
 constexpr const char* GetExpectedTagForType() {
   // This array does not appear at runtime, as GetExpectedTagForType function is used by compiler
   // to inject proper 'expected_tag' into ElementCast.
-  std::array<std::pair<const char*, bool>, 37> tags = {
+  std::array<std::pair<const char*, bool>, 38> tags = {
       std::make_pair("action", std::is_same<Action, T>::value),
       std::make_pair("activity", std::is_same<Activity, T>::value),
       std::make_pair("additional-certificate", std::is_same<AdditionalCertificate, T>::value),
@@ -2878,6 +2905,7 @@
       std::make_pair("compatible-screens", std::is_same<CompatibleScreens, T>::value),
       std::make_pair("feature-group", std::is_same<FeatureGroup, T>::value),
       std::make_pair("input-type", std::is_same<InputType, T>::value),
+      std::make_pair("install-constraints", std::is_same<InstallConstraints, T>::value),
       std::make_pair("intent-filter", std::is_same<IntentFilter, T>::value),
       std::make_pair("meta-data", std::is_same<MetaData, T>::value),
       std::make_pair("manifest", std::is_same<Manifest, T>::value),
@@ -2948,6 +2976,7 @@
           {"compatible-screens", &CreateType<CompatibleScreens>},
           {"feature-group", &CreateType<FeatureGroup>},
           {"input-type", &CreateType<InputType>},
+          {"install-constraints", &CreateType<InstallConstraints>},
           {"intent-filter", &CreateType<IntentFilter>},
           {"manifest", &CreateType<Manifest>},
           {"meta-data", &CreateType<MetaData>},
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index 56d9075..53f0abe 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -749,6 +749,23 @@
     attr->value = options_.compile_sdk_version_codename.value();
   }
 
+  if (!options_.fingerprint_prefixes.empty()) {
+    xml::Element* install_constraints_el = root->FindChild({}, "install-constraints");
+    if (install_constraints_el == nullptr) {
+      std::unique_ptr<xml::Element> install_constraints = std::make_unique<xml::Element>();
+      install_constraints->name = "install-constraints";
+      install_constraints_el = install_constraints.get();
+      root->AppendChild(std::move(install_constraints));
+    }
+    for (const std::string& prefix : options_.fingerprint_prefixes) {
+      std::unique_ptr<xml::Element> prefix_el = std::make_unique<xml::Element>();
+      prefix_el->name = "fingerprint-prefix";
+      xml::Attribute* attr = prefix_el->FindOrCreateAttribute(xml::kSchemaAndroid, "value");
+      attr->value = prefix;
+      install_constraints_el->AppendChild(std::move(prefix_el));
+    }
+  }
+
   xml::XmlActionExecutor executor;
   if (!BuildRules(&executor, context->GetDiagnostics())) {
     return false;
diff --git a/tools/aapt2/link/ManifestFixer.h b/tools/aapt2/link/ManifestFixer.h
index 90f5177..175ab6f 100644
--- a/tools/aapt2/link/ManifestFixer.h
+++ b/tools/aapt2/link/ManifestFixer.h
@@ -18,11 +18,10 @@
 #define AAPT_LINK_MANIFESTFIXER_H
 
 #include <string>
+#include <vector>
 
 #include "android-base/macros.h"
-
 #include "process/IResourceTableConsumer.h"
-
 #include "xml/XmlActionExecutor.h"
 #include "xml/XmlDom.h"
 
@@ -75,6 +74,9 @@
   // 'android:compileSdkVersionCodename' in the <manifest> tag.
   std::optional<std::string> compile_sdk_version_codename;
 
+  // The fingerprint prefixes to be added to the <install-constraints> tag.
+  std::vector<std::string> fingerprint_prefixes;
+
   // Whether validation errors should be treated only as warnings. If this is 'true', then an
   // incorrect node will not result in an error, but only as a warning, and the parsing will
   // continue.
diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp
index 7180ae6..1b8f05b 100644
--- a/tools/aapt2/link/ManifestFixer_test.cpp
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -965,6 +965,63 @@
   ASSERT_THAT(manifest, IsNull());
 }
 
+TEST_F(ManifestFixerTest, InsertFingerprintPrefixIfNotExist) {
+  std::string input = R"(
+      <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android">
+      </manifest>)";
+  ManifestFixerOptions options;
+  options.fingerprint_prefixes = {"foo", "bar"};
+
+  std::unique_ptr<xml::XmlResource> manifest = VerifyWithOptions(input, options);
+  ASSERT_THAT(manifest, NotNull());
+  xml::Element* install_constraints = manifest->root.get()->FindChild({}, "install-constraints");
+  ASSERT_THAT(install_constraints, NotNull());
+  std::vector<xml::Element*> fingerprint_prefixes = install_constraints->GetChildElements();
+  EXPECT_EQ(fingerprint_prefixes.size(), 2);
+  xml::Attribute* attr;
+  EXPECT_THAT(fingerprint_prefixes[0]->name, StrEq("fingerprint-prefix"));
+  attr = fingerprint_prefixes[0]->FindAttribute(xml::kSchemaAndroid, "value");
+  ASSERT_THAT(attr, NotNull());
+  EXPECT_THAT(attr->value, StrEq("foo"));
+  EXPECT_THAT(fingerprint_prefixes[1]->name, StrEq("fingerprint-prefix"));
+  attr = fingerprint_prefixes[1]->FindAttribute(xml::kSchemaAndroid, "value");
+  ASSERT_THAT(attr, NotNull());
+  EXPECT_THAT(attr->value, StrEq("bar"));
+}
+
+TEST_F(ManifestFixerTest, AppendFingerprintPrefixIfExists) {
+  std::string input = R"(
+      <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android">
+          <install-constraints>
+            <fingerprint-prefix android:value="foo" />
+          </install-constraints>
+      </manifest>)";
+  ManifestFixerOptions options;
+  options.fingerprint_prefixes = {"bar", "baz"};
+
+  std::unique_ptr<xml::XmlResource> manifest = VerifyWithOptions(input, options);
+  ASSERT_THAT(manifest, NotNull());
+  xml::Element* install_constraints = manifest->root.get()->FindChild({}, "install-constraints");
+  ASSERT_THAT(install_constraints, NotNull());
+  std::vector<xml::Element*> fingerprint_prefixes = install_constraints->GetChildElements();
+  EXPECT_EQ(fingerprint_prefixes.size(), 3);
+  xml::Attribute* attr;
+  EXPECT_THAT(fingerprint_prefixes[0]->name, StrEq("fingerprint-prefix"));
+  attr = fingerprint_prefixes[0]->FindAttribute(xml::kSchemaAndroid, "value");
+  ASSERT_THAT(attr, NotNull());
+  EXPECT_THAT(attr->value, StrEq("foo"));
+  EXPECT_THAT(fingerprint_prefixes[1]->name, StrEq("fingerprint-prefix"));
+  attr = fingerprint_prefixes[1]->FindAttribute(xml::kSchemaAndroid, "value");
+  ASSERT_THAT(attr, NotNull());
+  EXPECT_THAT(attr->value, StrEq("bar"));
+  EXPECT_THAT(fingerprint_prefixes[2]->name, StrEq("fingerprint-prefix"));
+  attr = fingerprint_prefixes[2]->FindAttribute(xml::kSchemaAndroid, "value");
+  ASSERT_THAT(attr, NotNull());
+  EXPECT_THAT(attr->value, StrEq("baz"));
+}
+
 TEST_F(ManifestFixerTest, UsesLibraryMustHaveNonEmptyName) {
   std::string input = R"(
       <manifest xmlns:android="http://schemas.android.com/apk/res/android"
diff --git a/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
index 423a684..b4f6a1c 100644
--- a/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
+++ b/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
@@ -36,7 +36,6 @@
         CallingSettingsNonUserGetterMethodsDetector.ISSUE_NON_USER_GETTER_CALLED,
         SaferParcelChecker.ISSUE_UNSAFE_API_USAGE,
         PackageVisibilityDetector.ISSUE_PACKAGE_NAME_NO_PACKAGE_VISIBILITY_FILTERS,
-        RegisterReceiverFlagDetector.ISSUE_RECEIVER_EXPORTED_FLAG,
         PermissionMethodDetector.ISSUE_PERMISSION_METHOD_USAGE,
         PermissionMethodDetector.ISSUE_CAN_BE_PERMISSION_METHOD,
     )
diff --git a/tools/lint/framework/checks/src/main/java/com/google/android/lint/RegisterReceiverFlagDetector.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/RegisterReceiverFlagDetector.kt
deleted file mode 100644
index c3e0428..0000000
--- a/tools/lint/framework/checks/src/main/java/com/google/android/lint/RegisterReceiverFlagDetector.kt
+++ /dev/null
@@ -1,927 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.android.lint
-
-import com.android.tools.lint.checks.DataFlowAnalyzer
-import com.android.tools.lint.detector.api.Category
-import com.android.tools.lint.detector.api.ConstantEvaluator
-import com.android.tools.lint.detector.api.Detector
-import com.android.tools.lint.detector.api.Implementation
-import com.android.tools.lint.detector.api.Issue
-import com.android.tools.lint.detector.api.JavaContext
-import com.android.tools.lint.detector.api.Scope
-import com.android.tools.lint.detector.api.Severity
-import com.android.tools.lint.detector.api.SourceCodeScanner
-import com.android.tools.lint.detector.api.UastLintUtils.Companion.findLastAssignment
-import com.android.tools.lint.detector.api.getMethodName
-import com.intellij.psi.PsiMethod
-import com.intellij.psi.PsiVariable
-import org.jetbrains.kotlin.psi.psiUtil.parameterIndex
-import org.jetbrains.uast.UCallExpression
-import org.jetbrains.uast.UElement
-import org.jetbrains.uast.UExpression
-import org.jetbrains.uast.UParenthesizedExpression
-import org.jetbrains.uast.UQualifiedReferenceExpression
-import org.jetbrains.uast.getContainingUMethod
-import org.jetbrains.uast.isNullLiteral
-import org.jetbrains.uast.skipParenthesizedExprDown
-import org.jetbrains.uast.tryResolve
-
-/**
- * Detector that identifies `registerReceiver()` calls which are missing the `RECEIVER_EXPORTED` or
- * `RECEIVER_NOT_EXPORTED` flags on T+.
- *
- * TODO: Add API level conditions to better support non-platform code.
- * 1. Check if registerReceiver() call is reachable on T+.
- * 2. Check if targetSdkVersion is T+.
- *
- * eg: isWithinVersionCheckConditional(context, node, 31, false)
- * eg: isPrecededByVersionCheckExit(context, node, 31) ?
- */
-@Suppress("UnstableApiUsage")
-class RegisterReceiverFlagDetector : Detector(), SourceCodeScanner {
-
-    override fun getApplicableMethodNames(): List<String> = listOf(
-            "registerReceiver",
-            "registerReceiverAsUser",
-            "registerReceiverForAllUsers"
-    )
-
-    private fun checkIsProtectedReceiverAndReturnUnprotectedActions(
-            filterArg: UExpression,
-            node: UCallExpression,
-            evaluator: ConstantEvaluator
-        ): Pair<Boolean, List<String>> { // isProtected, unprotectedActions
-            val actions = mutableSetOf<String>()
-            val construction = findIntentFilterConstruction(filterArg, node)
-
-            if (construction == null) return Pair(false, listOf<String>())
-            val constructorActionArg = construction.getArgumentForParameter(0)
-            (constructorActionArg?.let(evaluator::evaluate) as? String)?.let(actions::add)
-
-            val actionCollectorVisitor =
-                ActionCollectorVisitor(setOf(construction), node, evaluator)
-
-            val parent = node.getContainingUMethod()
-            parent?.accept(actionCollectorVisitor)
-            actions.addAll(actionCollectorVisitor.actions)
-
-            // If we failed to evaluate any actions, there will be a null action in the set.
-            val isProtected =
-              actions.all(PROTECTED_BROADCASTS::contains) &&
-                !actionCollectorVisitor.intentFilterEscapesScope
-            val unprotectedActionsList = actions.filterNot(PROTECTED_BROADCASTS::contains)
-            return Pair(isProtected, unprotectedActionsList)
-        }
-
-    override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
-        if (!context.evaluator.isMemberInSubClassOf(method, "android.content.Context")) return
-
-        // The parameter positions vary across the various registerReceiver*() methods, so rather
-        // than hardcode them we simply look them up based on the parameter name and type.
-        val receiverArg =
-            findArgument(node, method, "android.content.BroadcastReceiver", "receiver")
-        val filterArg = findArgument(node, method, "android.content.IntentFilter", "filter")
-        val flagsArg = findArgument(node, method, "int", "flags")
-
-        if (receiverArg == null || receiverArg.isNullLiteral() || filterArg == null) {
-            return
-        }
-
-        val evaluator = ConstantEvaluator().allowFieldInitializers()
-
-        val (isProtected, unprotectedActionsList) =
-          checkIsProtectedReceiverAndReturnUnprotectedActions(filterArg, node, evaluator)
-
-        val flags = evaluator.evaluate(flagsArg) as? Int
-
-        if (!isProtected) {
-            val actionsList = unprotectedActionsList.joinToString(", ", "", "", -1, "")
-            val message = "$receiverArg is missing 'RECEIVED_EXPORTED` or 'RECEIVE_NOT_EXPORTED' " +
-                            "flag for unprotected broadcast(s) registered for $actionsList."
-            if (flagsArg == null) {
-                context.report(
-                  ISSUE_RECEIVER_EXPORTED_FLAG, node, context.getLocation(node), message)
-            } else if (flags != null && (flags and RECEIVER_EXPORTED_FLAG_PRESENT_MASK) == 0) {
-                context.report(
-                  ISSUE_RECEIVER_EXPORTED_FLAG, node, context.getLocation(flagsArg), message)
-            }
-        }
-
-        if (DEBUG) {
-            println(node.asRenderString())
-            println("Unprotected Actions: $unprotectedActionsList")
-            println("Protected: $isProtected")
-            println("Flags: $flags")
-        }
-    }
-
-    /** Finds the first argument of a method that matches the given parameter type and name. */
-    private fun findArgument(
-            node: UCallExpression,
-            method: PsiMethod,
-            type: String,
-            name: String
-    ): UExpression? {
-        val psiParameter = method.parameterList.parameters.firstOrNull {
-            it.type.canonicalText == type && it.name == name
-        } ?: return null
-        val argument = node.getArgumentForParameter(psiParameter.parameterIndex())
-        return argument?.skipParenthesizedExprDown()
-    }
-
-    /**
-     * For the supplied expression (eg. intent filter argument), attempts to find its construction.
-     * This will be an `IntentFilter()` constructor, an `IntentFilter.create()` call, or `null`.
-     */
-    private fun findIntentFilterConstruction(
-            expression: UExpression,
-            node: UCallExpression
-    ): UCallExpression? {
-        val resolved = expression.tryResolve()
-
-        if (resolved is PsiVariable) {
-            val assignment = findLastAssignment(resolved, node) ?: return null
-            return findIntentFilterConstruction(assignment, node)
-        }
-
-        if (expression is UParenthesizedExpression) {
-            return findIntentFilterConstruction(expression.expression, node)
-        }
-
-        if (expression is UQualifiedReferenceExpression) {
-            val call = expression.selector as? UCallExpression ?: return null
-            return if (isReturningContext(call)) {
-                // eg. filter.apply { addAction("abc") } --> use filter variable.
-                findIntentFilterConstruction(expression.receiver, node)
-            } else {
-                // eg. IntentFilter.create("abc") --> use create("abc") UCallExpression.
-                findIntentFilterConstruction(call, node)
-            }
-        }
-
-        val method = resolved as? PsiMethod ?: return null
-        return if (isIntentFilterFactoryMethod(method)) {
-            expression as? UCallExpression
-        } else {
-            null
-        }
-    }
-
-    private fun isIntentFilterFactoryMethod(method: PsiMethod) =
-            (method.containingClass?.qualifiedName == "android.content.IntentFilter" &&
-               (method.returnType?.canonicalText == "android.content.IntentFilter" ||
-                    method.isConstructor))
-
-    /**
-     * Returns true if the given call represents a Kotlin scope function where the return value is
-     * the context object; see https://kotlinlang.org/docs/scope-functions.html#function-selection.
-     */
-    private fun isReturningContext(node: UCallExpression): Boolean {
-        val name = getMethodName(node)
-        if (name == "apply" || name == "also") {
-            return isScopingFunction(node)
-        }
-        return false
-    }
-
-    /**
-     * Returns true if the given node appears to be one of the scope functions. Only checks parent
-     * class; caller should intend that it's actually one of let, with, apply, etc.
-     */
-    private fun isScopingFunction(node: UCallExpression): Boolean {
-        val called = node.resolve() ?: return true
-        // See libraries/stdlib/jvm/build/stdlib-declarations.json
-        return called.containingClass?.qualifiedName == "kotlin.StandardKt__StandardKt"
-    }
-
-    inner class ActionCollectorVisitor(
-        start: Collection<UElement>,
-        val functionCall: UCallExpression,
-        val evaluator: ConstantEvaluator,
-    ) : DataFlowAnalyzer(start) {
-       private var finished = false
-       var intentFilterEscapesScope = false; private set
-       val actions = mutableSetOf<String>()
-
-       override fun argument(call: UCallExpression, reference: UElement) {
-           // TODO: Remove this temporary fix for DataFlowAnalyzer bug (ag/15787550):
-           if (reference !in call.valueArguments) return
-           val methodNames = super@RegisterReceiverFlagDetector.getApplicableMethodNames()
-           when {
-               finished -> return
-               // We've reached the registerReceiver*() call in question.
-               call == functionCall -> finished = true
-               // The filter 'intentFilterEscapesScope' to a method which could modify it.
-               methodNames != null && getMethodName(call)!! !in methodNames ->
-                 intentFilterEscapesScope = true
-           }
-       }
-
-       // Fixed in b/199163915: DataFlowAnalyzer doesn't call this for Kotlin properties.
-       override fun field(field: UElement) {
-           if (!finished) intentFilterEscapesScope = true
-       }
-
-       override fun receiver(call: UCallExpression) {
-           if (!finished && getMethodName(call) == "addAction") {
-               val actionArg = call.getArgumentForParameter(0)
-               if (actionArg != null) {
-                   val action = evaluator.evaluate(actionArg) as? String
-                   if (action != null) actions.add(action)
-               }
-           }
-       }
-    }
-
-    companion object {
-        const val DEBUG = false
-
-        private const val RECEIVER_EXPORTED = 0x2
-        private const val RECEIVER_NOT_EXPORTED = 0x4
-        private const val RECEIVER_EXPORTED_FLAG_PRESENT_MASK =
-          RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED
-
-        @JvmField
-        val ISSUE_RECEIVER_EXPORTED_FLAG: Issue = Issue.create(
-                id = "UnspecifiedRegisterReceiverFlag",
-                briefDescription = "Missing `registerReceiver()` exported flag",
-                explanation = """
-                    Apps targeting Android T (SDK 33) and higher must specify either `RECEIVER_EXPORTED` \
-                    or `RECEIVER_NOT_EXPORTED` when registering a broadcast receiver, unless the \
-                    receiver is only registered for protected system broadcast actions.
-                    """,
-                category = Category.SECURITY,
-                priority = 5,
-                severity = Severity.WARNING,
-                implementation = Implementation(
-                        RegisterReceiverFlagDetector::class.java,
-                        Scope.JAVA_FILE_SCOPE
-                )
-        )
-
-        val PROTECTED_BROADCASTS = listOf(
-                "android.intent.action.SCREEN_OFF",
-                "android.intent.action.SCREEN_ON",
-                "android.intent.action.USER_PRESENT",
-                "android.intent.action.TIME_SET",
-                "android.intent.action.TIME_TICK",
-                "android.intent.action.TIMEZONE_CHANGED",
-                "android.intent.action.DATE_CHANGED",
-                "android.intent.action.PRE_BOOT_COMPLETED",
-                "android.intent.action.BOOT_COMPLETED",
-                "android.intent.action.PACKAGE_INSTALL",
-                "android.intent.action.PACKAGE_ADDED",
-                "android.intent.action.PACKAGE_REPLACED",
-                "android.intent.action.MY_PACKAGE_REPLACED",
-                "android.intent.action.PACKAGE_REMOVED",
-                "android.intent.action.PACKAGE_REMOVED_INTERNAL",
-                "android.intent.action.PACKAGE_FULLY_REMOVED",
-                "android.intent.action.PACKAGE_CHANGED",
-                "android.intent.action.PACKAGE_FULLY_LOADED",
-                "android.intent.action.PACKAGE_ENABLE_ROLLBACK",
-                "android.intent.action.CANCEL_ENABLE_ROLLBACK",
-                "android.intent.action.ROLLBACK_COMMITTED",
-                "android.intent.action.PACKAGE_RESTARTED",
-                "android.intent.action.PACKAGE_DATA_CLEARED",
-                "android.intent.action.PACKAGE_FIRST_LAUNCH",
-                "android.intent.action.PACKAGE_NEEDS_INTEGRITY_VERIFICATION",
-                "android.intent.action.PACKAGE_NEEDS_VERIFICATION",
-                "android.intent.action.PACKAGE_VERIFIED",
-                "android.intent.action.PACKAGES_SUSPENDED",
-                "android.intent.action.PACKAGES_UNSUSPENDED",
-                "android.intent.action.PACKAGES_SUSPENSION_CHANGED",
-                "android.intent.action.PACKAGE_UNSUSPENDED_MANUALLY",
-                "android.intent.action.DISTRACTING_PACKAGES_CHANGED",
-                "android.intent.action.ACTION_PREFERRED_ACTIVITY_CHANGED",
-                "android.intent.action.UID_REMOVED",
-                "android.intent.action.QUERY_PACKAGE_RESTART",
-                "android.intent.action.CONFIGURATION_CHANGED",
-                "android.intent.action.SPLIT_CONFIGURATION_CHANGED",
-                "android.intent.action.LOCALE_CHANGED",
-                "android.intent.action.APPLICATION_LOCALE_CHANGED",
-                "android.intent.action.BATTERY_CHANGED",
-                "android.intent.action.BATTERY_LEVEL_CHANGED",
-                "android.intent.action.BATTERY_LOW",
-                "android.intent.action.BATTERY_OKAY",
-                "android.intent.action.ACTION_POWER_CONNECTED",
-                "android.intent.action.ACTION_POWER_DISCONNECTED",
-                "android.intent.action.ACTION_SHUTDOWN",
-                "android.intent.action.CHARGING",
-                "android.intent.action.DISCHARGING",
-                "android.intent.action.DEVICE_STORAGE_LOW",
-                "android.intent.action.DEVICE_STORAGE_OK",
-                "android.intent.action.DEVICE_STORAGE_FULL",
-                "android.intent.action.DEVICE_STORAGE_NOT_FULL",
-                "android.intent.action.NEW_OUTGOING_CALL",
-                "android.intent.action.REBOOT",
-                "android.intent.action.DOCK_EVENT",
-                "android.intent.action.THERMAL_EVENT",
-                "android.intent.action.MASTER_CLEAR_NOTIFICATION",
-                "android.intent.action.USER_ADDED",
-                "android.intent.action.USER_REMOVED",
-                "android.intent.action.USER_STARTING",
-                "android.intent.action.USER_STARTED",
-                "android.intent.action.USER_STOPPING",
-                "android.intent.action.USER_STOPPED",
-                "android.intent.action.USER_BACKGROUND",
-                "android.intent.action.USER_FOREGROUND",
-                "android.intent.action.USER_SWITCHED",
-                "android.intent.action.USER_INITIALIZE",
-                "android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION",
-                "android.intent.action.DOMAINS_NEED_VERIFICATION",
-                "android.intent.action.OVERLAY_ADDED",
-                "android.intent.action.OVERLAY_CHANGED",
-                "android.intent.action.OVERLAY_REMOVED",
-                "android.intent.action.OVERLAY_PRIORITY_CHANGED",
-                "android.intent.action.MY_PACKAGE_SUSPENDED",
-                "android.intent.action.MY_PACKAGE_UNSUSPENDED",
-                "android.os.action.POWER_SAVE_MODE_CHANGED",
-                "android.os.action.DEVICE_IDLE_MODE_CHANGED",
-                "android.os.action.POWER_SAVE_WHITELIST_CHANGED",
-                "android.os.action.POWER_SAVE_TEMP_WHITELIST_CHANGED",
-                "android.os.action.POWER_SAVE_MODE_CHANGED_INTERNAL",
-                "android.os.action.LOW_POWER_STANDBY_ENABLED_CHANGED",
-                "android.os.action.ENHANCED_DISCHARGE_PREDICTION_CHANGED",
-                "android.os.action.SCREEN_BRIGHTNESS_BOOST_CHANGED",
-                "android.app.action.CLOSE_NOTIFICATION_HANDLER_PANEL",
-                "android.app.action.ENTER_CAR_MODE",
-                "android.app.action.EXIT_CAR_MODE",
-                "android.app.action.ENTER_CAR_MODE_PRIORITIZED",
-                "android.app.action.EXIT_CAR_MODE_PRIORITIZED",
-                "android.app.action.ENTER_DESK_MODE",
-                "android.app.action.EXIT_DESK_MODE",
-                "android.app.action.NEXT_ALARM_CLOCK_CHANGED",
-                "android.app.action.USER_ADDED",
-                "android.app.action.USER_REMOVED",
-                "android.app.action.USER_STARTED",
-                "android.app.action.USER_STOPPED",
-                "android.app.action.USER_SWITCHED",
-                "android.app.action.BUGREPORT_SHARING_DECLINED",
-                "android.app.action.BUGREPORT_FAILED",
-                "android.app.action.BUGREPORT_SHARE",
-                "android.app.action.SHOW_DEVICE_MONITORING_DIALOG",
-                "android.intent.action.PENDING_INCIDENT_REPORTS_CHANGED",
-                "android.intent.action.INCIDENT_REPORT_READY",
-                "android.appwidget.action.APPWIDGET_UPDATE_OPTIONS",
-                "android.appwidget.action.APPWIDGET_DELETED",
-                "android.appwidget.action.APPWIDGET_DISABLED",
-                "android.appwidget.action.APPWIDGET_ENABLED",
-                "android.appwidget.action.APPWIDGET_HOST_RESTORED",
-                "android.appwidget.action.APPWIDGET_RESTORED",
-                "android.appwidget.action.APPWIDGET_ENABLE_AND_UPDATE",
-                "android.os.action.SETTING_RESTORED",
-                "android.app.backup.intent.CLEAR",
-                "android.app.backup.intent.INIT",
-                "android.bluetooth.intent.DISCOVERABLE_TIMEOUT",
-                "android.bluetooth.adapter.action.STATE_CHANGED",
-                "android.bluetooth.adapter.action.SCAN_MODE_CHANGED",
-                "android.bluetooth.adapter.action.DISCOVERY_STARTED",
-                "android.bluetooth.adapter.action.DISCOVERY_FINISHED",
-                "android.bluetooth.adapter.action.LOCAL_NAME_CHANGED",
-                "android.bluetooth.adapter.action.BLUETOOTH_ADDRESS_CHANGED",
-                "android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED",
-                "android.bluetooth.device.action.UUID",
-                "android.bluetooth.device.action.MAS_INSTANCE",
-                "android.bluetooth.device.action.ALIAS_CHANGED",
-                "android.bluetooth.device.action.FOUND",
-                "android.bluetooth.device.action.CLASS_CHANGED",
-                "android.bluetooth.device.action.ACL_CONNECTED",
-                "android.bluetooth.device.action.ACL_DISCONNECT_REQUESTED",
-                "android.bluetooth.device.action.ACL_DISCONNECTED",
-                "android.bluetooth.device.action.NAME_CHANGED",
-                "android.bluetooth.device.action.BOND_STATE_CHANGED",
-                "android.bluetooth.device.action.NAME_FAILED",
-                "android.bluetooth.device.action.PAIRING_REQUEST",
-                "android.bluetooth.device.action.PAIRING_CANCEL",
-                "android.bluetooth.device.action.CONNECTION_ACCESS_REPLY",
-                "android.bluetooth.device.action.CONNECTION_ACCESS_CANCEL",
-                "android.bluetooth.device.action.CONNECTION_ACCESS_REQUEST",
-                "android.bluetooth.device.action.SDP_RECORD",
-                "android.bluetooth.device.action.BATTERY_LEVEL_CHANGED",
-                "android.bluetooth.devicepicker.action.LAUNCH",
-                "android.bluetooth.devicepicker.action.DEVICE_SELECTED",
-                "android.bluetooth.action.CSIS_CONNECTION_STATE_CHANGED",
-                "android.bluetooth.action.CSIS_DEVICE_AVAILABLE",
-                "android.bluetooth.action.CSIS_SET_MEMBER_AVAILABLE",
-                "android.bluetooth.mapmce.profile.action.CONNECTION_STATE_CHANGED",
-                "android.bluetooth.mapmce.profile.action.MESSAGE_RECEIVED",
-                "android.bluetooth.mapmce.profile.action.MESSAGE_SENT_SUCCESSFULLY",
-                "android.bluetooth.mapmce.profile.action.MESSAGE_DELIVERED_SUCCESSFULLY",
-                "android.bluetooth.mapmce.profile.action.MESSAGE_READ_STATUS_CHANGED",
-                "android.bluetooth.mapmce.profile.action.MESSAGE_DELETED_STATUS_CHANGED",
-                "android.bluetooth.action.LE_AUDIO_CONNECTION_STATE_CHANGED",
-                "android.bluetooth.action.LE_AUDIO_ACTIVE_DEVICE_CHANGED",
-                "android.bluetooth.action.LE_AUDIO_CONF_CHANGED",
-                "android.bluetooth.action.LE_AUDIO_GROUP_NODE_STATUS_CHANGED",
-                "android.bluetooth.action.LE_AUDIO_GROUP_STATUS_CHANGED",
-                "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED",
-                "android.bluetooth.pbapclient.profile.action.CONNECTION_STATE_CHANGED",
-                "android.bluetooth.sap.profile.action.CONNECTION_STATE_CHANGED",
-                "android.btopp.intent.action.INCOMING_FILE_NOTIFICATION",
-                "android.btopp.intent.action.USER_CONFIRMATION_TIMEOUT",
-                "android.btopp.intent.action.LIST",
-                "android.btopp.intent.action.OPEN_OUTBOUND",
-                "android.btopp.intent.action.HIDE_COMPLETE",
-                "android.btopp.intent.action.CONFIRM",
-                "android.btopp.intent.action.HIDE",
-                "android.btopp.intent.action.RETRY",
-                "android.btopp.intent.action.OPEN",
-                "android.btopp.intent.action.OPEN_INBOUND",
-                "android.btopp.intent.action.TRANSFER_COMPLETE",
-                "android.btopp.intent.action.ACCEPT",
-                "android.btopp.intent.action.DECLINE",
-                "com.android.bluetooth.gatt.REFRESH_BATCHED_SCAN",
-                "com.android.bluetooth.pbap.authchall",
-                "com.android.bluetooth.pbap.userconfirmtimeout",
-                "com.android.bluetooth.pbap.authresponse",
-                "com.android.bluetooth.pbap.authcancelled",
-                "com.android.bluetooth.sap.USER_CONFIRM_TIMEOUT",
-                "com.android.bluetooth.sap.action.DISCONNECT_ACTION",
-                "android.hardware.display.action.WIFI_DISPLAY_STATUS_CHANGED",
-                "android.hardware.usb.action.USB_STATE",
-                "android.hardware.usb.action.USB_PORT_CHANGED",
-                "android.hardware.usb.action.USB_ACCESSORY_ATTACHED",
-                "android.hardware.usb.action.USB_ACCESSORY_DETACHED",
-                "android.hardware.usb.action.USB_ACCESSORY_HANDSHAKE",
-                "android.hardware.usb.action.USB_DEVICE_ATTACHED",
-                "android.hardware.usb.action.USB_DEVICE_DETACHED",
-                "android.intent.action.HEADSET_PLUG",
-                "android.media.action.HDMI_AUDIO_PLUG",
-                "android.media.action.MICROPHONE_MUTE_CHANGED",
-                "android.media.action.SPEAKERPHONE_STATE_CHANGED",
-                "android.media.AUDIO_BECOMING_NOISY",
-                "android.media.RINGER_MODE_CHANGED",
-                "android.media.VIBRATE_SETTING_CHANGED",
-                "android.media.VOLUME_CHANGED_ACTION",
-                "android.media.MASTER_VOLUME_CHANGED_ACTION",
-                "android.media.MASTER_MUTE_CHANGED_ACTION",
-                "android.media.MASTER_MONO_CHANGED_ACTION",
-                "android.media.MASTER_BALANCE_CHANGED_ACTION",
-                "android.media.SCO_AUDIO_STATE_CHANGED",
-                "android.media.ACTION_SCO_AUDIO_STATE_UPDATED",
-                "android.intent.action.MEDIA_REMOVED",
-                "android.intent.action.MEDIA_UNMOUNTED",
-                "android.intent.action.MEDIA_CHECKING",
-                "android.intent.action.MEDIA_NOFS",
-                "android.intent.action.MEDIA_MOUNTED",
-                "android.intent.action.MEDIA_SHARED",
-                "android.intent.action.MEDIA_UNSHARED",
-                "android.intent.action.MEDIA_BAD_REMOVAL",
-                "android.intent.action.MEDIA_UNMOUNTABLE",
-                "android.intent.action.MEDIA_EJECT",
-                "android.net.conn.CAPTIVE_PORTAL",
-                "android.net.conn.CONNECTIVITY_CHANGE",
-                "android.net.conn.CONNECTIVITY_CHANGE_IMMEDIATE",
-                "android.net.conn.DATA_ACTIVITY_CHANGE",
-                "android.net.conn.RESTRICT_BACKGROUND_CHANGED",
-                "android.net.conn.BACKGROUND_DATA_SETTING_CHANGED",
-                "android.net.conn.CAPTIVE_PORTAL_TEST_COMPLETED",
-                "android.net.nsd.STATE_CHANGED",
-                "android.se.omapi.action.SECURE_ELEMENT_STATE_CHANGED",
-                "android.nfc.action.ADAPTER_STATE_CHANGED",
-                "android.nfc.action.PREFERRED_PAYMENT_CHANGED",
-                "android.nfc.action.TRANSACTION_DETECTED",
-                "android.nfc.action.REQUIRE_UNLOCK_FOR_NFC",
-                "com.android.nfc.action.LLCP_UP",
-                "com.android.nfc.action.LLCP_DOWN",
-                "com.android.nfc.cardemulation.action.CLOSE_TAP_DIALOG",
-                "com.android.nfc.handover.action.ALLOW_CONNECT",
-                "com.android.nfc.handover.action.DENY_CONNECT",
-                "com.android.nfc.handover.action.TIMEOUT_CONNECT",
-                "com.android.nfc_extras.action.RF_FIELD_ON_DETECTED",
-                "com.android.nfc_extras.action.RF_FIELD_OFF_DETECTED",
-                "com.android.nfc_extras.action.AID_SELECTED",
-                "android.btopp.intent.action.WHITELIST_DEVICE",
-                "android.btopp.intent.action.STOP_HANDOVER_TRANSFER",
-                "android.nfc.handover.intent.action.HANDOVER_SEND",
-                "android.nfc.handover.intent.action.HANDOVER_SEND_MULTIPLE",
-                "com.android.nfc.handover.action.CANCEL_HANDOVER_TRANSFER",
-                "android.net.action.CLEAR_DNS_CACHE",
-                "android.intent.action.PROXY_CHANGE",
-                "android.os.UpdateLock.UPDATE_LOCK_CHANGED",
-                "android.intent.action.DREAMING_STARTED",
-                "android.intent.action.DREAMING_STOPPED",
-                "android.intent.action.ANY_DATA_STATE",
-                "com.android.server.stats.action.TRIGGER_COLLECTION",
-                "com.android.server.WifiManager.action.START_SCAN",
-                "com.android.server.WifiManager.action.START_PNO",
-                "com.android.server.WifiManager.action.DELAYED_DRIVER_STOP",
-                "com.android.server.WifiManager.action.DEVICE_IDLE",
-                "com.android.server.action.REMOTE_BUGREPORT_SHARING_ACCEPTED",
-                "com.android.server.action.REMOTE_BUGREPORT_SHARING_DECLINED",
-                "com.android.internal.action.EUICC_FACTORY_RESET",
-                "com.android.server.usb.ACTION_OPEN_IN_APPS",
-                "com.android.server.am.DELETE_DUMPHEAP",
-                "com.android.server.net.action.SNOOZE_WARNING",
-                "com.android.server.net.action.SNOOZE_RAPID",
-                "com.android.server.wifi.ACTION_SHOW_SET_RANDOMIZATION_DETAILS",
-                "com.android.server.wifi.action.NetworkSuggestion.USER_ALLOWED_APP",
-                "com.android.server.wifi.action.NetworkSuggestion.USER_DISALLOWED_APP",
-                "com.android.server.wifi.action.NetworkSuggestion.USER_DISMISSED",
-                "com.android.server.wifi.action.CarrierNetwork.USER_ALLOWED_CARRIER",
-                "com.android.server.wifi.action.CarrierNetwork.USER_DISALLOWED_CARRIER",
-                "com.android.server.wifi.action.CarrierNetwork.USER_DISMISSED",
-                "com.android.server.wifi.ConnectToNetworkNotification.USER_DISMISSED_NOTIFICATION",
-                "com.android.server.wifi.ConnectToNetworkNotification.CONNECT_TO_NETWORK",
-                "com.android.server.wifi.ConnectToNetworkNotification.PICK_WIFI_NETWORK",
-                "com.android.server.wifi.ConnectToNetworkNotification.PICK_NETWORK_AFTER_FAILURE",
-                "com.android.server.wifi.wakeup.DISMISS_NOTIFICATION",
-                "com.android.server.wifi.wakeup.OPEN_WIFI_PREFERENCES",
-                "com.android.server.wifi.wakeup.OPEN_WIFI_SETTINGS",
-                "com.android.server.wifi.wakeup.TURN_OFF_WIFI_WAKE",
-                "android.net.wifi.WIFI_STATE_CHANGED",
-                "android.net.wifi.WIFI_AP_STATE_CHANGED",
-                "android.net.wifi.WIFI_CREDENTIAL_CHANGED",
-                "android.net.wifi.aware.action.WIFI_AWARE_STATE_CHANGED",
-                "android.net.wifi.aware.action.WIFI_AWARE_RESOURCE_CHANGED",
-                "android.net.wifi.rtt.action.WIFI_RTT_STATE_CHANGED",
-                "android.net.wifi.SCAN_RESULTS",
-                "android.net.wifi.RSSI_CHANGED",
-                "android.net.wifi.STATE_CHANGE",
-                "android.net.wifi.LINK_CONFIGURATION_CHANGED",
-                "android.net.wifi.CONFIGURED_NETWORKS_CHANGE",
-                "android.net.wifi.action.NETWORK_SETTINGS_RESET",
-                "android.net.wifi.action.PASSPOINT_DEAUTH_IMMINENT",
-                "android.net.wifi.action.PASSPOINT_ICON",
-                "android.net.wifi.action.PASSPOINT_OSU_PROVIDERS_LIST",
-                "android.net.wifi.action.PASSPOINT_SUBSCRIPTION_REMEDIATION",
-                "android.net.wifi.action.PASSPOINT_LAUNCH_OSU_VIEW",
-                "android.net.wifi.action.REFRESH_USER_PROVISIONING",
-                "android.net.wifi.action.WIFI_NETWORK_SUGGESTION_POST_CONNECTION",
-                "android.net.wifi.action.WIFI_SCAN_AVAILABILITY_CHANGED",
-                "android.net.wifi.supplicant.CONNECTION_CHANGE",
-                "android.net.wifi.supplicant.STATE_CHANGE",
-                "android.net.wifi.p2p.STATE_CHANGED",
-                "android.net.wifi.p2p.DISCOVERY_STATE_CHANGE",
-                "android.net.wifi.p2p.THIS_DEVICE_CHANGED",
-                "android.net.wifi.p2p.PEERS_CHANGED",
-                "android.net.wifi.p2p.CONNECTION_STATE_CHANGE",
-                "android.net.wifi.p2p.action.WIFI_P2P_PERSISTENT_GROUPS_CHANGED",
-                "android.net.conn.TETHER_STATE_CHANGED",
-                "android.net.conn.INET_CONDITION_ACTION",
-                "android.net.conn.NETWORK_CONDITIONS_MEASURED",
-                "android.net.scoring.SCORE_NETWORKS",
-                "android.net.scoring.SCORER_CHANGED",
-                "android.intent.action.EXTERNAL_APPLICATIONS_AVAILABLE",
-                "android.intent.action.EXTERNAL_APPLICATIONS_UNAVAILABLE",
-                "android.intent.action.AIRPLANE_MODE",
-                "android.intent.action.ADVANCED_SETTINGS",
-                "android.intent.action.APPLICATION_RESTRICTIONS_CHANGED",
-                "com.android.server.adb.WIRELESS_DEBUG_PAIRED_DEVICES",
-                "com.android.server.adb.WIRELESS_DEBUG_PAIRING_RESULT",
-                "com.android.server.adb.WIRELESS_DEBUG_STATUS",
-                "android.intent.action.ACTION_IDLE_MAINTENANCE_START",
-                "android.intent.action.ACTION_IDLE_MAINTENANCE_END",
-                "com.android.server.ACTION_TRIGGER_IDLE",
-                "android.intent.action.HDMI_PLUGGED",
-                "android.intent.action.PHONE_STATE",
-                "android.intent.action.SUB_DEFAULT_CHANGED",
-                "android.location.PROVIDERS_CHANGED",
-                "android.location.MODE_CHANGED",
-                "android.location.action.GNSS_CAPABILITIES_CHANGED",
-                "android.net.proxy.PAC_REFRESH",
-                "android.telecom.action.DEFAULT_DIALER_CHANGED",
-                "android.provider.action.DEFAULT_SMS_PACKAGE_CHANGED",
-                "android.provider.action.SMS_MMS_DB_CREATED",
-                "android.provider.action.SMS_MMS_DB_LOST",
-                "android.intent.action.CONTENT_CHANGED",
-                "android.provider.Telephony.MMS_DOWNLOADED",
-                "android.content.action.PERMISSION_RESPONSE_RECEIVED",
-                "android.content.action.REQUEST_PERMISSION",
-                "android.nfc.handover.intent.action.HANDOVER_STARTED",
-                "android.nfc.handover.intent.action.TRANSFER_DONE",
-                "android.nfc.handover.intent.action.TRANSFER_PROGRESS",
-                "android.nfc.handover.intent.action.TRANSFER_DONE",
-                "android.intent.action.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED",
-                "android.intent.action.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED",
-                "android.intent.action.ACTION_SET_RADIO_CAPABILITY_DONE",
-                "android.intent.action.ACTION_SET_RADIO_CAPABILITY_FAILED",
-                "android.internal.policy.action.BURN_IN_PROTECTION",
-                "android.app.action.SYSTEM_UPDATE_POLICY_CHANGED",
-                "android.app.action.RESET_PROTECTION_POLICY_CHANGED",
-                "android.app.action.DEVICE_OWNER_CHANGED",
-                "android.app.action.MANAGED_USER_CREATED",
-                "android.intent.action.ANR",
-                "android.intent.action.CALL",
-                "android.intent.action.CALL_PRIVILEGED",
-                "android.intent.action.DROPBOX_ENTRY_ADDED",
-                "android.intent.action.INPUT_METHOD_CHANGED",
-                "android.intent.action.internal_sim_state_changed",
-                "android.intent.action.LOCKED_BOOT_COMPLETED",
-                "android.intent.action.PRECISE_CALL_STATE",
-                "android.intent.action.SUBSCRIPTION_PHONE_STATE",
-                "android.intent.action.USER_INFO_CHANGED",
-                "android.intent.action.USER_UNLOCKED",
-                "android.intent.action.WALLPAPER_CHANGED",
-                "android.app.action.DEVICE_POLICY_MANAGER_STATE_CHANGED",
-                "android.app.action.CHOOSE_PRIVATE_KEY_ALIAS",
-                "android.app.action.DEVICE_ADMIN_DISABLED",
-                "android.app.action.DEVICE_ADMIN_DISABLE_REQUESTED",
-                "android.app.action.DEVICE_ADMIN_ENABLED",
-                "android.app.action.LOCK_TASK_ENTERING",
-                "android.app.action.LOCK_TASK_EXITING",
-                "android.app.action.NOTIFY_PENDING_SYSTEM_UPDATE",
-                "android.app.action.ACTION_PASSWORD_CHANGED",
-                "android.app.action.ACTION_PASSWORD_EXPIRING",
-                "android.app.action.ACTION_PASSWORD_FAILED",
-                "android.app.action.ACTION_PASSWORD_SUCCEEDED",
-                "com.android.server.ACTION_EXPIRED_PASSWORD_NOTIFICATION",
-                "com.android.server.ACTION_PROFILE_OFF_DEADLINE",
-                "com.android.server.ACTION_TURN_PROFILE_ON_NOTIFICATION",
-                "android.intent.action.MANAGED_PROFILE_ADDED",
-                "android.intent.action.MANAGED_PROFILE_UNLOCKED",
-                "android.intent.action.MANAGED_PROFILE_REMOVED",
-                "android.app.action.MANAGED_PROFILE_PROVISIONED",
-                "android.bluetooth.adapter.action.BLE_STATE_CHANGED",
-                "com.android.bluetooth.map.USER_CONFIRM_TIMEOUT",
-                "com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_SENT",
-                "com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_DELIVERY",
-                "android.content.jobscheduler.JOB_DELAY_EXPIRED",
-                "android.content.syncmanager.SYNC_ALARM",
-                "android.media.INTERNAL_RINGER_MODE_CHANGED_ACTION",
-                "android.media.STREAM_DEVICES_CHANGED_ACTION",
-                "android.media.STREAM_MUTE_CHANGED_ACTION",
-                "android.net.sip.SIP_SERVICE_UP",
-                "android.nfc.action.ADAPTER_STATE_CHANGED",
-                "android.os.action.CHARGING",
-                "android.os.action.DISCHARGING",
-                "android.search.action.SEARCHABLES_CHANGED",
-                "android.security.STORAGE_CHANGED",
-                "android.security.action.TRUST_STORE_CHANGED",
-                "android.security.action.KEYCHAIN_CHANGED",
-                "android.security.action.KEY_ACCESS_CHANGED",
-                "android.telecom.action.NUISANCE_CALL_STATUS_CHANGED",
-                "android.telecom.action.PHONE_ACCOUNT_REGISTERED",
-                "android.telecom.action.PHONE_ACCOUNT_UNREGISTERED",
-                "android.telecom.action.POST_CALL",
-                "android.telecom.action.SHOW_MISSED_CALLS_NOTIFICATION",
-                "android.telephony.action.CARRIER_CONFIG_CHANGED",
-                "android.telephony.action.DEFAULT_SUBSCRIPTION_CHANGED",
-                "android.telephony.action.DEFAULT_SMS_SUBSCRIPTION_CHANGED",
-                "android.telephony.action.SECRET_CODE",
-                "android.telephony.action.SHOW_VOICEMAIL_NOTIFICATION",
-                "android.telephony.action.SUBSCRIPTION_PLANS_CHANGED",
-                "com.android.bluetooth.btservice.action.ALARM_WAKEUP",
-                "com.android.server.action.NETWORK_STATS_POLL",
-                "com.android.server.action.NETWORK_STATS_UPDATED",
-                "com.android.server.timedetector.NetworkTimeUpdateService.action.POLL",
-                "com.android.server.telecom.intent.action.CALLS_ADD_ENTRY",
-                "com.android.settings.location.MODE_CHANGING",
-                "com.android.settings.bluetooth.ACTION_DISMISS_PAIRING",
-                "com.android.settings.network.DELETE_SUBSCRIPTION",
-                "com.android.settings.network.SWITCH_TO_SUBSCRIPTION",
-                "com.android.settings.wifi.action.NETWORK_REQUEST",
-                "NotificationManagerService.TIMEOUT",
-                "NotificationHistoryDatabase.CLEANUP",
-                "ScheduleConditionProvider.EVALUATE",
-                "EventConditionProvider.EVALUATE",
-                "SnoozeHelper.EVALUATE",
-                "wifi_scan_available",
-                "action.cne.started",
-                "android.content.jobscheduler.JOB_DEADLINE_EXPIRED",
-                "android.intent.action.ACTION_UNSOL_RESPONSE_OEM_HOOK_RAW",
-                "android.net.conn.CONNECTIVITY_CHANGE_SUPL",
-                "android.os.action.LIGHT_DEVICE_IDLE_MODE_CHANGED",
-                "android.os.storage.action.VOLUME_STATE_CHANGED",
-                "android.os.storage.action.DISK_SCANNED",
-                "com.android.server.action.UPDATE_TWILIGHT_STATE",
-                "com.android.server.action.RESET_TWILIGHT_AUTO",
-                "com.android.server.device_idle.STEP_IDLE_STATE",
-                "com.android.server.device_idle.STEP_LIGHT_IDLE_STATE",
-                "com.android.server.Wifi.action.TOGGLE_PNO",
-                "intent.action.ACTION_RF_BAND_INFO",
-                "android.intent.action.MEDIA_RESOURCE_GRANTED",
-                "android.app.action.NETWORK_LOGS_AVAILABLE",
-                "android.app.action.SECURITY_LOGS_AVAILABLE",
-                "android.app.action.COMPLIANCE_ACKNOWLEDGEMENT_REQUIRED",
-                "android.app.action.INTERRUPTION_FILTER_CHANGED",
-                "android.app.action.INTERRUPTION_FILTER_CHANGED_INTERNAL",
-                "android.app.action.NOTIFICATION_POLICY_CHANGED",
-                "android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED",
-                "android.app.action.AUTOMATIC_ZEN_RULE_STATUS_CHANGED",
-                "android.os.action.ACTION_EFFECTS_SUPPRESSOR_CHANGED",
-                "android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED",
-                "android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED",
-                "android.app.action.NOTIFICATION_LISTENER_ENABLED_CHANGED",
-                "android.app.action.APP_BLOCK_STATE_CHANGED",
-                "android.permission.GET_APP_GRANTED_URI_PERMISSIONS",
-                "android.permission.CLEAR_APP_GRANTED_URI_PERMISSIONS",
-                "android.intent.action.DYNAMIC_SENSOR_CHANGED",
-                "android.accounts.LOGIN_ACCOUNTS_CHANGED",
-                "android.accounts.action.ACCOUNT_REMOVED",
-                "android.accounts.action.VISIBLE_ACCOUNTS_CHANGED",
-                "com.android.sync.SYNC_CONN_STATUS_CHANGED",
-                "android.net.sip.action.SIP_INCOMING_CALL",
-                "com.android.phone.SIP_ADD_PHONE",
-                "android.net.sip.action.SIP_REMOVE_PROFILE",
-                "android.net.sip.action.SIP_SERVICE_UP",
-                "android.net.sip.action.SIP_CALL_OPTION_CHANGED",
-                "android.net.sip.action.START_SIP",
-                "android.bluetooth.adapter.action.BLE_ACL_CONNECTED",
-                "android.bluetooth.adapter.action.BLE_ACL_DISCONNECTED",
-                "android.bluetooth.input.profile.action.HANDSHAKE",
-                "android.bluetooth.input.profile.action.REPORT",
-                "android.intent.action.TWILIGHT_CHANGED",
-                "com.android.server.fingerprint.ACTION_LOCKOUT_RESET",
-                "android.net.wifi.PASSPOINT_ICON_RECEIVED",
-                "com.android.server.notification.CountdownConditionProvider",
-                "android.server.notification.action.ENABLE_NAS",
-                "android.server.notification.action.DISABLE_NAS",
-                "android.server.notification.action.LEARNMORE_NAS",
-                "com.android.internal.location.ALARM_WAKEUP",
-                "com.android.internal.location.ALARM_TIMEOUT",
-                "android.intent.action.GLOBAL_BUTTON",
-                "android.intent.action.MANAGED_PROFILE_AVAILABLE",
-                "android.intent.action.MANAGED_PROFILE_UNAVAILABLE",
-                "com.android.server.pm.DISABLE_QUIET_MODE_AFTER_UNLOCK",
-                "android.intent.action.PROFILE_ACCESSIBLE",
-                "android.intent.action.PROFILE_INACCESSIBLE",
-                "com.android.server.retaildemo.ACTION_RESET_DEMO",
-                "android.intent.action.DEVICE_LOCKED_CHANGED",
-                "com.android.content.pm.action.CAN_INTERACT_ACROSS_PROFILES_CHANGED",
-                "android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED",
-                "com.android.server.wm.ACTION_REVOKE_SYSTEM_ALERT_WINDOW_PERMISSION",
-                "android.media.tv.action.PARENTAL_CONTROLS_ENABLED_CHANGED",
-                "android.content.pm.action.SESSION_COMMITTED",
-                "android.os.action.USER_RESTRICTIONS_CHANGED",
-                "android.media.tv.action.PREVIEW_PROGRAM_ADDED_TO_WATCH_NEXT",
-                "android.media.tv.action.PREVIEW_PROGRAM_BROWSABLE_DISABLED",
-                "android.media.tv.action.WATCH_NEXT_PROGRAM_BROWSABLE_DISABLED",
-                "android.media.tv.action.CHANNEL_BROWSABLE_REQUESTED",
-                "com.android.server.inputmethod.InputMethodManagerService.SHOW_INPUT_METHOD_PICKER",
-                "com.android.intent.action.timezone.RULES_UPDATE_OPERATION",
-                "com.android.intent.action.timezone.TRIGGER_RULES_UPDATE_CHECK",
-                "android.intent.action.GET_RESTRICTION_ENTRIES",
-                "android.telephony.euicc.action.OTA_STATUS_CHANGED",
-                "android.app.action.PROFILE_OWNER_CHANGED",
-                "android.app.action.TRANSFER_OWNERSHIP_COMPLETE",
-                "android.app.action.AFFILIATED_PROFILE_TRANSFER_OWNERSHIP_COMPLETE",
-                "android.app.action.STATSD_STARTED",
-                "com.android.server.biometrics.fingerprint.ACTION_LOCKOUT_RESET",
-                "com.android.server.biometrics.face.ACTION_LOCKOUT_RESET",
-                "android.intent.action.DOCK_IDLE",
-                "android.intent.action.DOCK_ACTIVE",
-                "android.content.pm.action.SESSION_UPDATED",
-                "android.settings.action.GRAYSCALE_CHANGED",
-                "com.android.server.jobscheduler.GARAGE_MODE_ON",
-                "com.android.server.jobscheduler.GARAGE_MODE_OFF",
-                "com.android.server.jobscheduler.FORCE_IDLE",
-                "com.android.server.jobscheduler.UNFORCE_IDLE",
-                "android.provider.action.DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL",
-                "android.intent.action.DEVICE_CUSTOMIZATION_READY",
-                "android.app.action.RESET_PROTECTION_POLICY_CHANGED",
-                "com.android.internal.intent.action.BUGREPORT_REQUESTED",
-                "android.scheduling.action.REBOOT_READY",
-                "android.app.action.DEVICE_POLICY_CONSTANTS_CHANGED",
-                "android.app.action.SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED",
-                "android.app.action.SHOW_NEW_USER_DISCLAIMER",
-                "android.telecom.action.CURRENT_TTY_MODE_CHANGED",
-                "android.intent.action.SERVICE_STATE",
-                "android.intent.action.RADIO_TECHNOLOGY",
-                "android.intent.action.EMERGENCY_CALLBACK_MODE_CHANGED",
-                "android.intent.action.EMERGENCY_CALL_STATE_CHANGED",
-                "android.intent.action.SIG_STR",
-                "android.intent.action.ANY_DATA_STATE",
-                "android.intent.action.DATA_STALL_DETECTED",
-                "android.intent.action.SIM_STATE_CHANGED",
-                "android.intent.action.USER_ACTIVITY_NOTIFICATION",
-                "android.telephony.action.SHOW_NOTICE_ECM_BLOCK_OTHERS",
-                "android.intent.action.ACTION_MDN_STATE_CHANGED",
-                "android.telephony.action.SERVICE_PROVIDERS_UPDATED",
-                "android.provider.Telephony.SIM_FULL",
-                "com.android.internal.telephony.carrier_key_download_alarm",
-                "com.android.internal.telephony.data-restart-trysetup",
-                "com.android.internal.telephony.data-stall",
-                "com.android.internal.telephony.provisioning_apn_alarm",
-                "android.intent.action.DATA_SMS_RECEIVED",
-                "android.provider.Telephony.SMS_RECEIVED",
-                "android.provider.Telephony.SMS_DELIVER",
-                "android.provider.Telephony.SMS_REJECTED",
-                "android.provider.Telephony.WAP_PUSH_DELIVER",
-                "android.provider.Telephony.WAP_PUSH_RECEIVED",
-                "android.provider.Telephony.SMS_CB_RECEIVED",
-                "android.provider.action.SMS_EMERGENCY_CB_RECEIVED",
-                "android.provider.Telephony.SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED",
-                "android.provider.Telephony.SECRET_CODE",
-                "com.android.internal.stk.command",
-                "com.android.internal.stk.session_end",
-                "com.android.internal.stk.icc_status_change",
-                "com.android.internal.stk.alpha_notify",
-                "com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED",
-                "com.android.internal.telephony.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED",
-                "com.android.internal.telephony.CARRIER_SIGNAL_PCO_VALUE",
-                "com.android.internal.telephony.CARRIER_SIGNAL_RESET",
-                "com.android.internal.telephony.CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE",
-                "com.android.internal.telephony.PROVISION",
-                "com.android.internal.telephony.ACTION_LINE1_NUMBER_ERROR_DETECTED",
-                "com.android.internal.provider.action.VOICEMAIL_SMS_RECEIVED",
-                "com.android.intent.isim_refresh",
-                "com.android.ims.ACTION_RCS_SERVICE_AVAILABLE",
-                "com.android.ims.ACTION_RCS_SERVICE_UNAVAILABLE",
-                "com.android.ims.ACTION_RCS_SERVICE_DIED",
-                "com.android.ims.ACTION_PRESENCE_CHANGED",
-                "com.android.ims.ACTION_PUBLISH_STATUS_CHANGED",
-                "com.android.ims.IMS_SERVICE_UP",
-                "com.android.ims.IMS_SERVICE_DOWN",
-                "com.android.ims.IMS_INCOMING_CALL",
-                "com.android.ims.internal.uce.UCE_SERVICE_UP",
-                "com.android.ims.internal.uce.UCE_SERVICE_DOWN",
-                "com.android.imsconnection.DISCONNECTED",
-                "com.android.intent.action.IMS_FEATURE_CHANGED",
-                "com.android.intent.action.IMS_CONFIG_CHANGED",
-                "android.telephony.ims.action.WFC_IMS_REGISTRATION_ERROR",
-                "com.android.phone.vvm.omtp.sms.REQUEST_SENT",
-                "com.android.phone.vvm.ACTION_VISUAL_VOICEMAIL_SERVICE_EVENT",
-                "com.android.internal.telephony.CARRIER_VVM_PACKAGE_INSTALLED",
-                "com.android.cellbroadcastreceiver.GET_LATEST_CB_AREA_INFO",
-                "com.android.internal.telephony.ACTION_CARRIER_CERTIFICATE_DOWNLOAD",
-                "com.android.internal.telephony.action.COUNTRY_OVERRIDE",
-                "com.android.internal.telephony.OPEN_DEFAULT_SMS_APP",
-                "com.android.internal.telephony.ACTION_TEST_OVERRIDE_CARRIER_ID",
-                "android.telephony.action.SIM_CARD_STATE_CHANGED",
-                "android.telephony.action.SIM_APPLICATION_STATE_CHANGED",
-                "android.telephony.action.SIM_SLOT_STATUS_CHANGED",
-                "android.telephony.action.SUBSCRIPTION_CARRIER_IDENTITY_CHANGED",
-                "android.telephony.action.SUBSCRIPTION_SPECIFIC_CARRIER_IDENTITY_CHANGED",
-                "android.telephony.action.TOGGLE_PROVISION",
-                "android.telephony.action.NETWORK_COUNTRY_CHANGED",
-                "android.telephony.action.PRIMARY_SUBSCRIPTION_LIST_CHANGED",
-                "android.telephony.action.MULTI_SIM_CONFIG_CHANGED",
-                "android.telephony.action.CARRIER_SIGNAL_RESET",
-                "android.telephony.action.CARRIER_SIGNAL_PCO_VALUE",
-                "android.telephony.action.CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE",
-                "android.telephony.action.CARRIER_SIGNAL_REDIRECTED",
-                "android.telephony.action.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED",
-                "com.android.phone.settings.CARRIER_PROVISIONING",
-                "com.android.phone.settings.TRIGGER_CARRIER_PROVISIONING",
-                "com.android.internal.telephony.ACTION_VOWIFI_ENABLED",
-                "android.telephony.action.ANOMALY_REPORTED",
-                "android.intent.action.SUBSCRIPTION_INFO_RECORD_ADDED",
-                "android.intent.action.ACTION_MANAGED_ROAMING_IND",
-                "android.telephony.ims.action.RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE",
-                "android.safetycenter.action.REFRESH_SAFETY_SOURCES",
-                "android.safetycenter.action.SAFETY_CENTER_ENABLED_CHANGED",
-                "android.app.action.DEVICE_POLICY_RESOURCE_UPDATED",
-                "android.intent.action.SHOW_FOREGROUND_SERVICE_MANAGER",
-                "android.service.autofill.action.DELAYED_FILL",
-                "android.app.action.PROVISIONING_COMPLETED",
-                "android.app.action.LOST_MODE_LOCATION_UPDATE",
-                "android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED",
-                "android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED",
-                "android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT",
-                "android.bluetooth.headset.action.HF_INDICATORS_VALUE_CHANGED",
-                "android.bluetooth.headset.profile.action.ACTIVE_DEVICE_CHANGED",
-                "android.bluetooth.headsetclient.profile.action.CONNECTION_STATE_CHANGED",
-                "android.bluetooth.headsetclient.profile.action.AUDIO_STATE_CHANGED",
-                "android.bluetooth.headsetclient.profile.action.AG_EVENT",
-                "android.bluetooth.headsetclient.profile.action.AG_CALL_CHANGED",
-                "android.bluetooth.headsetclient.profile.action.RESULT",
-                "android.bluetooth.headsetclient.profile.action.LAST_VTAG",
-                "android.bluetooth.headsetclient.profile.action.NETWORK_SERVICE_STATE_CHANGED",
-                "android.bluetooth.hearingaid.profile.action.CONNECTION_STATE_CHANGED",
-                "android.bluetooth.hearingaid.profile.action.PLAYING_STATE_CHANGED",
-                "android.bluetooth.hearingaid.profile.action.ACTIVE_DEVICE_CHANGED",
-                "android.bluetooth.volume-control.profile.action.CONNECTION_STATE_CHANGED",
-                "android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED",
-                "android.bluetooth.a2dp.profile.action.ACTIVE_DEVICE_CHANGED",
-                "android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED",
-                "android.bluetooth.a2dp.profile.action.CODEC_CONFIG_CHANGED",
-                "android.bluetooth.a2dp-sink.profile.action.CONNECTION_STATE_CHANGED",
-                "android.bluetooth.a2dp-sink.profile.action.PLAYING_STATE_CHANGED",
-                "android.bluetooth.a2dp-sink.profile.action.AUDIO_CONFIG_CHANGED",
-                "android.bluetooth.avrcp-controller.profile.action.BROWSE_CONNECTION_STATE_CHANGED",
-                "android.bluetooth.avrcp-controller.profile.action.CONNECTION_STATE_CHANGED",
-                "android.bluetooth.avrcp-controller.profile.action.FOLDER_LIST",
-                "android.bluetooth.avrcp-controller.profile.action.TRACK_EVENT",
-                "android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED",
-                "android.bluetooth.input.profile.action.IDLE_TIME_CHANGED",
-                "android.bluetooth.input.profile.action.PROTOCOL_MODE_CHANGED",
-                "android.bluetooth.input.profile.action.VIRTUAL_UNPLUG_STATUS",
-                "android.bluetooth.hiddevice.profile.action.CONNECTION_STATE_CHANGED",
-                "android.bluetooth.map.profile.action.CONNECTION_STATE_CHANGED",
-                "com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_SENT",
-                "com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_DELIVERY",
-                "android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED",
-                "android.bluetooth.action.TETHERING_STATE_CHANGED",
-                "com.android.internal.action.EUICC_REMOVE_INVISIBLE_SUBSCRIPTIONS",
-                "android.net.ConnectivityService.action.PKT_CNT_SAMPLE_INTERVAL_ELAPSED",
-                "com.android.server.connectivityservice.CONNECTED_TO_PROVISIONING_NETWORK_ACTION",
-                "com.android.server.connectivity.tethering.PROVISIONING_RECHECK_ALARM"
-        )
-    }
-}
diff --git a/tools/lint/framework/checks/src/test/java/com/google/android/lint/RegisterReceiverFlagDetectorTest.kt b/tools/lint/framework/checks/src/test/java/com/google/android/lint/RegisterReceiverFlagDetectorTest.kt
deleted file mode 100644
index 7c0ebca..0000000
--- a/tools/lint/framework/checks/src/test/java/com/google/android/lint/RegisterReceiverFlagDetectorTest.kt
+++ /dev/null
@@ -1,569 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.android.lint
-
-import com.android.tools.lint.checks.infrastructure.LintDetectorTest
-import com.android.tools.lint.checks.infrastructure.TestFile
-import com.android.tools.lint.checks.infrastructure.TestLintTask
-import com.android.tools.lint.detector.api.Detector
-import com.android.tools.lint.detector.api.Issue
-
-@Suppress("UnstableApiUsage")
-class RegisterReceiverFlagDetectorTest : LintDetectorTest() {
-
-    override fun getDetector(): Detector = RegisterReceiverFlagDetector()
-
-    override fun getIssues(): List<Issue> = listOf(
-            RegisterReceiverFlagDetector.ISSUE_RECEIVER_EXPORTED_FLAG
-    )
-
-    override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
-
-    fun testProtectedBroadcast() {
-        lint().files(
-                java(
-                        """
-                    package test.pkg;
-                    import android.content.BroadcastReceiver;
-                    import android.content.Context;
-                    import android.content.Intent;
-                    import android.content.IntentFilter;
-                    public class TestClass1 {
-                        public void testMethod(Context context, BroadcastReceiver receiver) {
-                            IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
-                            context.registerReceiver(receiver, filter);
-                        }
-                    }
-                   """
-                ).indented(),
-                *stubs
-        )
-                .run()
-                .expectClean()
-    }
-
-    fun testProtectedBroadcastCreate() {
-        lint().files(
-                java(
-                        """
-                    package test.pkg;
-                    import android.content.BroadcastReceiver;
-                    import android.content.Context;
-                    import android.content.Intent;
-                    import android.content.IntentFilter;
-                    public class TestClass1 {
-                        public void testMethod(Context context, BroadcastReceiver receiver) {
-                            IntentFilter filter =
-                                    IntentFilter.create(Intent.ACTION_BATTERY_CHANGED, "foo/bar");
-                            context.registerReceiver(receiver, filter);
-                        }
-                    }
-                   """
-                ).indented(),
-                *stubs
-        )
-                .run()
-                .expectClean()
-    }
-
-    fun testMultipleProtectedBroadcasts() {
-        lint().files(
-                java(
-                        """
-                    package test.pkg;
-                    import android.content.BroadcastReceiver;
-                    import android.content.Context;
-                    import android.content.Intent;
-                    import android.content.IntentFilter;
-                    public class TestClass1 {
-                        public void testMethod(Context context, BroadcastReceiver receiver) {
-                            IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
-                            filter.addAction(Intent.ACTION_BATTERY_LOW);
-                            filter.addAction(Intent.ACTION_BATTERY_OKAY);
-                            context.registerReceiver(receiver, filter);
-                        }
-                    }
-                   """
-                ).indented(),
-                *stubs
-        )
-                .run()
-                .expectClean()
-    }
-
-    // TODO(b/267510341): Reenable this test
-    // fun testSubsequentFilterModification() {
-    //     lint().files(
-    //             java(
-    //                     """
-    //                 package test.pkg;
-    //                 import android.content.BroadcastReceiver;
-    //                 import android.content.Context;
-    //                 import android.content.Intent;
-    //                 import android.content.IntentFilter;
-    //                 public class TestClass1 {
-    //                     public void testMethod(Context context, BroadcastReceiver receiver) {
-    //                         IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
-    //                         filter.addAction(Intent.ACTION_BATTERY_LOW);
-    //                         filter.addAction(Intent.ACTION_BATTERY_OKAY);
-    //                         context.registerReceiver(receiver, filter);
-    //                         filter.addAction("querty");
-    //                         context.registerReceiver(receiver, filter);
-    //                     }
-    //                 }
-    //                """
-    //             ).indented(),
-    //             *stubs
-    //     )
-    //             .run()
-    //             .expect("""
-    //             src/test/pkg/TestClass1.java:13: Warning: Missing RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED flag [UnspecifiedRegisterReceiverFlag]
-    //                     context.registerReceiver(receiver, filter);
-    //                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-    //             0 errors, 1 warnings
-    //         """.trimIndent())
-    // }
-
-    fun testNullReceiver() {
-        lint().files(
-                java(
-                        """
-                    package test.pkg;
-                    import android.content.BroadcastReceiver;
-                    import android.content.Context;
-                    import android.content.Intent;
-                    import android.content.IntentFilter;
-                    public class TestClass1 {
-                        public void testMethod(Context context) {
-                            IntentFilter filter = new IntentFilter("qwerty");
-                            context.registerReceiver(null, filter);
-                        }
-                    }
-                   """
-                ).indented(),
-                *stubs
-        )
-                .run()
-                .expectClean()
-    }
-
-    fun testExportedFlagPresent() {
-        lint().files(
-                java(
-                        """
-                    package test.pkg;
-                    import android.content.BroadcastReceiver;
-                    import android.content.Context;
-                    import android.content.Intent;
-                    import android.content.IntentFilter;
-                    public class TestClass1 {
-                        public void testMethod(Context context, BroadcastReceiver receiver) {
-                            IntentFilter filter = new IntentFilter("qwerty");
-                            context.registerReceiver(receiver, filter, Context.RECEIVER_EXPORTED);
-                        }
-                    }
-                   """
-                ).indented(),
-                *stubs
-        )
-                .run()
-                .expectClean()
-    }
-
-    fun testNotExportedFlagPresent() {
-        lint().files(
-                java(
-                        """
-                    package test.pkg;
-                    import android.content.BroadcastReceiver;
-                    import android.content.Context;
-                    import android.content.Intent;
-                    import android.content.IntentFilter;
-                    public class TestClass1 {
-                        public void testMethod(Context context, BroadcastReceiver receiver) {
-                            IntentFilter filter = new IntentFilter("qwerty");
-                            context.registerReceiver(receiver, filter,
-                                    Context.RECEIVER_NOT_EXPORTED);
-                        }
-                    }
-                   """
-                ).indented(),
-                *stubs
-        )
-                .run()
-                .expectClean()
-    }
-
-    // TODO(b/267510341): Reenable this test
-    // fun testFlagArgumentAbsent() {
-    //     lint().files(
-    //             java(
-    //                     """
-    //                 package test.pkg;
-    //                 import android.content.BroadcastReceiver;
-    //                 import android.content.Context;
-    //                 import android.content.Intent;
-    //                 import android.content.IntentFilter;
-    //                 public class TestClass1 {
-    //                     public void testMethod(Context context, BroadcastReceiver receiver) {
-    //                         IntentFilter filter = new IntentFilter("qwerty");
-    //                         context.registerReceiver(receiver, filter);
-    //                     }
-    //                 }
-    //                """
-    //             ).indented(),
-    //             *stubs
-    //     )
-    //             .run()
-    //             .expect("""
-    //             src/test/pkg/TestClass1.java:9: Warning: Missing RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED flag [UnspecifiedRegisterReceiverFlag]
-    //                     context.registerReceiver(receiver, filter);
-    //                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-    //             0 errors, 1 warnings
-    //         """.trimIndent())
-    // }
-
-    // TODO(b/267510341): Reenable this test
-    // fun testExportedFlagsAbsent() {
-    //     lint().files(
-    //             java(
-    //                     """
-    //                 package test.pkg;
-    //                 import android.content.BroadcastReceiver;
-    //                 import android.content.Context;
-    //                 import android.content.Intent;
-    //                 import android.content.IntentFilter;
-    //                 public class TestClass1 {
-    //                     public void testMethod(Context context, BroadcastReceiver receiver) {
-    //                         IntentFilter filter = new IntentFilter("qwerty");
-    //                         context.registerReceiver(receiver, filter, 0);
-    //                     }
-    //                 }
-    //                """
-    //             ).indented(),
-    //             *stubs
-    //     )
-    //             .run()
-    //             .expect("""
-    //             src/test/pkg/TestClass1.java:9: Warning: Missing RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED flag [UnspecifiedRegisterReceiverFlag]
-    //                     context.registerReceiver(receiver, filter, 0);
-    //                                                                ~
-    //             0 errors, 1 warnings
-    //         """.trimIndent())
-    // }
-
-    fun testExportedFlagVariable() {
-        lint().files(
-                java(
-                        """
-                    package test.pkg;
-                    import android.content.BroadcastReceiver;
-                    import android.content.Context;
-                    import android.content.Intent;
-                    import android.content.IntentFilter;
-                    public class TestClass1 {
-                        public void testMethod(Context context, BroadcastReceiver receiver) {
-                            IntentFilter filter = new IntentFilter("qwerty");
-                            var flags = Context.RECEIVER_EXPORTED;
-                            context.registerReceiver(receiver, filter, flags);
-                        }
-                    }
-                   """
-                ).indented(),
-                *stubs
-        )
-                .run()
-                .expectClean()
-    }
-
-    // TODO(b/267510341): Reenable this test
-    // fun testUnknownFilter() {
-    //     lint().files(
-    //             java(
-    //                     """
-    //                 package test.pkg;
-    //                 import android.content.BroadcastReceiver;
-    //                 import android.content.Context;
-    //                 import android.content.Intent;
-    //                 import android.content.IntentFilter;
-    //                 public class TestClass1 {
-    //                     public void testMethod(Context context, BroadcastReceiver receiver,
-    //                             IntentFilter filter) {
-    //                         context.registerReceiver(receiver, filter);
-    //                     }
-    //                 }
-    //                """
-    //             ).indented(),
-    //             *stubs
-    //     )
-    //             .run()
-    //             .expect("""
-    //             src/test/pkg/TestClass1.java:9: Warning: Missing RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED flag [UnspecifiedRegisterReceiverFlag]
-    //                     context.registerReceiver(receiver, filter);
-    //                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-    //             0 errors, 1 warnings
-    //         """.trimIndent())
-    // }
-
-    // TODO(b/267510341): Reenable this test
-    // fun testFilterEscapes() {
-    //     lint().files(
-    //             java(
-    //                     """
-    //                 package test.pkg;
-    //                 import android.content.BroadcastReceiver;
-    //                 import android.content.Context;
-    //                 import android.content.Intent;
-    //                 import android.content.IntentFilter;
-    //                 public class TestClass1 {
-    //                     public void testMethod(Context context, BroadcastReceiver receiver) {
-    //                         IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
-    //                         updateFilter(filter);
-    //                         context.registerReceiver(receiver, filter);
-    //                     }
-    //                 }
-    //                """
-    //             ).indented(),
-    //             *stubs
-    //     )
-    //             .run()
-    //             .expect("""
-    //             src/test/pkg/TestClass1.java:10: Warning: Missing RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED flag [UnspecifiedRegisterReceiverFlag]
-    //                     context.registerReceiver(receiver, filter);
-    //                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-    //             0 errors, 1 warnings
-    //         """.trimIndent())
-    // }
-
-    fun testInlineFilter() {
-        lint().files(
-                java(
-                        """
-                    package test.pkg;
-                    import android.content.BroadcastReceiver;
-                    import android.content.Context;
-                    import android.content.Intent;
-                    import android.content.IntentFilter;
-                    public class TestClass1 {
-                        public void testMethod(Context context, BroadcastReceiver receiver) {
-                            context.registerReceiver(receiver,
-                                    new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
-                        }
-                    }
-                   """
-                ).indented(),
-                *stubs
-        )
-                .run()
-                .expectClean()
-    }
-
-    // TODO(b/267510341): Reenable this test
-    // fun testInlineFilterApply() {
-    //     lint().files(
-    //             kotlin(
-    //                     """
-    //                 package test.pkg
-    //                 import android.content.BroadcastReceiver
-    //                 import android.content.Context
-    //                 import android.content.Intent
-    //                 import android.content.IntentFilter
-    //                 class TestClass1 {
-    //                     fun test(context: Context, receiver: BroadcastReceiver) {
-    //                         context.registerReceiver(receiver,
-    //                                 IntentFilter(Intent.ACTION_BATTERY_CHANGED).apply {
-    //                                     addAction("qwerty")
-    //                                 })
-    //                     }
-    //                 }
-    //                """
-    //             ).indented(),
-    //             *stubs
-    //     )
-    //             .run()
-    //             .expect("""
-    //             src/test/pkg/TestClass1.kt:8: Warning: Missing RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED flag [UnspecifiedRegisterReceiverFlag]
-    //                     context.registerReceiver(receiver,
-    //                     ^
-    //             0 errors, 1 warnings
-    //         """.trimIndent())
-    // }
-
-    // TODO(b/267510341): Reenable this test
-    // fun testFilterVariableApply() {
-    //     lint().files(
-    //             kotlin(
-    //                     """
-    //                 package test.pkg
-    //                 import android.content.BroadcastReceiver
-    //                 import android.content.Context
-    //                 import android.content.Intent
-    //                 import android.content.IntentFilter
-    //                 class TestClass1 {
-    //                     fun test(context: Context, receiver: BroadcastReceiver) {
-    //                         val filter = IntentFilter(Intent.ACTION_BATTERY_CHANGED).apply {
-    //                             addAction("qwerty")
-    //                         }
-    //                         context.registerReceiver(receiver, filter)
-    //                     }
-    //                 }
-    //                """
-    //             ).indented(),
-    //             *stubs
-    //     )
-    //             .run()
-    //             .expect("""
-    //             src/test/pkg/TestClass1.kt:11: Warning: Missing RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED flag [UnspecifiedRegisterReceiverFlag]
-    //                     context.registerReceiver(receiver, filter)
-    //                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-    //             0 errors, 1 warnings
-    //         """.trimIndent())
-    // }
-
-    // TODO(b/267510341): Reenable this test
-    // fun testFilterVariableApply2() {
-    //     lint().files(
-    //             kotlin(
-    //                     """
-    //                 package test.pkg
-    //                 import android.content.BroadcastReceiver
-    //                 import android.content.Context
-    //                 import android.content.Intent
-    //                 import android.content.IntentFilter
-    //                 class TestClass1 {
-    //                     fun test(context: Context, receiver: BroadcastReceiver) {
-    //                         val filter = IntentFilter(Intent.ACTION_BATTERY_CHANGED).apply {
-    //                             addAction(Intent.ACTION_BATTERY_OKAY)
-    //                         }
-    //                         context.registerReceiver(receiver, filter.apply {
-    //                             addAction("qwerty")
-    //                         })
-    //                     }
-    //                 }
-    //                """
-    //             ).indented(),
-    //             *stubs
-    //     )
-    //             .run()
-    //             .expect("""
-    //             src/test/pkg/TestClass1.kt:11: Warning: Missing RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED flag [UnspecifiedRegisterReceiverFlag]
-    //                     context.registerReceiver(receiver, filter.apply {
-    //                     ^
-    //             0 errors, 1 warnings
-    //         """.trimIndent())
-    // }
-
-    // TODO(b/267510341): Reenable this test
-    // fun testFilterComplexChain() {
-    //     lint().files(
-    //             kotlin(
-    //                     """
-    //                 package test.pkg
-    //                 import android.content.BroadcastReceiver
-    //                 import android.content.Context
-    //                 import android.content.Intent
-    //                 import android.content.IntentFilter
-    //                 class TestClass1 {
-    //                     fun test(context: Context, receiver: BroadcastReceiver) {
-    //                         val filter = IntentFilter(Intent.ACTION_BATTERY_CHANGED).apply {
-    //                             addAction(Intent.ACTION_BATTERY_OKAY)
-    //                         }
-    //                         val filter2 = filter
-    //                         val filter3 = filter2.apply {
-    //                             addAction(Intent.ACTION_BATTERY_LOW)
-    //                         }
-    //                         context.registerReceiver(receiver, filter3)
-    //                         val filter4 = filter3.apply {
-    //                             addAction("qwerty")
-    //                         }
-    //                         context.registerReceiver(receiver, filter4)
-    //                     }
-    //                 }
-    //                """
-    //             ).indented(),
-    //             *stubs
-    //     )
-    //             .run()
-    //             .expect("""
-    //             src/test/pkg/TestClass1.kt:19: Warning: Missing RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED flag [UnspecifiedRegisterReceiverFlag]
-    //                     context.registerReceiver(receiver, filter4)
-    //                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-    //             0 errors, 1 warnings
-    //         """.trimIndent())
-    // }
-
-    private val broadcastReceiverStub: TestFile = java(
-            """
-            package android.content;
-            public class BroadcastReceiver {
-                // Stub
-            }
-            """
-    ).indented()
-
-    private val contextStub: TestFile = java(
-            """
-            package android.content;
-            public class Context {
-                public static final int RECEIVER_EXPORTED = 0x2;
-                public static final int RECEIVER_NOT_EXPORTED = 0x4;
-                @Nullable
-                public abstract Intent registerReceiver(@Nullable BroadcastReceiver receiver,
-                                                        IntentFilter filter,
-                                                        @RegisterReceiverFlags int flags);
-            }
-            """
-    ).indented()
-
-    private val intentStub: TestFile = java(
-            """
-            package android.content;
-            public class Intent {
-                public static final String ACTION_BATTERY_CHANGED =
-                        "android.intent.action.BATTERY_CHANGED";
-                public static final String ACTION_BATTERY_LOW = "android.intent.action.BATTERY_LOW";
-                public static final String ACTION_BATTERY_OKAY =
-                        "android.intent.action.BATTERY_OKAY";
-            }
-            """
-    ).indented()
-
-    private val intentFilterStub: TestFile = java(
-            """
-            package android.content;
-            public class IntentFilter {
-                public IntentFilter() {
-                    // Stub
-                }
-                public IntentFilter(String action) {
-                    // Stub
-                }
-                public IntentFilter(String action, String dataType) {
-                    // Stub
-                }
-                public static IntentFilter create(String action, String dataType) {
-                    return null;
-                }
-                public final void addAction(String action) {
-                    // Stub
-                }
-            }
-            """
-    ).indented()
-
-    private val stubs = arrayOf(broadcastReceiverStub, contextStub, intentStub, intentFilterStub)
-}
diff --git a/tools/locked_region_code_injection/Android.bp b/tools/locked_region_code_injection/Android.bp
index a0cc446..954b816 100644
--- a/tools/locked_region_code_injection/Android.bp
+++ b/tools/locked_region_code_injection/Android.bp
@@ -19,3 +19,20 @@
         "ow2-asm-tree",
     ],
 }
+
+java_library_host {
+   name: "lockedregioncodeinjection_input",
+   manifest: "test/manifest.txt",
+   srcs: ["test/*/*.java"],
+   static_libs: [
+        "guava",
+        "ow2-asm",
+        "ow2-asm-analysis",
+        "ow2-asm-commons",
+        "ow2-asm-tree",
+        "hamcrest-library",
+        "hamcrest",
+        "platform-test-annotations",
+        "junit",
+    ],
+}
diff --git a/tools/locked_region_code_injection/OWNERS b/tools/locked_region_code_injection/OWNERS
new file mode 100644
index 0000000..bd43f17
--- /dev/null
+++ b/tools/locked_region_code_injection/OWNERS
@@ -0,0 +1,4 @@
+# Everyone in frameworks/base is included by default
+shayba@google.com
+shombert@google.com
+timmurray@google.com
diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java
index 81a0773..2067bb4 100644
--- a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java
+++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java
@@ -13,37 +13,51 @@
  */
 package lockedregioncodeinjection;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.LinkedList;
-import java.util.List;
+import static com.google.common.base.Preconditions.checkElementIndex;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
 import org.objectweb.asm.ClassVisitor;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 import org.objectweb.asm.commons.TryCatchBlockSorter;
 import org.objectweb.asm.tree.AbstractInsnNode;
 import org.objectweb.asm.tree.InsnList;
+import org.objectweb.asm.tree.InsnNode;
 import org.objectweb.asm.tree.LabelNode;
+import org.objectweb.asm.tree.LineNumberNode;
 import org.objectweb.asm.tree.MethodInsnNode;
 import org.objectweb.asm.tree.MethodNode;
+import org.objectweb.asm.tree.TypeInsnNode;
 import org.objectweb.asm.tree.TryCatchBlockNode;
 import org.objectweb.asm.tree.analysis.Analyzer;
 import org.objectweb.asm.tree.analysis.AnalyzerException;
 import org.objectweb.asm.tree.analysis.BasicValue;
 import org.objectweb.asm.tree.analysis.Frame;
 
-import static com.google.common.base.Preconditions.checkElementIndex;
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Preconditions.checkState;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
 
 /**
- * This visitor does two things:
+ * This visitor operates on two kinds of targets.  For a legacy target, it does the following:
  *
- * 1. Finds all the MONITOR_ENTER / MONITOR_EXIT in the byte code and insert the corresponding pre
+ * 1. Finds all the MONITOR_ENTER / MONITOR_EXIT in the byte code and inserts the corresponding pre
  * and post methods calls should it matches one of the given target type in the Configuration.
  *
  * 2. Find all methods that are synchronized and insert pre method calls in the beginning and post
  * method calls just before all return instructions.
+ *
+ * For a scoped target, it does the following:
+ *
+ * 1. Finds all the MONITOR_ENTER instructions in the byte code.  If the target of the opcode is
+ *    named in a --scope switch, then the pre method is invoked ON THE TARGET immediately after
+ *    MONITOR_ENTER opcode completes.
+ *
+ * 2. Finds all the MONITOR_EXIT instructions in the byte code.  If the target of the opcode is
+ *    named in a --scope switch, then the post method is invoked ON THE TARGET immediately before
+ *    MONITOR_EXIT opcode completes.
  */
 class LockFindingClassVisitor extends ClassVisitor {
     private String className = null;
@@ -73,12 +87,16 @@
     class LockFindingMethodVisitor extends MethodVisitor {
         private String owner;
         private MethodVisitor chain;
+        private final String className;
+        private final String methodName;
 
         public LockFindingMethodVisitor(String owner, MethodNode mn, MethodVisitor chain) {
             super(Utils.ASM_VERSION, mn);
             assert owner != null;
             this.owner = owner;
             this.chain = chain;
+            className = owner;
+            methodName = mn.name;
         }
 
         @SuppressWarnings("unchecked")
@@ -93,6 +111,12 @@
                 for (LockTarget t : targets) {
                     if (t.getTargetDesc().equals("L" + owner + ";")) {
                         ownerMonitor = t;
+                        if (ownerMonitor.getScoped()) {
+                            final String emsg = String.format(
+                                "scoped targets do not support synchronized methods in %s.%s()",
+                                className, methodName);
+                            throw new RuntimeException(emsg);
+                        }
                     }
                 }
             }
@@ -118,9 +142,11 @@
                 AbstractInsnNode s = instructions.getFirst();
                 MethodInsnNode call = new MethodInsnNode(Opcodes.INVOKESTATIC,
                         ownerMonitor.getPreOwner(), ownerMonitor.getPreMethod(), "()V", false);
-                insertMethodCallBefore(mn, frameMap, handlersMap, s, 0, call);
+                insertMethodCallBeforeSync(mn, frameMap, handlersMap, s, 0, call);
             }
 
+            boolean anyDup = false;
+
             for (int i = 0; i < instructions.size(); i++) {
                 AbstractInsnNode s = instructions.get(i);
 
@@ -131,9 +157,15 @@
                         LockTargetState state = (LockTargetState) operand;
                         for (int j = 0; j < state.getTargets().size(); j++) {
                             LockTarget target = state.getTargets().get(j);
-                            MethodInsnNode call = new MethodInsnNode(Opcodes.INVOKESTATIC,
-                                    target.getPreOwner(), target.getPreMethod(), "()V", false);
-                            insertMethodCallAfter(mn, frameMap, handlersMap, s, i, call);
+                            MethodInsnNode call = methodCall(target, true);
+                            if (target.getScoped()) {
+                                TypeInsnNode cast = typeCast(target);
+                                i += insertInvokeAcquire(mn, frameMap, handlersMap, s, i,
+                                        call, cast);
+                                anyDup = true;
+                            } else {
+                                i += insertMethodCallBefore(mn, frameMap, handlersMap, s, i, call);
+                            }
                         }
                     }
                 }
@@ -144,8 +176,9 @@
                     if (operand instanceof LockTargetState) {
                         LockTargetState state = (LockTargetState) operand;
                         for (int j = 0; j < state.getTargets().size(); j++) {
-                            // The instruction after a monitor_exit should be a label for the end of the implicit
-                            // catch block that surrounds the synchronized block to call monitor_exit when an exception
+                            // The instruction after a monitor_exit should be a label for
+                            // the end of the implicit catch block that surrounds the
+                            // synchronized block to call monitor_exit when an exception
                             // occurs.
                             checkState(instructions.get(i + 1).getType() == AbstractInsnNode.LABEL,
                                 "Expected to find label after monitor exit");
@@ -161,9 +194,16 @@
                                 "Expected label to be the end of monitor exit's try block");
 
                             LockTarget target = state.getTargets().get(j);
-                            MethodInsnNode call = new MethodInsnNode(Opcodes.INVOKESTATIC,
-                                    target.getPostOwner(), target.getPostMethod(), "()V", false);
-                            insertMethodCallAfter(mn, frameMap, handlersMap, label, labelIndex, call);
+                            MethodInsnNode call = methodCall(target, false);
+                            if (target.getScoped()) {
+                                TypeInsnNode cast = typeCast(target);
+                                i += insertInvokeRelease(mn, frameMap, handlersMap, s, i,
+                                        call, cast);
+                                anyDup = true;
+                            } else {
+                                insertMethodCallAfter(mn, frameMap, handlersMap, label,
+                                        labelIndex, call);
+                            }
                         }
                     }
                 }
@@ -174,16 +214,116 @@
                     MethodInsnNode call =
                             new MethodInsnNode(Opcodes.INVOKESTATIC, ownerMonitor.getPostOwner(),
                                     ownerMonitor.getPostMethod(), "()V", false);
-                    insertMethodCallBefore(mn, frameMap, handlersMap, s, i, call);
+                    insertMethodCallBeforeSync(mn, frameMap, handlersMap, s, i, call);
                     i++; // Skip ahead. Otherwise, we will revisit this instruction again.
                 }
             }
+
+            if (anyDup) {
+                mn.maxStack++;
+            }
+
             super.visitEnd();
             mn.accept(chain);
         }
+
+        // Insert a call to a monitor pre handler.  The node and the index identify the
+        // monitorenter call itself.  Insert DUP immediately prior to the MONITORENTER.
+        // Insert the typecast and call (in that order) after the MONITORENTER.
+        public int insertInvokeAcquire(MethodNode mn, List<Frame> frameMap,
+                List<List<TryCatchBlockNode>> handlersMap, AbstractInsnNode node, int index,
+                MethodInsnNode call, TypeInsnNode cast) {
+            InsnList instructions = mn.instructions;
+
+            // Insert a DUP right before MONITORENTER, to capture the object being locked.
+            // Note that the object will be typed as java.lang.Object.
+            instructions.insertBefore(node, new InsnNode(Opcodes.DUP));
+            frameMap.add(index, frameMap.get(index));
+            handlersMap.add(index, handlersMap.get(index));
+
+            // Insert the call right after the MONITORENTER.  These entries are pushed after
+            // MONITORENTER so they are inserted in reverse order.  MONITORENTER should be
+            // the target of a try/catch block, which means it must be immediately
+            // followed by a label (which is part of the try/catch block definition).
+            // Move forward past the label so the invocation in inside the proper block.
+            // Throw an error if the next instruction is not a label.
+            node = node.getNext();
+            if (!(node instanceof LabelNode)) {
+                throw new RuntimeException(String.format("invalid bytecode sequence in %s.%s()",
+                                className, methodName));
+            }
+            node = node.getNext();
+            index = instructions.indexOf(node);
+
+            instructions.insertBefore(node, cast);
+            frameMap.add(index, frameMap.get(index));
+            handlersMap.add(index, handlersMap.get(index));
+
+            instructions.insertBefore(node, call);
+            frameMap.add(index, frameMap.get(index));
+            handlersMap.add(index, handlersMap.get(index));
+
+            return 3;
+        }
+
+        // Insert instructions completely before the current opcode.  This is slightly
+        // different from insertMethodCallBefore(), which inserts the call before MONITOREXIT
+        // but inserts the start and end labels after MONITOREXIT.
+        public int insertInvokeRelease(MethodNode mn, List<Frame> frameMap,
+                List<List<TryCatchBlockNode>> handlersMap, AbstractInsnNode node, int index,
+                MethodInsnNode call, TypeInsnNode cast) {
+            InsnList instructions = mn.instructions;
+
+            instructions.insertBefore(node, new InsnNode(Opcodes.DUP));
+            frameMap.add(index, frameMap.get(index));
+            handlersMap.add(index, handlersMap.get(index));
+
+            instructions.insertBefore(node, cast);
+            frameMap.add(index, frameMap.get(index));
+            handlersMap.add(index, handlersMap.get(index));
+
+            instructions.insertBefore(node, call);
+            frameMap.add(index, frameMap.get(index));
+            handlersMap.add(index, handlersMap.get(index));
+
+            return 3;
+        }
     }
 
-    public static void insertMethodCallBefore(MethodNode mn, List<Frame> frameMap,
+    public static MethodInsnNode methodCall(LockTarget target, boolean pre) {
+        String spec = "()V";
+        if (!target.getScoped()) {
+            if (pre) {
+                return new MethodInsnNode(
+                    Opcodes.INVOKESTATIC, target.getPreOwner(), target.getPreMethod(), spec);
+            } else {
+                return new MethodInsnNode(
+                    Opcodes.INVOKESTATIC, target.getPostOwner(), target.getPostMethod(), spec);
+            }
+        } else {
+            if (pre) {
+                return new MethodInsnNode(
+                    Opcodes.INVOKEVIRTUAL, target.getPreOwner(), target.getPreMethod(), spec);
+            } else {
+                return new MethodInsnNode(
+                    Opcodes.INVOKEVIRTUAL, target.getPostOwner(), target.getPostMethod(), spec);
+            }
+        }
+    }
+
+    public static TypeInsnNode typeCast(LockTarget target) {
+        if (!target.getScoped()) {
+            return null;
+        } else {
+            // preOwner and postOwner return the same string for scoped targets.
+            return new TypeInsnNode(Opcodes.CHECKCAST, target.getPreOwner());
+        }
+    }
+
+    /**
+     * Insert a method call before the beginning or end of a synchronized method.
+     */
+    public static void insertMethodCallBeforeSync(MethodNode mn, List<Frame> frameMap,
             List<List<TryCatchBlockNode>> handlersMap, AbstractInsnNode node, int index,
             MethodInsnNode call) {
         List<TryCatchBlockNode> handlers = handlersMap.get(index);
@@ -226,6 +366,22 @@
         updateCatchHandler(mn, handlers, start, end, handlersMap);
     }
 
+    // Insert instructions completely before the current opcode.  This is slightly different from
+    // insertMethodCallBeforeSync(), which inserts the call before MONITOREXIT but inserts the
+    // start and end labels after MONITOREXIT.
+    public int insertMethodCallBefore(MethodNode mn, List<Frame> frameMap,
+            List<List<TryCatchBlockNode>> handlersMap, AbstractInsnNode node, int index,
+            MethodInsnNode call) {
+        InsnList instructions = mn.instructions;
+
+        instructions.insertBefore(node, call);
+        frameMap.add(index, frameMap.get(index));
+        handlersMap.add(index, handlersMap.get(index));
+
+        return 1;
+    }
+
+
     @SuppressWarnings("unchecked")
     public static void updateCatchHandler(MethodNode mn, List<TryCatchBlockNode> handlers,
             LabelNode start, LabelNode end, List<List<TryCatchBlockNode>> handlersMap) {
diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTarget.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTarget.java
index c5e59e3..5f62403 100644
--- a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTarget.java
+++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTarget.java
@@ -21,14 +21,28 @@
 public class LockTarget {
     public static final LockTarget NO_TARGET = new LockTarget("", null, null);
 
+    // The lock which must be instrumented, in Java internal form (L<path>;).
     private final String targetDesc;
+    // The methods to be called when the lock is taken (released).  For non-scoped locks,
+    // these are fully qualified static methods.  For scoped locks, these are the
+    // unqualified names of a member method of the target lock.
     private final String pre;
     private final String post;
+    // If true, the pre and post methods are virtual on the target class.  The pre and post methods
+    // are both called while the lock is held.  If this field is false then the pre and post methods
+    // take no parameters and the post method is called after the lock is released.  This is legacy
+    // behavior.
+    private final boolean scoped;
 
-    public LockTarget(String targetDesc, String pre, String post) {
+    public LockTarget(String targetDesc, String pre, String post, boolean scoped) {
         this.targetDesc = targetDesc;
         this.pre = pre;
         this.post = post;
+        this.scoped = scoped;
+    }
+
+    public LockTarget(String targetDesc, String pre, String post) {
+        this(targetDesc, pre, post, false);
     }
 
     public String getTargetDesc() {
@@ -40,7 +54,11 @@
     }
 
     public String getPreOwner() {
-        return pre.substring(0, pre.lastIndexOf('.'));
+        if (scoped) {
+            return targetDesc.substring(1, targetDesc.length() - 1);
+        } else {
+            return pre.substring(0, pre.lastIndexOf('.'));
+        }
     }
 
     public String getPreMethod() {
@@ -52,10 +70,23 @@
     }
 
     public String getPostOwner() {
-        return post.substring(0, post.lastIndexOf('.'));
+        if (scoped) {
+            return targetDesc.substring(1, targetDesc.length() - 1);
+        } else {
+            return post.substring(0, post.lastIndexOf('.'));
+        }
     }
 
     public String getPostMethod() {
         return post.substring(post.lastIndexOf('.') + 1);
     }
+
+    public boolean getScoped() {
+        return scoped;
+    }
+
+    @Override
+    public String toString() {
+        return targetDesc + ":" + pre + ":" + post;
+    }
 }
diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTargetState.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTargetState.java
index 99d8418..5df0160 100644
--- a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTargetState.java
+++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTargetState.java
@@ -13,10 +13,11 @@
  */
 package lockedregioncodeinjection;
 
-import java.util.List;
 import org.objectweb.asm.Type;
 import org.objectweb.asm.tree.analysis.BasicValue;
 
+import java.util.List;
+
 public class LockTargetState extends BasicValue {
     private final List<LockTarget> lockTargets;
 
@@ -31,4 +32,10 @@
     public List<LockTarget> getTargets() {
         return lockTargets;
     }
+
+    @Override
+    public String toString() {
+        return "LockTargetState(" + getType().getDescriptor()
+                + ", " + lockTargets.size() + ")";
+    }
 }
diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/Main.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/Main.java
index 828cce7..d22ea23 100644
--- a/tools/locked_region_code_injection/src/lockedregioncodeinjection/Main.java
+++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/Main.java
@@ -21,7 +21,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.util.Collections;
+import java.util.ArrayList;
 import java.util.Enumeration;
 import java.util.List;
 import java.util.zip.ZipEntry;
@@ -36,6 +36,7 @@
         String legacyTargets = null;
         String legacyPreMethods = null;
         String legacyPostMethods = null;
+        List<LockTarget> targets = new ArrayList<>();
         for (int i = 0; i < args.length; i++) {
             if ("-i".equals(args[i].trim())) {
                 i++;
@@ -52,23 +53,25 @@
             } else if ("--post".equals(args[i].trim())) {
                 i++;
                 legacyPostMethods = args[i].trim();
+            } else if ("--scoped".equals(args[i].trim())) {
+                i++;
+                targets.add(Utils.getScopedTarget(args[i].trim()));
             }
-
         }
 
-        // TODO(acleung): Better help message than asserts.
-        assert inJar != null;
-        assert outJar != null;
+        if (inJar == null) {
+            throw new RuntimeException("missing input jar path");
+        }
+        if (outJar == null) {
+            throw new RuntimeException("missing output jar path");
+        }
         assert legacyTargets == null || (legacyPreMethods != null && legacyPostMethods != null);
 
         ZipFile zipSrc = new ZipFile(inJar);
         ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(outJar));
-        List<LockTarget> targets = null;
         if (legacyTargets != null) {
-            targets = Utils.getTargetsFromLegacyJackConfig(legacyTargets, legacyPreMethods,
-                    legacyPostMethods);
-        } else {
-            targets = Collections.emptyList();
+            targets.addAll(Utils.getTargetsFromLegacyJackConfig(legacyTargets, legacyPreMethods,
+                                                                legacyPostMethods));
         }
 
         Enumeration<? extends ZipEntry> srcEntries = zipSrc.entries();
diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java
index b44e8b4..bfef106 100644
--- a/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java
+++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java
@@ -44,4 +44,27 @@
 
         return config;
     }
+
+    /**
+     * Returns a single {@link LockTarget} from a string.  The target is a comma-separated list of
+     * the target class, the request method, the release method, and a boolean which is true if this
+     * is a scoped target and false if this is a legacy target.  The boolean is optional and
+     * defaults to true.
+     */
+    public static LockTarget getScopedTarget(String arg) {
+        String[] c = arg.split(",");
+        if (c.length == 3) {
+          return new LockTarget(c[0], c[1], c[2], true);
+        } else if (c.length == 4) {
+            if (c[3].equals("true")) {
+                return new LockTarget(c[0], c[1], c[2], true);
+            } else if (c[3].equals("false")) {
+                return new LockTarget(c[0], c[1], c[2], false);
+            } else {
+                System.err.println("illegal target parameter \"" + c[3] + "\"");
+            }
+        }
+        // Fall through
+        throw new RuntimeException("invalid scoped target format");
+    }
 }
diff --git a/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java
index 31fa0bf..28f00b9 100644
--- a/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java
+++ b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java
@@ -17,7 +17,10 @@
 import org.junit.Test;
 
 /**
- * To run the unit tests:
+ * To run the unit tests, first build the two necessary artifacts.  Do this explicitly as they are
+ * not generally retained by a normal "build all".  After lunching a target:
+ *   m lockedregioncodeinjection
+ *   m lockedregioncodeinjection_input
  *
  * <pre>
  * <code>
@@ -29,31 +32,25 @@
  * mkdir -p out
  * rm -fr out/*
  *
- * # Make booster
- * javac -cp lib/asm-7.0_BETA.jar:lib/asm-commons-7.0_BETA.jar:lib/asm-tree-7.0_BETA.jar:lib/asm-analysis-7.0_BETA.jar:lib/guava-21.0.jar src&#47;*&#47;*.java -d out/
- * pushd out
- * jar cfe lockedregioncodeinjection.jar lockedregioncodeinjection.Main *&#47;*.class
- * popd
- *
- * # Make unit tests.
- * javac -cp lib/junit-4.12.jar test&#47;*&#47;*.java -d out/
- *
- * pushd out
- * jar cfe test_input.jar lockedregioncodeinjection.Test *&#47;*.class
- * popd
+ * # Paths to the build artifacts.  These assume linux-x86; YMMV.
+ * ROOT=$TOP/out/host/linux-x86
+ * EXE=$ROOT/bin/lockedregioncodeinjection
+ * INPUT=$ROOT/frameworkd/lockedregioncodeinjection_input.jar
  *
  * # Run tool on unit tests.
- * java -ea -cp lib/asm-7.0_BETA.jar:lib/asm-commons-7.0_BETA.jar:lib/asm-tree-7.0_BETA.jar:lib/asm-analysis-7.0_BETA.jar:lib/guava-21.0.jar:out/lockedregioncodeinjection.jar \
- *     lockedregioncodeinjection.Main \
- *     -i out/test_input.jar -o out/test_output.jar \
+ * $EXE -i $INPUT -o out/test_output.jar \
  *     --targets 'Llockedregioncodeinjection/TestTarget;' \
  *     --pre     'lockedregioncodeinjection/TestTarget.boost' \
  *     --post    'lockedregioncodeinjection/TestTarget.unboost'
  *
  * # Run unit tests.
- * java -ea -cp lib/hamcrest-core-1.3.jar:lib/junit-4.12.jar:out/test_output.jar \
+ * java -ea -cp out/test_output.jar \
  *     org.junit.runner.JUnitCore lockedregioncodeinjection.TestMain
  * </code>
+ * OR
+ * <code>
+ * bash test/unit-test.sh
+ * </code>
  * </pre>
  */
 public class TestMain {
@@ -64,7 +61,9 @@
 
         Assert.assertEquals(TestTarget.boostCount, 0);
         Assert.assertEquals(TestTarget.unboostCount, 0);
-        Assert.assertEquals(TestTarget.unboostCount, 0);
+        Assert.assertEquals(TestTarget.invokeCount, 0);
+        Assert.assertEquals(TestTarget.boostCountLocked, 0);
+        Assert.assertEquals(TestTarget.unboostCountLocked, 0);
 
         synchronized (t) {
             Assert.assertEquals(TestTarget.boostCount, 1);
@@ -75,6 +74,8 @@
         Assert.assertEquals(TestTarget.boostCount, 1);
         Assert.assertEquals(TestTarget.unboostCount, 1);
         Assert.assertEquals(TestTarget.invokeCount, 1);
+        Assert.assertEquals(TestTarget.boostCountLocked, 0);
+        Assert.assertEquals(TestTarget.unboostCountLocked, 0);
     }
 
     @Test
@@ -84,12 +85,16 @@
 
         Assert.assertEquals(TestTarget.boostCount, 0);
         Assert.assertEquals(TestTarget.unboostCount, 0);
+        Assert.assertEquals(TestTarget.boostCountLocked, 0);
+        Assert.assertEquals(TestTarget.unboostCountLocked, 0);
 
         t.synchronizedCall();
 
         Assert.assertEquals(TestTarget.boostCount, 1);
         Assert.assertEquals(TestTarget.unboostCount, 1);
         Assert.assertEquals(TestTarget.invokeCount, 1);
+        Assert.assertEquals(TestTarget.boostCountLocked, 0);
+        Assert.assertEquals(TestTarget.unboostCountLocked, 0);
     }
 
     @Test
@@ -99,12 +104,16 @@
 
         Assert.assertEquals(TestTarget.boostCount, 0);
         Assert.assertEquals(TestTarget.unboostCount, 0);
+        Assert.assertEquals(TestTarget.boostCountLocked, 0);
+        Assert.assertEquals(TestTarget.unboostCountLocked, 0);
 
         t.synchronizedCallReturnInt();
 
         Assert.assertEquals(TestTarget.boostCount, 1);
         Assert.assertEquals(TestTarget.unboostCount, 1);
         Assert.assertEquals(TestTarget.invokeCount, 1);
+        Assert.assertEquals(TestTarget.boostCountLocked, 0);
+        Assert.assertEquals(TestTarget.unboostCountLocked, 0);
     }
 
     @Test
@@ -253,4 +262,125 @@
         Assert.assertEquals(TestTarget.invokeCount, 1);
     }
 
+    @Test
+    public void testScopedTarget() {
+        TestScopedTarget target = new TestScopedTarget();
+        Assert.assertEquals(0, target.scopedLock().mLevel);
+
+        synchronized (target.scopedLock()) {
+            Assert.assertEquals(1, target.scopedLock().mLevel);
+        }
+        Assert.assertEquals(0, target.scopedLock().mLevel);
+
+        synchronized (target.scopedLock()) {
+            synchronized (target.scopedLock()) {
+                Assert.assertEquals(2, target.scopedLock().mLevel);
+            }
+        }
+        Assert.assertEquals(0, target.scopedLock().mLevel);
+    }
+
+    @Test
+    public void testScopedExceptionHandling() {
+        TestScopedTarget target = new TestScopedTarget();
+        Assert.assertEquals(0, target.scopedLock().mLevel);
+
+        boolean handled;
+
+        // 1: an exception inside the block properly releases the lock.
+        handled = false;
+        try {
+            synchronized (target.scopedLock()) {
+                Assert.assertEquals(true, Thread.holdsLock(target.scopedLock()));
+                Assert.assertEquals(1, target.scopedLock().mLevel);
+                throw new RuntimeException();
+            }
+        } catch (RuntimeException e) {
+            Assert.assertEquals(0, target.scopedLock().mLevel);
+            handled = true;
+        }
+        Assert.assertEquals(0, target.scopedLock().mLevel);
+        Assert.assertEquals(true, handled);
+        // Just verify that the lock can still be taken
+        Assert.assertEquals(false, Thread.holdsLock(target.scopedLock()));
+
+        // 2: An exception inside the monitor enter function
+        handled = false;
+        target.throwOnEnter(true);
+        try {
+            synchronized (target.scopedLock()) {
+                // The exception was thrown inside monitorEnter(), so the code should
+                // never reach this point.
+                Assert.assertEquals(0, 1);
+            }
+        } catch (RuntimeException e) {
+            Assert.assertEquals(0, target.scopedLock().mLevel);
+            handled = true;
+        }
+        Assert.assertEquals(0, target.scopedLock().mLevel);
+        Assert.assertEquals(true, handled);
+        // Just verify that the lock can still be taken
+        Assert.assertEquals(false, Thread.holdsLock(target.scopedLock()));
+
+        // 3: An exception inside the monitor exit function
+        handled = false;
+        target.throwOnEnter(true);
+        try {
+            synchronized (target.scopedLock()) {
+                Assert.assertEquals(true, Thread.holdsLock(target.scopedLock()));
+                Assert.assertEquals(1, target.scopedLock().mLevel);
+            }
+        } catch (RuntimeException e) {
+            Assert.assertEquals(0, target.scopedLock().mLevel);
+            handled = true;
+        }
+        Assert.assertEquals(0, target.scopedLock().mLevel);
+        Assert.assertEquals(true, handled);
+        // Just verify that the lock can still be taken
+        Assert.assertEquals(false, Thread.holdsLock(target.scopedLock()));
+    }
+
+    // Provide an in-class type conversion for the scoped target.
+    private Object untypedLock(TestScopedTarget target) {
+        return target.scopedLock();
+    }
+
+    @Test
+    public void testScopedLockTyping() {
+        TestScopedTarget target = new TestScopedTarget();
+        Assert.assertEquals(target.scopedLock().mLevel, 0);
+
+        // Scoped lock injection works on the static type of an object.  In general, it is
+        // a very bad idea to do type conversion on scoped locks, but the general rule is
+        // that conversions within a single method are recognized by the lock injection
+        // tool and injection occurs.  Conversions outside a single method are not
+        // recognized and injection does not occur.
+
+        // 1. Conversion occurs outside the class.  The visible type of the lock is Object
+        // in this block, so no injection takes place on 'untypedLock', even though the
+        // dynamic type is TestScopedLock.
+        synchronized (target.untypedLock()) {
+            Assert.assertEquals(0, target.scopedLock().mLevel);
+            Assert.assertEquals(true, target.untypedLock() instanceof TestScopedLock);
+            Assert.assertEquals(true, Thread.holdsLock(target.scopedLock()));
+        }
+
+        // 2. Conversion occurs inside the class but in another method.  The visible type
+        // of the lock is Object in this block, so no injection takes place on
+        // 'untypedLock', even though the dynamic type is TestScopedLock.
+        synchronized (untypedLock(target)) {
+            Assert.assertEquals(0, target.scopedLock().mLevel);
+            Assert.assertEquals(true, target.untypedLock() instanceof TestScopedLock);
+            Assert.assertEquals(true, Thread.holdsLock(target.scopedLock()));
+        }
+
+        // 3. Conversion occurs inside the method.  The compiler can determine the type of
+        // the lock within a single function, so injection does take place here.
+        Object untypedLock = target.scopedLock();
+        synchronized (untypedLock) {
+            Assert.assertEquals(1, target.scopedLock().mLevel);
+            Assert.assertEquals(true, untypedLock instanceof TestScopedLock);
+            Assert.assertEquals(true, Thread.holdsLock(target.scopedLock()));
+        }
+    }
 }
diff --git a/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestScopedLock.java b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestScopedLock.java
new file mode 100644
index 0000000..7441d2b
--- /dev/null
+++ b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestScopedLock.java
@@ -0,0 +1,38 @@
+/*
+ * 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 lockedregioncodeinjection;
+
+public class TestScopedLock {
+    public int mEntered = 0;
+    public int mExited = 0;
+
+    public int mLevel = 0;
+    private final TestScopedTarget mTarget;
+
+    TestScopedLock(TestScopedTarget target) {
+        mTarget = target;
+    }
+
+    void monitorEnter() {
+        mLevel++;
+        mEntered++;
+        mTarget.enter(mLevel);
+    }
+
+    void monitorExit() {
+        mLevel--;
+        mExited++;
+        mTarget.exit(mLevel);
+    }
+}
diff --git a/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestScopedTarget.java b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestScopedTarget.java
new file mode 100644
index 0000000..c80975e
--- /dev/null
+++ b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestScopedTarget.java
@@ -0,0 +1,56 @@
+/*
+ * 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 lockedregioncodeinjection;
+
+public class TestScopedTarget {
+
+    public final TestScopedLock mLock;;
+
+    private boolean mNextEnterThrows = false;
+    private boolean mNextExitThrows = false;
+
+    TestScopedTarget() {
+        mLock = new TestScopedLock(this);
+    }
+
+    TestScopedLock scopedLock() {
+        return mLock;
+    }
+
+    Object untypedLock() {
+        return mLock;
+    }
+
+    void enter(int level) {
+        if (mNextEnterThrows) {
+            mNextEnterThrows = false;
+            throw new RuntimeException();
+        }
+    }
+
+    void exit(int level) {
+        if (mNextExitThrows) {
+            mNextExitThrows = false;
+            throw new RuntimeException();
+        }
+    }
+
+    void throwOnEnter(boolean b) {
+        mNextEnterThrows = b;
+    }
+
+    void throwOnExit(boolean b) {
+        mNextExitThrows = b;
+    }
+}
diff --git a/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestTarget.java b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestTarget.java
index d1c8f34..e3ba6a7 100644
--- a/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestTarget.java
+++ b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestTarget.java
@@ -19,8 +19,17 @@
   public static int invokeCount = 0;
   public static boolean nextUnboostThrows = false;
 
+  // If this is not null, then this is the lock under test.  The lock must not be held when boost()
+  // or unboost() are called.
+  public static Object mLock = null;
+  public static int boostCountLocked = 0;
+  public static int unboostCountLocked = 0;
+
   public static void boost() {
     boostCount++;
+    if (mLock != null && Thread.currentThread().holdsLock(mLock)) {
+      boostCountLocked++;
+    }
   }
 
   public static void unboost() {
@@ -29,6 +38,9 @@
       throw new RuntimeException();
     }
     unboostCount++;
+    if (mLock != null && Thread.currentThread().holdsLock(mLock)) {
+      unboostCountLocked++;
+    }
   }
 
   public static void invoke() {
diff --git a/tools/locked_region_code_injection/test/manifest.txt b/tools/locked_region_code_injection/test/manifest.txt
new file mode 100644
index 0000000..2314c18
--- /dev/null
+++ b/tools/locked_region_code_injection/test/manifest.txt
@@ -0,0 +1 @@
+Main-Class: org.junit.runner.JUnitCore
diff --git a/tools/locked_region_code_injection/test/unit-test.sh b/tools/locked_region_code_injection/test/unit-test.sh
new file mode 100755
index 0000000..9fa6f39
--- /dev/null
+++ b/tools/locked_region_code_injection/test/unit-test.sh
@@ -0,0 +1,98 @@
+#! /bin/bash
+#
+
+# 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.
+
+# This script runs the tests for the lockedregioninjectioncode.  See
+# TestMain.java for the invocation.  The script expects that a full build has
+# already been done and artifacts are in $TOP/out.
+
+# Compute the default top of the workspace.  The following code copies the
+# strategy of croot.  (croot cannot be usd directly because it is a function and
+# functions are not carried over into subshells.)  This gives the correct answer
+# if run from inside a workspace.  If run from outside a workspace, supply TOP
+# on the command line.
+TOPFILE=build/make/core/envsetup.mk
+TOP=$(dirname $(realpath $0))
+while [[ ! $TOP = / && ! -f $TOP/$TOPFILE ]]; do
+  TOP=$(dirname $TOP)
+done
+# TOP is "/" if this script is located outside a workspace.
+
+# If the user supplied a top directory, use it instead
+if [[ -n $1 ]]; then
+  TOP=$1
+  shift
+fi
+if [[ -z $TOP || $TOP = / ]]; then
+  echo "usage: $0 <workspace-root>"
+  exit 1
+elif [[ ! -d $TOP ]]; then
+  echo "$TOP is not a directory"
+  exit 1
+elif [[ ! -d $TOP/prebuilts/misc/common ]]; then
+  echo "$TOP does not look like w workspace"
+  exit 1
+fi
+echo "Using workspace $TOP"
+
+# Pick up the current java compiler.  The lunch target is not very important,
+# since most, if not all, will use the same host binaries.
+pushd $TOP > /dev/null
+. build/envsetup.sh > /dev/null 2>&1
+lunch redfin-userdebug > /dev/null 2>&1
+popd > /dev/null
+
+# Bail on any error
+set -o pipefail
+trap 'exit 1' ERR
+
+# Create the two sources
+pushd $TOP > /dev/null
+m lockedregioncodeinjection
+m lockedregioncodeinjection_input
+popd > /dev/null
+
+# Create a temporary directory outside of the workspace.
+OUT=$TOP/out/host/test/lockedregioncodeinjection
+echo
+
+# Clean the directory
+if [[ -d $OUT ]]; then rm -r $OUT; fi
+mkdir -p $OUT
+
+ROOT=$TOP/out/host/linux-x86
+EXE=$ROOT/bin/lockedregioncodeinjection
+INP=$ROOT/framework/lockedregioncodeinjection_input.jar
+
+# Run tool on unit tests.
+$EXE \
+    -i $INP -o $OUT/test_output.jar \
+    --targets 'Llockedregioncodeinjection/TestTarget;' \
+    --pre     'lockedregioncodeinjection/TestTarget.boost' \
+    --post    'lockedregioncodeinjection/TestTarget.unboost' \
+    --scoped  'Llockedregioncodeinjection/TestScopedLock;,monitorEnter,monitorExit'
+
+# Run unit tests.
+java -ea -cp $OUT/test_output.jar \
+    org.junit.runner.JUnitCore lockedregioncodeinjection.TestMain
+
+# Extract the class files and decompile them for possible post-analysis.
+pushd $OUT > /dev/null
+jar -x --file test_output.jar lockedregioncodeinjection
+for class in lockedregioncodeinjection/*.class; do
+  javap -c -v $class > ${class%.class}.asm
+done
+popd > /dev/null
+
+echo "artifacts are in $OUT"
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/HotspotNetwork.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/HotspotNetwork.java
index 9bfeb63..fe397d9 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/HotspotNetwork.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/HotspotNetwork.java
@@ -23,6 +23,7 @@
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.net.wifi.sharedconnectivity.service.SharedConnectivityService;
+import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.ArraySet;
@@ -86,6 +87,7 @@
     @Nullable
     @SecurityType
     private final ArraySet<Integer> mHotspotSecurityTypes;
+    private final Bundle mExtras;
 
     /**
      * Builder class for {@link HotspotNetwork}.
@@ -102,8 +104,8 @@
         private String mHotspotBssid;
         @Nullable
         @SecurityType
-        private final ArraySet<Integer> mHotspotSecurityTypes =
-                new ArraySet<>();
+        private final ArraySet<Integer> mHotspotSecurityTypes = new ArraySet<>();
+        private Bundle mExtras = Bundle.EMPTY;
 
         /**
          * Set the remote device ID.
@@ -190,6 +192,17 @@
         }
 
         /**
+         * Sets the extras bundle
+         *
+         * @return Returns the Builder object.
+         */
+        @NonNull
+        public Builder setExtras(@NonNull Bundle extras) {
+            mExtras = extras;
+            return this;
+        }
+
+        /**
          * Builds the {@link HotspotNetwork} object.
          *
          * @return Returns the built {@link HotspotNetwork} object.
@@ -203,7 +216,8 @@
                     mNetworkName,
                     mHotspotSsid,
                     mHotspotBssid,
-                    mHotspotSecurityTypes);
+                    mHotspotSecurityTypes,
+                    mExtras);
         }
     }
 
@@ -231,7 +245,8 @@
             @NonNull String networkName,
             @Nullable String hotspotSsid,
             @Nullable String hotspotBssid,
-            @Nullable @SecurityType ArraySet<Integer> hotspotSecurityTypes) {
+            @Nullable @SecurityType ArraySet<Integer> hotspotSecurityTypes,
+            @NonNull Bundle extras) {
         validate(deviceId,
                 networkType,
                 networkName,
@@ -243,6 +258,7 @@
         mHotspotSsid = hotspotSsid;
         mHotspotBssid = hotspotBssid;
         mHotspotSecurityTypes = new ArraySet<>(hotspotSecurityTypes);
+        mExtras = extras;
     }
 
     /**
@@ -315,6 +331,16 @@
         return mHotspotSecurityTypes;
     }
 
+    /**
+     * Gets the extras Bundle.
+     *
+     * @return Returns a Bundle object.
+     */
+    @NonNull
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
     @Override
     public boolean equals(Object obj) {
         if (!(obj instanceof HotspotNetwork)) return false;
@@ -348,6 +374,7 @@
         dest.writeString(mHotspotSsid);
         dest.writeString(mHotspotBssid);
         dest.writeArraySet(mHotspotSecurityTypes);
+        dest.writeBundle(mExtras);
     }
 
     /**
@@ -359,7 +386,7 @@
     public static HotspotNetwork readFromParcel(@NonNull Parcel in) {
         return new HotspotNetwork(in.readLong(), NetworkProviderInfo.readFromParcel(in),
                 in.readInt(), in.readString(), in.readString(), in.readString(),
-                (ArraySet<Integer>) in.readArraySet(null));
+                (ArraySet<Integer>) in.readArraySet(null), in.readBundle());
     }
 
     @NonNull
@@ -385,6 +412,7 @@
                 .append(", hotspotSsid=").append(mHotspotSsid)
                 .append(", hotspotBssid=").append(mHotspotBssid)
                 .append(", hotspotSecurityTypes=").append(mHotspotSecurityTypes.toString())
+                .append(", extras=").append(mExtras.toString())
                 .append("]").toString();
     }
 }
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/HotspotNetworkConnectionStatus.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/HotspotNetworkConnectionStatus.java
index 69767f3..72acf2c 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/HotspotNetworkConnectionStatus.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/HotspotNetworkConnectionStatus.java
@@ -117,7 +117,7 @@
         @ConnectionStatus
         private int mStatus;
         private HotspotNetwork mHotspotNetwork;
-        private Bundle mExtras;
+        private Bundle mExtras = Bundle.EMPTY;
 
         /**
          * Sets the status of the connection
@@ -179,7 +179,7 @@
     }
 
     private HotspotNetworkConnectionStatus(@ConnectionStatus int status,
-            HotspotNetwork hotspotNetwork, Bundle extras) {
+            HotspotNetwork hotspotNetwork, @NonNull Bundle extras) {
         validate(status);
         mStatus = status;
         mHotspotNetwork = hotspotNetwork;
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.java
index 64412bc..c390e42 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.java
@@ -22,6 +22,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
@@ -73,6 +74,7 @@
     @SecurityType
     private final ArraySet<Integer> mSecurityTypes;
     private final NetworkProviderInfo mNetworkProviderInfo;
+    private final Bundle mExtras;
 
     /**
      * Builder class for {@link KnownNetwork}.
@@ -84,6 +86,7 @@
         @SecurityType
         private final ArraySet<Integer> mSecurityTypes = new ArraySet<>();
         private NetworkProviderInfo mNetworkProviderInfo;
+        private Bundle mExtras = Bundle.EMPTY;
 
         /**
          * Sets the indicated source of the known network.
@@ -135,6 +138,17 @@
         }
 
         /**
+         * Sets the extras bundle
+         *
+         * @return Returns the Builder object.
+         */
+        @NonNull
+        public Builder setExtras(@NonNull Bundle extras) {
+            mExtras = extras;
+            return this;
+        }
+
+        /**
          * Builds the {@link KnownNetwork} object.
          *
          * @return Returns the built {@link KnownNetwork} object.
@@ -145,7 +159,8 @@
                     mNetworkSource,
                     mSsid,
                     mSecurityTypes,
-                    mNetworkProviderInfo);
+                    mNetworkProviderInfo,
+                    mExtras);
         }
     }
 
@@ -173,12 +188,14 @@
             @NetworkSource int networkSource,
             @NonNull String ssid,
             @NonNull @SecurityType ArraySet<Integer> securityTypes,
-            @Nullable NetworkProviderInfo networkProviderInfo) {
+            @Nullable NetworkProviderInfo networkProviderInfo,
+            @NonNull Bundle extras) {
         validate(networkSource, ssid, securityTypes, networkProviderInfo);
         mNetworkSource = networkSource;
         mSsid = ssid;
         mSecurityTypes = new ArraySet<>(securityTypes);
         mNetworkProviderInfo = networkProviderInfo;
+        mExtras = extras;
     }
 
     /**
@@ -223,6 +240,16 @@
         return mNetworkProviderInfo;
     }
 
+    /**
+     * Gets the extras Bundle.
+     *
+     * @return Returns a Bundle object.
+     */
+    @NonNull
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
     @Override
     public boolean equals(Object obj) {
         if (!(obj instanceof KnownNetwork)) return false;
@@ -249,6 +276,7 @@
         dest.writeString(mSsid);
         dest.writeArraySet(mSecurityTypes);
         mNetworkProviderInfo.writeToParcel(dest, flags);
+        dest.writeBundle(mExtras);
     }
 
     /**
@@ -260,7 +288,7 @@
     public static KnownNetwork readFromParcel(@NonNull Parcel in) {
         return new KnownNetwork(in.readInt(), in.readString(),
                 (ArraySet<Integer>) in.readArraySet(null),
-                NetworkProviderInfo.readFromParcel(in));
+                NetworkProviderInfo.readFromParcel(in), in.readBundle());
     }
 
     @NonNull
@@ -283,6 +311,7 @@
                 .append(", ssid=").append(mSsid)
                 .append(", securityTypes=").append(mSecurityTypes.toString())
                 .append(", networkProviderInfo=").append(mNetworkProviderInfo.toString())
+                .append(", extras=").append(mExtras.toString())
                 .append("]").toString();
     }
 }
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetworkConnectionStatus.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetworkConnectionStatus.java
index 6bd0a5e..b30dc3f 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetworkConnectionStatus.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetworkConnectionStatus.java
@@ -72,7 +72,7 @@
     public static final class Builder {
         @ConnectionStatus private int mStatus;
         private KnownNetwork mKnownNetwork;
-        private Bundle mExtras;
+        private Bundle mExtras = Bundle.EMPTY;
 
         public Builder() {}
 
@@ -128,7 +128,7 @@
     }
 
     private KnownNetworkConnectionStatus(@ConnectionStatus int status, KnownNetwork knownNetwork,
-            Bundle extras) {
+            @NonNull Bundle extras) {
         validate(status);
         mStatus = status;
         mKnownNetwork = knownNetwork;
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
index ed4d699..25fbabc 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
@@ -21,6 +21,7 @@
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.net.wifi.sharedconnectivity.service.SharedConnectivityService;
+import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -89,6 +90,7 @@
     private final String mModelName;
     private final int mBatteryPercentage;
     private final int mConnectionStrength;
+    private final Bundle mExtras;
 
     /**
      * Builder class for {@link NetworkProviderInfo}.
@@ -99,6 +101,7 @@
         private String mModelName;
         private int mBatteryPercentage;
         private int mConnectionStrength;
+        private Bundle mExtras = Bundle.EMPTY;
 
         public Builder(@NonNull String deviceName, @NonNull String modelName) {
             Objects.requireNonNull(deviceName);
@@ -170,6 +173,17 @@
         }
 
         /**
+         * Sets the extras bundle
+         *
+         * @return Returns the Builder object.
+         */
+        @NonNull
+        public Builder setExtras(@NonNull Bundle extras) {
+            mExtras = extras;
+            return this;
+        }
+
+        /**
          * Builds the {@link NetworkProviderInfo} object.
          *
          * @return Returns the built {@link NetworkProviderInfo} object.
@@ -177,7 +191,7 @@
         @NonNull
         public NetworkProviderInfo build() {
             return new NetworkProviderInfo(mDeviceType, mDeviceName, mModelName, mBatteryPercentage,
-                    mConnectionStrength);
+                    mConnectionStrength, mExtras);
         }
     }
 
@@ -197,13 +211,15 @@
     }
 
     private NetworkProviderInfo(@DeviceType int deviceType, @NonNull String deviceName,
-            @NonNull String modelName, int batteryPercentage, int connectionStrength) {
+            @NonNull String modelName, int batteryPercentage, int connectionStrength,
+            @NonNull Bundle extras) {
         validate(deviceType, deviceName, modelName, batteryPercentage, connectionStrength);
         mDeviceType = deviceType;
         mDeviceName = deviceName;
         mModelName = modelName;
         mBatteryPercentage = batteryPercentage;
         mConnectionStrength = connectionStrength;
+        mExtras = extras;
     }
 
     /**
@@ -256,6 +272,16 @@
         return mConnectionStrength;
     }
 
+    /**
+     * Gets the extras Bundle.
+     *
+     * @return Returns a Bundle object.
+     */
+    @NonNull
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
     @Override
     public boolean equals(Object obj) {
         if (!(obj instanceof NetworkProviderInfo)) return false;
@@ -280,6 +306,7 @@
         dest.writeString(mModelName);
         dest.writeInt(mBatteryPercentage);
         dest.writeInt(mConnectionStrength);
+        dest.writeBundle(mExtras);
     }
 
     @Override
@@ -295,7 +322,7 @@
     @NonNull
     public static NetworkProviderInfo readFromParcel(@NonNull Parcel in) {
         return new NetworkProviderInfo(in.readInt(), in.readString(), in.readString(), in.readInt(),
-                in.readInt());
+                in.readInt(), in.readBundle());
     }
 
     @NonNull
@@ -319,6 +346,7 @@
                 .append(", modelName=").append(mModelName)
                 .append(", batteryPercentage=").append(mBatteryPercentage)
                 .append(", connectionStrength=").append(mConnectionStrength)
+                .append(", extras=").append(mExtras.toString())
                 .append("]").toString();
     }
 }
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java
index 4809bef..30bb989 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java
@@ -17,7 +17,11 @@
 package android.net.wifi.sharedconnectivity.app;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -37,6 +41,7 @@
 public final class SharedConnectivitySettingsState implements Parcelable {
 
     private final boolean mInstantTetherEnabled;
+    private final PendingIntent mInstantTetherSettingsPendingIntent;
     private final Bundle mExtras;
 
     /**
@@ -44,9 +49,13 @@
      */
     public static final class Builder {
         private boolean mInstantTetherEnabled;
-        private Bundle mExtras;
+        private Intent mInstantTetherSettingsIntent;
+        private final Context mContext;
+        private Bundle mExtras = Bundle.EMPTY;
 
-        public Builder() {}
+        public Builder(@NonNull Context context) {
+            mContext = context;
+        }
 
         /**
          * Sets the state of Instant Tether in settings
@@ -60,6 +69,20 @@
         }
 
         /**
+         * Sets the intent that will open the Instant Tether settings page.
+         * The intent will be stored as a {@link PendingIntent} in the settings object. The pending
+         * intent will be set as {@link PendingIntent#FLAG_IMMUTABLE} and
+         * {@link PendingIntent#FLAG_ONE_SHOT}.
+         *
+         * @return Returns the Builder object.
+         */
+        @NonNull
+        public Builder setInstantTetherSettingsPendingIntent(@NonNull Intent intent) {
+            mInstantTetherSettingsIntent = intent;
+            return this;
+        }
+
+        /**
          * Sets the extras bundle
          *
          * @return Returns the Builder object.
@@ -77,12 +100,21 @@
          */
         @NonNull
         public SharedConnectivitySettingsState build() {
-            return new SharedConnectivitySettingsState(mInstantTetherEnabled, mExtras);
+            if (mInstantTetherSettingsIntent != null) {
+                PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0,
+                        mInstantTetherSettingsIntent,
+                        PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT);
+                return new SharedConnectivitySettingsState(mInstantTetherEnabled,
+                        pendingIntent, mExtras);
+            }
+            return new SharedConnectivitySettingsState(mInstantTetherEnabled, null, mExtras);
         }
     }
 
-    private SharedConnectivitySettingsState(boolean instantTetherEnabled, Bundle extras) {
+    private SharedConnectivitySettingsState(boolean instantTetherEnabled,
+            PendingIntent pendingIntent, @NonNull Bundle extras) {
         mInstantTetherEnabled = instantTetherEnabled;
+        mInstantTetherSettingsPendingIntent = pendingIntent;
         mExtras = extras;
     }
 
@@ -96,6 +128,16 @@
     }
 
     /**
+     * Gets the pending intent to open Instant Tether settings page.
+     *
+     * @return Returns the pending intent that opens the settings page, null if none.
+     */
+    @Nullable
+    public PendingIntent getInstantTetherSettingsPendingIntent() {
+        return mInstantTetherSettingsPendingIntent;
+    }
+
+    /**
      * Gets the extras Bundle.
      *
      * @return Returns a Bundle object.
@@ -109,12 +151,14 @@
     public boolean equals(Object obj) {
         if (!(obj instanceof SharedConnectivitySettingsState)) return false;
         SharedConnectivitySettingsState other = (SharedConnectivitySettingsState) obj;
-        return mInstantTetherEnabled == other.isInstantTetherEnabled();
+        return mInstantTetherEnabled == other.isInstantTetherEnabled()
+                && Objects.equals(mInstantTetherSettingsPendingIntent,
+                other.getInstantTetherSettingsPendingIntent());
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mInstantTetherEnabled);
+        return Objects.hash(mInstantTetherEnabled, mInstantTetherSettingsPendingIntent);
     }
 
     @Override
@@ -124,6 +168,7 @@
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
+        mInstantTetherSettingsPendingIntent.writeToParcel(dest, 0);
         dest.writeBoolean(mInstantTetherEnabled);
         dest.writeBundle(mExtras);
     }
@@ -135,8 +180,10 @@
      */
     @NonNull
     public static SharedConnectivitySettingsState readFromParcel(@NonNull Parcel in) {
-        return new SharedConnectivitySettingsState(in.readBoolean(),
-                in.readBundle());
+        PendingIntent pendingIntent = PendingIntent.CREATOR.createFromParcel(in);
+        boolean instantTetherEnabled = in.readBoolean();
+        Bundle extras = in.readBundle();
+        return new SharedConnectivitySettingsState(instantTetherEnabled, pendingIntent, extras);
     }
 
     @NonNull
@@ -156,6 +203,7 @@
     public String toString() {
         return new StringBuilder("SharedConnectivitySettingsState[")
                 .append("instantTetherEnabled=").append(mInstantTetherEnabled)
+                .append("PendingIntent=").append(mInstantTetherSettingsPendingIntent.toString())
                 .append("extras=").append(mExtras.toString())
                 .append("]").toString();
     }
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java b/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java
index 57108e4..87ca99f 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java
@@ -68,9 +68,7 @@
             new RemoteCallbackList<>();
     private List<HotspotNetwork> mHotspotNetworks = Collections.emptyList();
     private List<KnownNetwork> mKnownNetworks = Collections.emptyList();
-    private SharedConnectivitySettingsState mSettingsState =
-            new SharedConnectivitySettingsState.Builder().setInstantTetherEnabled(false)
-                    .setExtras(Bundle.EMPTY).build();
+    private SharedConnectivitySettingsState mSettingsState = null;
     private HotspotNetworkConnectionStatus mHotspotNetworkConnectionStatus =
             new HotspotNetworkConnectionStatus.Builder()
                     .setStatus(HotspotNetworkConnectionStatus.CONNECTION_STATUS_UNKNOWN)
@@ -202,6 +200,13 @@
             @Override
             public SharedConnectivitySettingsState getSettingsState() {
                 checkPermissions();
+                // Done lazily since creating it needs a context.
+                if (mSettingsState == null) {
+                    mSettingsState = new SharedConnectivitySettingsState
+                            .Builder(getApplicationContext())
+                            .setInstantTetherEnabled(false)
+                            .setExtras(Bundle.EMPTY).build();
+                }
                 return mSettingsState;
             }
 
diff --git a/wifi/tests/Android.bp b/wifi/tests/Android.bp
index c9105f7..7a29969 100644
--- a/wifi/tests/Android.bp
+++ b/wifi/tests/Android.bp
@@ -36,6 +36,7 @@
 
     static_libs: [
         "androidx.test.rules",
+        "androidx.test.core",
         "frameworks-base-testutils",
         "guava",
         "mockito-target-minus-junit4",
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/HotspotNetworkTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/HotspotNetworkTest.java
index 8302094..0827ffa 100644
--- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/HotspotNetworkTest.java
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/HotspotNetworkTest.java
@@ -28,6 +28,7 @@
 
 import static org.junit.Assert.assertThrows;
 
+import android.os.Bundle;
 import android.os.Parcel;
 import android.util.ArraySet;
 
@@ -55,6 +56,8 @@
     private static final String HOTSPOT_SSID = "TEST_SSID";
     private static final String HOTSPOT_BSSID = "TEST _BSSID";
     private static final int[] HOTSPOT_SECURITY_TYPES = {SECURITY_TYPE_WEP, SECURITY_TYPE_EAP};
+    private static final String BUNDLE_KEY = "INT-KEY";
+    private static final int BUNDLE_VALUE = 1;
 
     private static final long DEVICE_ID_1 = 111L;
     private static final NetworkProviderInfo NETWORK_PROVIDER_INFO1 =
@@ -138,6 +141,7 @@
         assertThat(network.getHotspotSsid()).isEqualTo(HOTSPOT_SSID);
         assertThat(network.getHotspotBssid()).isEqualTo(HOTSPOT_BSSID);
         assertThat(network.getHotspotSecurityTypes()).containsExactlyElementsIn(securityTypes);
+        assertThat(network.getExtras().getInt(BUNDLE_KEY)).isEqualTo(BUNDLE_VALUE);
     }
 
     @Test
@@ -161,11 +165,18 @@
                 .setHostNetworkType(NETWORK_TYPE)
                 .setNetworkName(NETWORK_NAME)
                 .setHotspotSsid(HOTSPOT_SSID)
-                .setHotspotBssid(HOTSPOT_BSSID);
+                .setHotspotBssid(HOTSPOT_BSSID)
+                .setExtras(buildBundle());
         Arrays.stream(HOTSPOT_SECURITY_TYPES).forEach(builder::addHotspotSecurityType);
         if (withNetworkProviderInfo) {
             builder.setNetworkProviderInfo(NETWORK_PROVIDER_INFO);
         }
         return builder;
     }
+
+    private Bundle buildBundle() {
+        Bundle bundle = new Bundle();
+        bundle.putInt(BUNDLE_KEY, BUNDLE_VALUE);
+        return bundle;
+    }
 }
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/KnownNetworkTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/KnownNetworkTest.java
index 1ecba76..81d7b44 100644
--- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/KnownNetworkTest.java
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/KnownNetworkTest.java
@@ -25,6 +25,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.os.Bundle;
 import android.os.Parcel;
 import android.util.ArraySet;
 
@@ -47,6 +48,9 @@
             new NetworkProviderInfo.Builder("TEST_NAME", "TEST_MODEL")
                     .setDeviceType(DEVICE_TYPE_TABLET).setConnectionStrength(2)
                     .setBatteryPercentage(50).build();
+    private static final String BUNDLE_KEY = "INT-KEY";
+    private static final int BUNDLE_VALUE = 1;
+
     private static final int NETWORK_SOURCE_1 = NETWORK_SOURCE_CLOUD_SELF;
     private static final String SSID_1 = "TEST_SSID1";
     private static final int[] SECURITY_TYPES_1 = {SECURITY_TYPE_PSK};
@@ -113,6 +117,7 @@
         assertThat(network.getSsid()).isEqualTo(SSID);
         assertThat(network.getSecurityTypes()).containsExactlyElementsIn(securityTypes);
         assertThat(network.getNetworkProviderInfo()).isEqualTo(NETWORK_PROVIDER_INFO);
+        assertThat(network.getExtras().getInt(BUNDLE_KEY)).isEqualTo(BUNDLE_VALUE);
     }
 
     @Test
@@ -125,8 +130,15 @@
 
     private KnownNetwork.Builder buildKnownNetworkBuilder() {
         KnownNetwork.Builder builder = new KnownNetwork.Builder().setNetworkSource(NETWORK_SOURCE)
-                .setSsid(SSID).setNetworkProviderInfo(NETWORK_PROVIDER_INFO);
+                .setSsid(SSID).setNetworkProviderInfo(NETWORK_PROVIDER_INFO)
+                .setExtras(buildBundle());
         Arrays.stream(SECURITY_TYPES).forEach(builder::addSecurityType);
         return builder;
     }
+
+    private Bundle buildBundle() {
+        Bundle bundle = new Bundle();
+        bundle.putInt(BUNDLE_KEY, BUNDLE_VALUE);
+        return bundle;
+    }
 }
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfoTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfoTest.java
index 8f35d8d..4aa9ca6 100644
--- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfoTest.java
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfoTest.java
@@ -21,6 +21,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.os.Bundle;
 import android.os.Parcel;
 
 import androidx.test.filters.SmallTest;
@@ -38,6 +39,8 @@
     private static final String DEVICE_MODEL = "TEST_MODEL";
     private static final int BATTERY_PERCENTAGE = 50;
     private static final int CONNECTION_STRENGTH = 2;
+    private static final String BUNDLE_KEY = "INT-KEY";
+    private static final int BUNDLE_VALUE = 1;
 
     private static final int DEVICE_TYPE_1 = DEVICE_TYPE_LAPTOP;
     private static final String DEVICE_NAME_1 = "TEST_NAME1";
@@ -105,6 +108,7 @@
         assertThat(info.getModelName()).isEqualTo(DEVICE_MODEL);
         assertThat(info.getBatteryPercentage()).isEqualTo(BATTERY_PERCENTAGE);
         assertThat(info.getConnectionStrength()).isEqualTo(CONNECTION_STRENGTH);
+        assertThat(info.getExtras().getInt(BUNDLE_KEY)).isEqualTo(BUNDLE_VALUE);
     }
 
     @Test
@@ -118,6 +122,13 @@
     private NetworkProviderInfo.Builder buildNetworkProviderInfoBuilder() {
         return new NetworkProviderInfo.Builder(DEVICE_NAME, DEVICE_MODEL).setDeviceType(DEVICE_TYPE)
                 .setBatteryPercentage(BATTERY_PERCENTAGE)
-                .setConnectionStrength(CONNECTION_STRENGTH);
+                .setConnectionStrength(CONNECTION_STRENGTH)
+                .setExtras(buildBundle());
+    }
+
+    private Bundle buildBundle() {
+        Bundle bundle = new Bundle();
+        bundle.putInt(BUNDLE_KEY, BUNDLE_VALUE);
+        return bundle;
     }
 }
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java
index 7578dfd..71239087 100644
--- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java
@@ -497,8 +497,9 @@
     @Test
     public void getSettingsState_serviceConnected_shouldReturnState() throws RemoteException {
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
-        SharedConnectivitySettingsState state = new SharedConnectivitySettingsState.Builder()
-                .setInstantTetherEnabled(true).setExtras(new Bundle()).build();
+        SharedConnectivitySettingsState state =
+                new SharedConnectivitySettingsState.Builder(mContext).setInstantTetherEnabled(true)
+                        .setExtras(new Bundle()).build();
         manager.setService(mService);
         when(mService.getSettingsState()).thenReturn(state);
 
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsStateTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsStateTest.java
index 752b749..5e17dfb 100644
--- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsStateTest.java
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsStateTest.java
@@ -18,8 +18,10 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.content.Intent;
 import android.os.Parcel;
 
+import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.SmallTest;
 
 import org.junit.Test;
@@ -30,8 +32,12 @@
 @SmallTest
 public class SharedConnectivitySettingsStateTest {
     private static final boolean INSTANT_TETHER_STATE = true;
+    private static final String INTENT_ACTION = "instant.tether.settings";
 
     private static final boolean INSTANT_TETHER_STATE_1 = false;
+    private static final String INTENT_ACTION_1 = "instant.tether.settings1";
+
+
     /**
      * Verifies parcel serialization/deserialization.
      */
@@ -39,16 +45,11 @@
     public void testParcelOperation() {
         SharedConnectivitySettingsState state = buildSettingsStateBuilder().build();
 
-        Parcel parcelW = Parcel.obtain();
-        state.writeToParcel(parcelW, 0);
-        byte[] bytes = parcelW.marshall();
-        parcelW.recycle();
-
-        Parcel parcelR = Parcel.obtain();
-        parcelR.unmarshall(bytes, 0, bytes.length);
-        parcelR.setDataPosition(0);
+        Parcel parcel = Parcel.obtain();
+        state.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
         SharedConnectivitySettingsState fromParcel =
-                SharedConnectivitySettingsState.CREATOR.createFromParcel(parcelR);
+                SharedConnectivitySettingsState.CREATOR.createFromParcel(parcel);
 
         assertThat(fromParcel).isEqualTo(state);
         assertThat(fromParcel.hashCode()).isEqualTo(state.hashCode());
@@ -66,6 +67,10 @@
         SharedConnectivitySettingsState.Builder builder = buildSettingsStateBuilder()
                 .setInstantTetherEnabled(INSTANT_TETHER_STATE_1);
         assertThat(builder.build()).isNotEqualTo(state1);
+
+        builder = buildSettingsStateBuilder()
+                .setInstantTetherSettingsPendingIntent(new Intent(INTENT_ACTION_1));
+        assertThat(builder.build()).isNotEqualTo(state1);
     }
 
     /**
@@ -86,7 +91,9 @@
     }
 
     private SharedConnectivitySettingsState.Builder buildSettingsStateBuilder() {
-        return new SharedConnectivitySettingsState.Builder()
-                .setInstantTetherEnabled(INSTANT_TETHER_STATE);
+        return new SharedConnectivitySettingsState.Builder(
+                ApplicationProvider.getApplicationContext())
+                .setInstantTetherEnabled(INSTANT_TETHER_STATE)
+                .setInstantTetherSettingsPendingIntent(new Intent(INTENT_ACTION));
     }
 }
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java
index b8b6b767..514ba3c 100644
--- a/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java
@@ -75,9 +75,6 @@
                     .addSecurityType(SECURITY_TYPE_EAP).setNetworkProviderInfo(
                             NETWORK_PROVIDER_INFO).build();
     private static final List<KnownNetwork> KNOWN_NETWORKS = List.of(KNOWN_NETWORK);
-    private static final SharedConnectivitySettingsState SETTINGS_STATE =
-            new SharedConnectivitySettingsState.Builder().setInstantTetherEnabled(true)
-                    .setExtras(Bundle.EMPTY).build();
     private static final HotspotNetworkConnectionStatus TETHER_NETWORK_CONNECTION_STATUS =
             new HotspotNetworkConnectionStatus.Builder().setStatus(CONNECTION_STATUS_UNKNOWN)
                     .setHotspotNetwork(HOTSPOT_NETWORK).setExtras(Bundle.EMPTY).build();
@@ -155,10 +152,11 @@
         SharedConnectivityService service = createService();
         ISharedConnectivityService.Stub binder =
                 (ISharedConnectivityService.Stub) service.onBind(new Intent());
+        when(mContext.getPackageName()).thenReturn("android.net.wifi.nonupdatable.test");
 
-        service.setSettingsState(SETTINGS_STATE);
+        service.setSettingsState(buildSettingsState());
 
-        assertThat(binder.getSettingsState()).isEqualTo(SETTINGS_STATE);
+        assertThat(binder.getSettingsState()).isEqualTo(buildSettingsState());
     }
 
     @Test
@@ -232,4 +230,10 @@
         service.attachBaseContext(mContext);
         return service;
     }
+
+    private SharedConnectivitySettingsState buildSettingsState() {
+        return new SharedConnectivitySettingsState.Builder(mContext).setInstantTetherEnabled(true)
+                .setInstantTetherSettingsPendingIntent(new Intent())
+                .setExtras(Bundle.EMPTY).build();
+    }
 }