Merge "Add tests for SharedConnectivityService" into udc-dev
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/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 056b6b9..08810b5 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -483,7 +483,7 @@
                         case Constants.KEY_RUNTIME_UI_LIMIT_MS:
                         case Constants.KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR:
                         case Constants.KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS:
-                        case Constants.KEY_RUNTIME_UI_DATA_TRANSFER_LIMIT_MS:
+                        case Constants.KEY_RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS:
                             if (!runtimeUpdated) {
                                 mConstants.updateRuntimeConstantsLocked();
                                 runtimeUpdated = true;
@@ -583,8 +583,8 @@
                 "runtime_min_ui_data_transfer_guarantee_buffer_factor";
         private static final String KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS =
                 "runtime_min_ui_data_transfer_guarantee_ms";
-        private static final String KEY_RUNTIME_UI_DATA_TRANSFER_LIMIT_MS =
-                "runtime_ui_data_transfer_limit_ms";
+        private static final String KEY_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";
 
@@ -616,15 +616,14 @@
         @VisibleForTesting
         public static final long DEFAULT_RUNTIME_MIN_EJ_GUARANTEE_MS = 3 * MINUTE_IN_MILLIS;
         public static final long DEFAULT_RUNTIME_MIN_UI_GUARANTEE_MS =
-                Math.max(10 * MINUTE_IN_MILLIS, DEFAULT_RUNTIME_MIN_GUARANTEE_MS);
+                Math.max(6 * HOUR_IN_MILLIS, DEFAULT_RUNTIME_MIN_GUARANTEE_MS);
         public static final long DEFAULT_RUNTIME_UI_LIMIT_MS =
-                Math.max(60 * MINUTE_IN_MILLIS, DEFAULT_RUNTIME_FREE_QUOTA_MAX_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 long DEFAULT_RUNTIME_UI_DATA_TRANSFER_LIMIT_MS =
-                Math.min(Long.MAX_VALUE, DEFAULT_RUNTIME_UI_LIMIT_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;
 
@@ -755,13 +754,17 @@
 
         /**
          * 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_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_UI_DATA_TRANSFER_LIMIT_MS = DEFAULT_RUNTIME_UI_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
@@ -879,7 +882,7 @@
                     KEY_RUNTIME_MIN_UI_GUARANTEE_MS,
                     KEY_RUNTIME_UI_LIMIT_MS,
                     KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS,
-                    KEY_RUNTIME_UI_DATA_TRANSFER_LIMIT_MS);
+                    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,
@@ -913,15 +916,10 @@
                     properties.getLong(
                             KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS,
                             DEFAULT_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS));
-            // User-initiated requires RUN_USER_INITIATED_JOBS permission, so the upper limit will
-            // be higher than other jobs.
-            // Max limit should be the min guarantee and the max of other user-initiated jobs.
-            RUNTIME_UI_DATA_TRANSFER_LIMIT_MS = Math.max(
-                    RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS,
-                    Math.max(RUNTIME_UI_LIMIT_MS,
-                            properties.getLong(
-                                    KEY_RUNTIME_UI_DATA_TRANSFER_LIMIT_MS,
-                                    DEFAULT_RUNTIME_UI_DATA_TRANSFER_LIMIT_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) {
@@ -975,8 +973,8 @@
                     RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR).println();
             pw.print(KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS,
                     RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS).println();
-            pw.print(KEY_RUNTIME_UI_DATA_TRANSFER_LIMIT_MS,
-                    RUNTIME_UI_DATA_TRANSFER_LIMIT_MS).println();
+            pw.print(KEY_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)
@@ -3306,20 +3304,24 @@
             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_UI_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_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR);
-                    return Math.min(mConstants.RUNTIME_UI_DATA_TRANSFER_LIMIT_MS,
-                            Math.max(factoredTransferTimeMs,
-                                    mConstants.RUNTIME_MIN_UI_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_UI_GUARANTEE_MS;
             } else if (job.shouldTreatAsExpeditedJob()) {
@@ -3336,13 +3338,9 @@
     /** 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_UI_DATA_TRANSFER_LIMIT_MS;
-            }
-            if (allowLongerJob) { // UI with LRJ permission
+                            job.getSourceUid(), job.getSourcePackageName())) {
                 return mConstants.RUNTIME_UI_LIMIT_MS;
             }
             if (job.shouldTreatAsUserInitiatedJob()) {
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 0cc7758..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;
@@ -1142,6 +1141,10 @@
         mInternalFlags |= flags;
     }
 
+    int getPreferredConstraintFlags() {
+        return mPreferredConstraints;
+    }
+
     public int getSatisfiedConstraintFlags() {
         return satisfiedConstraints;
     }
@@ -2501,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/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/core/api/current.txt b/core/api/current.txt
index 79123c7..b292956 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -5281,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);
@@ -6623,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...);
   }
 
@@ -7575,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;
@@ -7894,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);
@@ -11186,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);
@@ -11205,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);
@@ -27347,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
@@ -51287,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);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 0a893f0..5a8ee94 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -968,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);
@@ -1261,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);
@@ -9984,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();
@@ -10003,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);
@@ -10039,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();
@@ -10054,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);
@@ -10085,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;
@@ -10103,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);
   }
 
@@ -13105,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();
@@ -13121,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
@@ -13140,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);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 48e7e1a..fb5d189 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/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/Notification.java b/core/java/android/app/Notification.java
index 0e89f57..502ef0d 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -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/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 6bbbfe1..924a7c6 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -15840,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.
@@ -16902,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 667ec7e..df8da24 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -5914,6 +5914,7 @@
     /**
      * 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";
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/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index c95d081..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 =
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index e6b3069..696873f 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -121,6 +121,7 @@
      * System property for allowing the above
      * @hide
      */
+    @TestApi
     public static final String LANDSCAPE_TO_PORTRAIT_PROP =
             "camera.enable_landscape_to_portrait";
 
@@ -622,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");
@@ -635,7 +646,6 @@
             try {
                 Size displaySize = getDisplaySize();
 
-                boolean overrideToPortrait = shouldOverrideToPortrait(mContext);
                 CameraMetadataNative info = cameraService.getCameraCharacteristics(cameraId,
                         mContext.getApplicationInfo().targetSdkVersion, overrideToPortrait);
                 try {
@@ -727,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 =
@@ -755,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,
@@ -891,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
@@ -994,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));
     }
 
     /**
@@ -1016,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");
@@ -1027,7 +1075,8 @@
             throw new IllegalArgumentException("No cameras available on device");
         }
 
-        openCameraDeviceUserAsync(cameraId, callback, executor, clientUid, oomScoreOffset);
+        openCameraDeviceUserAsync(cameraId, callback, executor, clientUid, oomScoreOffset,
+                overrideToPortrait);
     }
 
     /**
@@ -1048,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));
     }
 
     /**
@@ -1191,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
             }
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/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 054ae21..5dc3825 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -33,21 +33,15 @@
 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;
@@ -1380,7 +1374,7 @@
      * @hide
      */
     public Vibrator getInputDeviceVibrator(int deviceId, int vibratorId) {
-        return new InputDeviceVibrator(this, deviceId, vibratorId);
+        return new InputDeviceVibrator(deviceId, vibratorId);
     }
 
     /**
@@ -1391,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);
     }
 
     /**
@@ -1499,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);
     }
 
     /**
diff --git a/core/java/android/hardware/input/InputManagerGlobal.java b/core/java/android/hardware/input/InputManagerGlobal.java
index 524d820..08d81bd 100644
--- a/core/java/android/hardware/input/InputManagerGlobal.java
+++ b/core/java/android/hardware/input/InputManagerGlobal.java
@@ -27,12 +27,18 @@
 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;
@@ -903,4 +909,152 @@
             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/provider/Settings.java b/core/java/android/provider/Settings.java
index 127c7a0..123f480 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -10022,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.
          *
@@ -11433,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
@@ -15030,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.
@@ -18582,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)
@@ -18592,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 d0f3820..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;
         }
 
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/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/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/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 48b112d..83de2a0 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -294,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);
 
@@ -302,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 fb25e7a6..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 {
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index cc846e3..cda1f3a 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -3616,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 b77b7a6..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);
@@ -560,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/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/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/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 928a097..6344568 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -590,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/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 70a1354..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
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/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/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/fonts/fonts.xml b/data/fonts/fonts.xml
index e7ddc88..0563519 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -251,7 +251,7 @@
     <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">
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/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index e5a4362..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.
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 ea2559a..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
@@ -193,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,
@@ -942,6 +942,10 @@
         }
     }
 
+    private void setLauncherAppIconSize(int iconSizePx) {
+        mPipBoundsState.getLauncherState().setAppIconSizePx(iconSizePx);
+    }
+
     private void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {
         mOnIsInPipStateChangedListener = callback;
         if (mOnIsInPipStateChangedListener != null) {
@@ -1286,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
@@ -1323,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/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/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 f939f52..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: {
@@ -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 0d283fa..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,6 +1508,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/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/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/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/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/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/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/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 abd2c75..f6c2f69 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -314,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/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 7607909..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;
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/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 4290ca0..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" />
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/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/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/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/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/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/strings.xml b/packages/SystemUI/res/values/strings.xml
index ebf0f8e..2aa912c 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -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/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/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/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/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 616bd81..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
@@ -101,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/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index c0e1133..b08822d9 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -214,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
@@ -236,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
 
@@ -522,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
@@ -544,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
@@ -661,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
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/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 00e5aac..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);
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/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 779f1d8..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,7 +135,7 @@
         // 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 -> {
                     bubbles.showOrHideAppBubble(intent, userTracker.userHandle)
@@ -134,7 +143,7 @@
                     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/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/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 30865aa..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);
         }
@@ -3862,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 a716a6e..5f6f158 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -16,7 +16,7 @@
 
 package com.android.systemui.shade;
 
-import static com.android.systemui.flags.Flags.TRACKPAD_GESTURE_BACK;
+import static com.android.systemui.flags.Flags.TRACKPAD_GESTURE_COMMON;
 import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
 
 import android.app.StatusBarManager;
@@ -37,14 +37,12 @@
 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;
@@ -94,9 +92,7 @@
     private final AmbientState mAmbientState;
     private final PulsingGestureListener mPulsingGestureListener;
     private final NotificationInsetsController mNotificationInsetsController;
-    private final AlternateBouncerInteractor mAlternateBouncerInteractor;
-    private final UdfpsOverlayInteractor mUdfpsOverlayInteractor;
-    private final boolean mIsTrackpadGestureBackEnabled;
+    private final boolean mIsTrackpadCommonEnabled;
     private GestureDetector mPulsingWakeupGestureHandler;
     private View mBrightnessMirror;
     private boolean mTouchActive;
@@ -145,8 +141,6 @@
             PulsingGestureListener pulsingGestureListener,
             KeyguardBouncerViewModel keyguardBouncerViewModel,
             KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory,
-            AlternateBouncerInteractor alternateBouncerInteractor,
-            UdfpsOverlayInteractor udfpsOverlayInteractor,
             KeyguardTransitionInteractor keyguardTransitionInteractor,
             PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
             FeatureFlags featureFlags,
@@ -170,9 +164,7 @@
         mAmbientState = ambientState;
         mPulsingGestureListener = pulsingGestureListener;
         mNotificationInsetsController = notificationInsetsController;
-        mAlternateBouncerInteractor = alternateBouncerInteractor;
-        mUdfpsOverlayInteractor = udfpsOverlayInteractor;
-        mIsTrackpadGestureBackEnabled = featureFlags.isEnabled(TRACKPAD_GESTURE_BACK);
+        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);
@@ -269,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
@@ -343,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)) {
@@ -381,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)
@@ -474,7 +468,7 @@
         if (mTouchActive) {
             final long now = mClock.uptimeMillis();
             final MotionEvent event;
-            if (mIsTrackpadGestureBackEnabled) {
+            if (mIsTrackpadCommonEnabled) {
                 event = MotionEvent.obtain(mDownEvent);
                 event.setDownTime(now);
                 event.setAction(MotionEvent.ACTION_CANCEL);
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/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 5834dcb..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() {
@@ -2193,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/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/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/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/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/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/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 40c733a..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(
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 bdb0e7e..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,13 +26,11 @@
 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
@@ -69,8 +67,8 @@
 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
@@ -97,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
@@ -127,7 +123,8 @@
             .thenReturn(emptyFlow<TransitionStep>())
 
         val featureFlags = FakeFeatureFlags()
-        featureFlags.set(Flags.TRACKPAD_GESTURE_BACK, false)
+        featureFlags.set(Flags.TRACKPAD_GESTURE_COMMON, true)
+        featureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, false)
         featureFlags.set(Flags.DUAL_SHADE, false)
 
         val inputProxy = MultiShadeInputProxy()
@@ -154,8 +151,6 @@
                 pulsingGestureListener,
                 keyguardBouncerViewModel,
                 keyguardBouncerComponentFactory,
-                alternateBouncerInteractor,
-                udfpsOverlayInteractor,
                 keyguardTransitionInteractor,
                 primaryBouncerToGoneTransitionViewModel,
                 featureFlags,
@@ -310,17 +305,15 @@
         }
 
     @Test
-    fun shouldInterceptTouchEvent_downEventAlternateBouncer_ignoreIfInUdfpsOverlay() =
-        testScope.runTest {
-            // Down event within udfpsOverlay bounds while alternateBouncer is showing
-            whenever(udfpsOverlayInteractor.canInterceptTouchInUdfpsBounds(DOWN_EVENT))
-                .thenReturn(false)
-            whenever(alternateBouncerInteractor.isVisibleState()).thenReturn(true)
+    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(DOWN_EVENT)
-            assertThat(shouldIntercept).isFalse()
-        }
+        // Then touch should not be intercepted
+        val shouldIntercept = interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)
+        assertThat(shouldIntercept).isTrue()
+    }
 
     @Test
     fun testGetBouncerContainer() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index 5d0f408..b4b5ec1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -26,13 +26,11 @@
 import com.android.keyguard.dagger.KeyguardBouncerComponent
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
 import com.android.systemui.classifier.FalsingCollectorFake
 import com.android.systemui.dock.DockManager
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
@@ -103,14 +101,12 @@
     @Mock
     private lateinit var keyguardSecurityContainerController: KeyguardSecurityContainerController
     @Mock private lateinit var notificationInsetsController: NotificationInsetsController
-    @Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
     @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
     @Mock
     private lateinit var primaryBouncerToGoneTransitionViewModel:
         PrimaryBouncerToGoneTransitionViewModel
     @Captor
     private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler>
-    @Mock private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor
 
     private lateinit var underTest: NotificationShadeWindowView
     private lateinit var controller: NotificationShadeWindowViewController
@@ -139,7 +135,8 @@
             .thenReturn(emptyFlow())
 
         val featureFlags = FakeFeatureFlags()
-        featureFlags.set(Flags.TRACKPAD_GESTURE_BACK, false)
+        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()
@@ -165,8 +162,6 @@
                 pulsingGestureListener,
                 keyguardBouncerViewModel,
                 keyguardBouncerComponentFactory,
-                alternateBouncerInteractor,
-                udfpsOverlayInteractor,
                 keyguardTransitionInteractor,
                 primaryBouncerToGoneTransitionViewModel,
                 featureFlags,
@@ -206,8 +201,7 @@
 
             // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept
             whenever(statusBarStateController.isDozing).thenReturn(false)
-            whenever(alternateBouncerInteractor.isVisibleState()).thenReturn(true)
-            whenever(udfpsOverlayInteractor.canInterceptTouchInUdfpsBounds(any())).thenReturn(true)
+            whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(any())).thenReturn(true)
             whenever(dragDownHelper.onInterceptTouchEvent(any())).thenReturn(false)
 
             // THEN we should intercept touch
@@ -221,7 +215,8 @@
 
             // WHEN not showing alt auth, not dozing, drag down helper doesn't want to intercept
             whenever(statusBarStateController.isDozing).thenReturn(false)
-            whenever(alternateBouncerInteractor.isVisibleState()).thenReturn(false)
+            whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(any()))
+                .thenReturn(false)
             whenever(dragDownHelper.onInterceptTouchEvent(any())).thenReturn(false)
 
             // THEN we shouldn't intercept touch
@@ -235,7 +230,7 @@
 
             // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept
             whenever(statusBarStateController.isDozing).thenReturn(false)
-            whenever(alternateBouncerInteractor.isVisibleState()).thenReturn(true)
+            whenever(statusBarKeyguardViewManager.onTouch(any())).thenReturn(true)
             whenever(dragDownHelper.onInterceptTouchEvent(any())).thenReturn(false)
 
             // THEN we should handle the touch
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 7db2197..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
@@ -1342,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);
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/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/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java
index a3dc21e..55069b7 100644
--- a/services/core/java/com/android/server/DropBoxManagerService.java
+++ b/services/core/java/com/android/server/DropBoxManagerService.java
@@ -311,14 +311,10 @@
             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)
                     .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
                     .toBundle();
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 7550196..b4e75e1 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -9351,7 +9351,9 @@
 
                 String logcatSetting = Settings.Global.ERROR_LOGCAT_PREFIX + dropboxTag;
                 String maxBytesSetting = Settings.Global.MAX_ERROR_BYTES_PREFIX + dropboxTag;
-                int lines = Settings.Global.getInt(mContext.getContentResolver(), logcatSetting, 0);
+                int lines = Build.IS_USER
+                        ? 0
+                        : Settings.Global.getInt(mContext.getContentResolver(), logcatSetting, 0);
                 int dropboxMaxSize = Settings.Global.getInt(
                         mContext.getContentResolver(), maxBytesSetting, DROPBOX_DEFAULT_MAX_SIZE);
                 int maxDataFileSize = dropboxMaxSize - sb.length()
diff --git a/services/core/java/com/android/server/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/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/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/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 3e86c45..127a9d8b 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1745,7 +1745,9 @@
         mSoundDoseHelper.reset();
 
         // Restore rotation information.
-        RotationHelper.forceUpdate();
+        if (mMonitorRotation) {
+            RotationHelper.forceUpdate();
+        }
 
         onIndicateSystemReady();
         // indicate the end of reconfiguration phase to audio HAL
@@ -7235,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:"
@@ -10471,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/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index c248367..d6a9d3a 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -300,7 +300,7 @@
                 SAFE_MEDIA_VOLUME_UNINITIALIZED);
     }
 
-    float getRs2Value() {
+    float getOutputRs2UpperBound() {
         if (!mEnableCsd) {
             return 0.f;
         }
@@ -312,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;
         }
@@ -331,7 +331,7 @@
         }
 
         try {
-            soundDose.setOutputRs2(rs2Value);
+            soundDose.setOutputRs2UpperBound(rs2Value);
         } catch (RemoteException e) {
             Log.e(TAG, "Exception while setting the RS2 exposure value", e);
         }
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
index 82444f0..6bd4880 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
@@ -16,14 +16,22 @@
 
 package com.android.server.biometrics.log;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.IBiometricContextListener;
+import android.hardware.biometrics.common.AuthenticateReason;
+import android.hardware.biometrics.common.OperationContext;
 import android.hardware.biometrics.common.OperationReason;
+import android.hardware.biometrics.common.WakeReason;
 import android.util.Slog;
 import android.view.Surface;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FrameworkStatsLog;
 
+import java.util.stream.Stream;
+
 /**
  * Wrapper for {@link FrameworkStatsLog} to isolate the testable parts.
  */
@@ -63,7 +71,7 @@
                 orientationType(operationContext.getOrientation()),
                 foldType(operationContext.getFoldState()),
                 operationContext.getOrderAndIncrement(),
-                BiometricsProtoEnums.WAKE_REASON_UNKNOWN);
+                toProtoWakeReason(operationContext));
     }
 
     /** {@see FrameworkStatsLog.BIOMETRIC_AUTHENTICATED}. */
@@ -89,7 +97,8 @@
                 orientationType(operationContext.getOrientation()),
                 foldType(operationContext.getFoldState()),
                 operationContext.getOrderAndIncrement(),
-                BiometricsProtoEnums.WAKE_REASON_UNKNOWN);
+                toProtoWakeReason(operationContext),
+                toProtoWakeReasonDetails(operationContext));
     }
 
     /** {@see FrameworkStatsLog.BIOMETRIC_AUTHENTICATED}. */
@@ -137,7 +146,81 @@
                 orientationType(operationContext.getOrientation()),
                 foldType(operationContext.getFoldState()),
                 operationContext.getOrderAndIncrement(),
-                BiometricsProtoEnums.WAKE_REASON_UNKNOWN);
+                toProtoWakeReason(operationContext),
+                toProtoWakeReasonDetails(operationContext));
+    }
+
+    @VisibleForTesting
+    static int[] toProtoWakeReasonDetails(@NonNull OperationContextExt operationContext) {
+        final OperationContext ctx = operationContext.toAidlContext();
+        return Stream.of(toProtoWakeReasonDetails(ctx.authenticateReason))
+                .mapToInt(i -> i)
+                .filter(i -> i != BiometricsProtoEnums.DETAILS_UNKNOWN)
+                .toArray();
+    }
+
+    @VisibleForTesting
+    static int toProtoWakeReason(@NonNull OperationContextExt operationContext) {
+        @WakeReason final int reason = operationContext.getWakeReason();
+        switch (reason) {
+            case WakeReason.POWER_BUTTON:
+                return BiometricsProtoEnums.WAKE_REASON_POWER_BUTTON;
+            case WakeReason.GESTURE:
+                return BiometricsProtoEnums.WAKE_REASON_GESTURE;
+            case WakeReason.WAKE_KEY:
+                return BiometricsProtoEnums.WAKE_REASON_WAKE_KEY;
+            case WakeReason.WAKE_MOTION:
+                return BiometricsProtoEnums.WAKE_REASON_WAKE_MOTION;
+            case WakeReason.LID:
+                return BiometricsProtoEnums.WAKE_REASON_LID;
+            case WakeReason.DISPLAY_GROUP_ADDED:
+                return BiometricsProtoEnums.WAKE_REASON_DISPLAY_GROUP_ADDED;
+            case WakeReason.TAP:
+                return BiometricsProtoEnums.WAKE_REASON_TAP;
+            case WakeReason.LIFT:
+                return BiometricsProtoEnums.WAKE_REASON_LIFT;
+            case WakeReason.BIOMETRIC:
+                return BiometricsProtoEnums.WAKE_REASON_BIOMETRIC;
+            default:
+                return BiometricsProtoEnums.WAKE_REASON_UNKNOWN;
+        }
+    }
+
+    private static int toProtoWakeReasonDetails(@Nullable AuthenticateReason reason) {
+        if (reason != null) {
+            switch (reason.getTag()) {
+                case AuthenticateReason.faceAuthenticateReason:
+                    return toProtoWakeReasonDetailsFromFace(reason.getFaceAuthenticateReason());
+            }
+        }
+        return BiometricsProtoEnums.DETAILS_UNKNOWN;
+    }
+
+    private static int toProtoWakeReasonDetailsFromFace(@AuthenticateReason.Face int reason) {
+        switch (reason) {
+            case AuthenticateReason.Face.STARTED_WAKING_UP:
+                return BiometricsProtoEnums.DETAILS_FACE_STARTED_WAKING_UP;
+            case AuthenticateReason.Face.PRIMARY_BOUNCER_SHOWN:
+                return BiometricsProtoEnums.DETAILS_FACE_PRIMARY_BOUNCER_SHOWN;
+            case AuthenticateReason.Face.ASSISTANT_VISIBLE:
+                return BiometricsProtoEnums.DETAILS_FACE_ASSISTANT_VISIBLE;
+            case AuthenticateReason.Face.ALTERNATE_BIOMETRIC_BOUNCER_SHOWN:
+                return BiometricsProtoEnums.DETAILS_FACE_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN;
+            case AuthenticateReason.Face.NOTIFICATION_PANEL_CLICKED:
+                return BiometricsProtoEnums.DETAILS_FACE_NOTIFICATION_PANEL_CLICKED;
+            case AuthenticateReason.Face.OCCLUDING_APP_REQUESTED:
+                return BiometricsProtoEnums.DETAILS_FACE_OCCLUDING_APP_REQUESTED;
+            case AuthenticateReason.Face.PICK_UP_GESTURE_TRIGGERED:
+                return BiometricsProtoEnums.DETAILS_FACE_PICK_UP_GESTURE_TRIGGERED;
+            case AuthenticateReason.Face.QS_EXPANDED:
+                return BiometricsProtoEnums.DETAILS_FACE_QS_EXPANDED;
+            case AuthenticateReason.Face.SWIPE_UP_ON_BOUNCER:
+                return BiometricsProtoEnums.DETAILS_FACE_SWIPE_UP_ON_BOUNCER;
+            case AuthenticateReason.Face.UDFPS_POINTER_DOWN:
+                return BiometricsProtoEnums.DETAILS_FACE_UDFPS_POINTER_DOWN;
+            default:
+                return BiometricsProtoEnums.DETAILS_UNKNOWN;
+        }
     }
 
     /** {@see FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED}. */
diff --git a/services/core/java/com/android/server/biometrics/log/OperationContextExt.java b/services/core/java/com/android/server/biometrics/log/OperationContextExt.java
index ecb7e7c..2934339 100644
--- a/services/core/java/com/android/server/biometrics/log/OperationContextExt.java
+++ b/services/core/java/com/android/server/biometrics/log/OperationContextExt.java
@@ -188,10 +188,17 @@
     }
 
     /** {@link OperationContext#reason}. */
+    @OperationReason
     public byte getReason() {
         return mAidlContext.reason;
     }
 
+    /** {@link OperationContext#wakeReason}. */
+    @WakeReason
+    public int getWakeReason() {
+        return mAidlContext.wakeReason;
+    }
+
     /** If the screen is currently on. */
     public boolean isDisplayOn() {
         return mIsDisplayOn;
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/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/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/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 46337a9..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());
         }
     }
 
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/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/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/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/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index fdc2aff..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.",
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/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/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/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 5db39fc..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;
         }
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index b1ca72b..78ee6f9 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -857,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
@@ -869,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);
@@ -878,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 6882e4c..8c6de8e 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -2244,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;
         }
 
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 370d304..873a83d 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -278,13 +278,14 @@
         setTransientLaunchToChanges(activity);
 
         if (restoreBelow != null) {
+            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()) {
+                    if (t.isRootTask() && t != transientRootTask) {
                         mTransientHideTasks.add(t);
                     }
                     if (t.isLeafTask()) {
@@ -828,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.
@@ -1044,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();
@@ -1107,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();
@@ -1124,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);
@@ -1137,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) {
@@ -1246,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)) {
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 86bb6b5..c74f167 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -976,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/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 8582356..f7641f5 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -237,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;
@@ -594,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;
 
@@ -4111,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.
      *
@@ -8571,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;
@@ -8589,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);
@@ -8630,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))
@@ -8646,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) {
@@ -8683,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;
@@ -8698,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/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/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 720533f..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;
@@ -3579,6 +3580,8 @@
         mInjector.binderWithCleanCallingIdentity(() ->
                 mInjector.getPackageManagerInternal().setOwnerProtectedPackages(
                         targetUserId, protectedPackages));
+        mUsageStatsManagerInternal.setAdminProtectedPackages(new ArraySet(protectedPackages),
+                targetUserId);
     }
 
     void handleUnlockUser(int userId) {
@@ -3965,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(() -> {
@@ -9479,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();
     }
 
@@ -9648,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()) {
@@ -9698,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()) {
@@ -10102,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)
@@ -10120,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();
@@ -10433,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);
     }
 
@@ -10640,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),
@@ -10664,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,
@@ -11777,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
@@ -11803,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);
                 }
             }
 
@@ -15921,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
@@ -16424,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 {
@@ -17157,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.");
         }
@@ -17758,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;
@@ -18696,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));
@@ -19513,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 {
@@ -19540,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());
     }
@@ -20520,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);
@@ -20542,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);
 
@@ -20640,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");
@@ -20742,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 =
-                getRoleHolderPackageName(mContext, RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT);
+        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);
@@ -20977,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()));
 
@@ -21165,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 {
@@ -21304,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);
@@ -21692,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();
 
@@ -21704,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;
 
@@ -21882,7 +21936,9 @@
                     }
                     if (!isProfileOwnerOfOrganizationOwnedDevice(
                             admin.info.getComponent(), user.getIdentifier())
-                            && !isDeviceOwner(admin)) {
+                            && !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
@@ -21995,7 +22051,7 @@
     @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);
@@ -22657,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);
     }
@@ -22924,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()) {
@@ -23290,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/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/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/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 e056417..ba70c584 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -262,9 +262,10 @@
         ConnectivityController connectivityController = mService.getConnectivityController();
         spyOn(connectivityController);
         mService.mConstants.RUNTIME_MIN_GUARANTEE_MS = 10 * MINUTE_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_DATA_TRANSFER_LIMIT_MS = 6 * HOUR_IN_MILLIS;
+        mService.mConstants.RUNTIME_UI_LIMIT_MS = 6 * HOUR_IN_MILLIS;
 
         assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
                 mService.getMinJobExecutionGuaranteeMs(ejMax));
@@ -283,9 +284,9 @@
         // 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_UI_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_UI_DATA_TRANSFER_GUARANTEE_MS,
@@ -294,16 +295,20 @@
                 .when(connectivityController).getEstimatedTransferTimeMs(any());
         assertEquals(mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS,
                 mService.getMinJobExecutionGuaranteeMs(jobUIDT));
-        doReturn(mService.mConstants.RUNTIME_MIN_UI_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_UI_DATA_TRANSFER_GUARANTEE_MS
-                        * 2 * mService.mConstants
-                                .RUNTIME_MIN_UI_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_UI_DATA_TRANSFER_LIMIT_MS * 2)
+        doReturn(mService.mConstants.RUNTIME_UI_LIMIT_MS * 2)
                 .when(connectivityController).getEstimatedTransferTimeMs(any());
-        assertEquals(mService.mConstants.RUNTIME_UI_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));
     }
 
@@ -325,7 +330,7 @@
                 .when(quotaController).getMaxJobExecutionTimeMsLocked(any());
 
         grantRunUserInitiatedJobsPermission(true);
-        assertEquals(mService.mConstants.RUNTIME_UI_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,
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/servicestests/src/com/android/server/biometrics/log/BiometricFrameworkStatsLoggerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricFrameworkStatsLoggerTest.java
new file mode 100644
index 0000000..5adf391
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricFrameworkStatsLoggerTest.java
@@ -0,0 +1,125 @@
+/*
+ * 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.biometrics.log;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.common.AuthenticateReason;
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.common.WakeReason;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+@Presubmit
+@SmallTest
+public class BiometricFrameworkStatsLoggerTest {
+
+    @Test
+    public void testConvertsWakeReason_whenEmpty() {
+        final OperationContextExt ctx = new OperationContextExt();
+
+        final int reason = BiometricFrameworkStatsLogger.toProtoWakeReason(ctx);
+        final int[] reasonDetails = BiometricFrameworkStatsLogger
+                .toProtoWakeReasonDetails(ctx);
+
+        assertThat(reason).isEqualTo(BiometricsProtoEnums.WAKE_REASON_UNKNOWN);
+        assertThat(reasonDetails).isEmpty();
+    }
+
+    @Test
+    public void testConvertsWakeReason_whenPowerReason() {
+        final OperationContext context = new OperationContext();
+        context.wakeReason = WakeReason.WAKE_MOTION;
+        final OperationContextExt ctx = new OperationContextExt(context);
+
+        final int reason = BiometricFrameworkStatsLogger.toProtoWakeReason(ctx);
+        final int[] reasonDetails = BiometricFrameworkStatsLogger
+                .toProtoWakeReasonDetails(new OperationContextExt(context));
+
+        assertThat(reason).isEqualTo(BiometricsProtoEnums.WAKE_REASON_WAKE_MOTION);
+        assertThat(reasonDetails).isEmpty();
+    }
+
+    @Test
+    public void testConvertsWakeReason_whenFaceReason() {
+        final OperationContext context = new OperationContext();
+        context.authenticateReason = AuthenticateReason.faceAuthenticateReason(
+                AuthenticateReason.Face.ASSISTANT_VISIBLE);
+        final OperationContextExt ctx = new OperationContextExt(context);
+
+        final int reason = BiometricFrameworkStatsLogger.toProtoWakeReason(ctx);
+        final int[] reasonDetails = BiometricFrameworkStatsLogger
+                .toProtoWakeReasonDetails(ctx);
+
+        assertThat(reason).isEqualTo(BiometricsProtoEnums.WAKE_REASON_UNKNOWN);
+        assertThat(reasonDetails).asList().containsExactly(
+                BiometricsProtoEnums.DETAILS_FACE_ASSISTANT_VISIBLE);
+    }
+
+    @Test
+    public void testConvertsWakeReason_whenVendorReason() {
+        final OperationContext context = new OperationContext();
+        context.authenticateReason = AuthenticateReason.vendorAuthenticateReason(
+                new AuthenticateReason.Vendor());
+        final OperationContextExt ctx = new OperationContextExt(context);
+
+        final int reason = BiometricFrameworkStatsLogger.toProtoWakeReason(ctx);
+        final int[] reasonDetails = BiometricFrameworkStatsLogger
+                .toProtoWakeReasonDetails(ctx);
+
+        assertThat(reason).isEqualTo(BiometricsProtoEnums.WAKE_REASON_UNKNOWN);
+        assertThat(reasonDetails).isEmpty();
+    }
+
+
+    @Test
+    public void testConvertsWakeReason_whenPowerAndFaceReason() {
+        final OperationContext context = new OperationContext();
+        context.wakeReason = WakeReason.WAKE_KEY;
+        context.authenticateReason = AuthenticateReason.faceAuthenticateReason(
+                AuthenticateReason.Face.PRIMARY_BOUNCER_SHOWN);
+        final OperationContextExt ctx = new OperationContextExt(context);
+
+        final int reason = BiometricFrameworkStatsLogger.toProtoWakeReason(ctx);
+        final int[] reasonDetails = BiometricFrameworkStatsLogger
+                .toProtoWakeReasonDetails(ctx);
+
+        assertThat(reason).isEqualTo(BiometricsProtoEnums.WAKE_REASON_WAKE_KEY);
+        assertThat(reasonDetails).asList().containsExactly(
+                BiometricsProtoEnums.DETAILS_FACE_PRIMARY_BOUNCER_SHOWN);
+    }
+
+    @Test
+    public void testConvertsWakeReason_whenPowerAndVendorReason() {
+        final OperationContext context = new OperationContext();
+        context.wakeReason = WakeReason.LID;
+        context.authenticateReason = AuthenticateReason.vendorAuthenticateReason(
+                new AuthenticateReason.Vendor());
+        final OperationContextExt ctx = new OperationContextExt(context);
+
+        final int reason = BiometricFrameworkStatsLogger.toProtoWakeReason(ctx);
+        final int[] reasonDetails = BiometricFrameworkStatsLogger
+                .toProtoWakeReasonDetails(ctx);
+
+        assertThat(reason).isEqualTo(BiometricsProtoEnums.WAKE_REASON_LID);
+        assertThat(reasonDetails).isEmpty();
+    }
+}
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/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/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/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/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 da078a2..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;
@@ -526,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/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/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/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/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 212dc41..7aa1334 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -8654,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";
@@ -8854,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 {}
 
@@ -8988,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);
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/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 63e471b..30bb989 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java
@@ -51,7 +51,7 @@
         private boolean mInstantTetherEnabled;
         private Intent mInstantTetherSettingsIntent;
         private final Context mContext;
-        private Bundle mExtras;
+        private Bundle mExtras = Bundle.EMPTY;
 
         public Builder(@NonNull Context context) {
             mContext = context;
@@ -112,8 +112,7 @@
     }
 
     private SharedConnectivitySettingsState(boolean instantTetherEnabled,
-            PendingIntent pendingIntent, Bundle extras) {
-
+            PendingIntent pendingIntent, @NonNull Bundle extras) {
         mInstantTetherEnabled = instantTetherEnabled;
         mInstantTetherSettingsPendingIntent = pendingIntent;
         mExtras = extras;
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;
     }
 }