Merge "[Dual Shade] Introduce a shade header that is closer to the UX mocks." into main
diff --git a/android-sdk-flags/OWNERS b/android-sdk-flags/OWNERS
new file mode 100644
index 0000000..01f45dd
--- /dev/null
+++ b/android-sdk-flags/OWNERS
@@ -0,0 +1 @@
+include /SDK_OWNERS
diff --git a/android-sdk-flags/flags.aconfig b/android-sdk-flags/flags.aconfig
index cfe298e..19c7bf6 100644
--- a/android-sdk-flags/flags.aconfig
+++ b/android-sdk-flags/flags.aconfig
@@ -6,6 +6,7 @@
     namespace: "android_sdk"
     description: "Use the new SDK major.minor versioning scheme (e.g. Android 40.1) which replaces the old single-integer scheme (e.g. Android 15)."
     bug: "350458259"
+    is_exported: true
 
     # Use is_fixed_read_only because DeviceConfig may not be available when Build.VERSION_CODES is first accessed
     is_fixed_read_only: true
diff --git a/apct-tests/perftests/core/src/android/os/LongArrayMultiStateCounterPerfTest.java b/apct-tests/perftests/core/src/android/os/LongArrayMultiStateCounterPerfTest.java
index 8d2d044..8160ca2 100644
--- a/apct-tests/perftests/core/src/android/os/LongArrayMultiStateCounterPerfTest.java
+++ b/apct-tests/perftests/core/src/android/os/LongArrayMultiStateCounterPerfTest.java
@@ -159,9 +159,7 @@
         LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 4);
         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         long time = 1000;
-        LongArrayMultiStateCounter.LongArrayContainer timeInFreq =
-                new LongArrayMultiStateCounter.LongArrayContainer(4);
-        timeInFreq.setValues(new long[]{100, 200, 300, 400});
+        long[] timeInFreq = {100, 200, 300, 400};
         while (state.keepRunning()) {
             counter.setState(1, time);
             counter.setState(0, time + 1000);
diff --git a/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java b/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java
index 3cfddc6..fb5ef87 100644
--- a/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java
+++ b/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java
@@ -173,6 +173,16 @@
     }
 
     @Override
+    @NonNull
+    public int[] getPendingJobReasons(int jobId) {
+        try {
+            return mBinder.getPendingJobReasons(mNamespace, jobId);
+        } catch (RemoteException e) {
+            return new int[] { PENDING_JOB_REASON_UNDEFINED };
+        }
+    }
+
+    @Override
     public boolean canRunUserInitiatedJobs() {
         try {
             return mBinder.canRunUserInitiatedJobs(mContext.getOpPackageName());
diff --git a/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl b/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl
index 416a2d8..21051b5 100644
--- a/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl
+++ b/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl
@@ -39,6 +39,7 @@
     ParceledListSlice<JobInfo> getAllPendingJobsInNamespace(String namespace);
     JobInfo getPendingJob(String namespace, int jobId);
     int getPendingJobReason(String namespace, int jobId);
+    int[] getPendingJobReasons(String namespace, int jobId);
     boolean canRunUserInitiatedJobs(String packageName);
     boolean hasRunUserInitiatedJobsPermission(String packageName, int userId);
     List<JobInfo> getStartedJobs();
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
index ad54cd39..bfdd15e 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
@@ -16,6 +16,7 @@
 
 package android.app.job;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -238,6 +239,13 @@
      * to defer this job.
      */
     public static final int PENDING_JOB_REASON_USER = 15;
+    /**
+     * The override deadline has not transpired.
+     *
+     * @see JobInfo.Builder#setOverrideDeadline(long)
+     */
+    @FlaggedApi(Flags.FLAG_GET_PENDING_JOB_REASONS_API)
+    public static final int PENDING_JOB_REASON_CONSTRAINT_DEADLINE = 16;
 
     /** @hide */
     @IntDef(prefix = {"PENDING_JOB_REASON_"}, value = {
@@ -259,6 +267,7 @@
             PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION,
             PENDING_JOB_REASON_QUOTA,
             PENDING_JOB_REASON_USER,
+            PENDING_JOB_REASON_CONSTRAINT_DEADLINE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface PendingJobReason {
@@ -458,6 +467,10 @@
     /**
      * Returns a reason why the job is pending and not currently executing. If there are multiple
      * reasons why a job may be pending, this will only return one of them.
+     *
+     * @apiNote
+     * To know all the potential reasons why the job may be pending,
+     * use {@link #getPendingJobReasons(int)} instead.
      */
     @PendingJobReason
     public int getPendingJobReason(int jobId) {
@@ -465,6 +478,21 @@
     }
 
     /**
+     * Returns potential reasons why the job with the given {@code jobId} may be pending
+     * and not currently executing.
+     *
+     * The returned array will include {@link PendingJobReason reasons} composed of both
+     * explicitly set constraints on the job and implicit constraints imposed by the system.
+     * The results can be used to debug why a given job may not be currently executing.
+     */
+    @FlaggedApi(Flags.FLAG_GET_PENDING_JOB_REASONS_API)
+    @NonNull
+    @PendingJobReason
+    public int[] getPendingJobReasons(int jobId) {
+        return new int[] { PENDING_JOB_REASON_UNDEFINED };
+    }
+
+    /**
      * Returns {@code true} if the calling app currently holds the
      * {@link android.Manifest.permission#RUN_USER_INITIATED_JOBS} permission, allowing it to run
      * user-initiated jobs.
diff --git a/apex/jobscheduler/service/aconfig/device_idle.aconfig b/apex/jobscheduler/service/aconfig/device_idle.aconfig
index c4d0d18..426031f 100644
--- a/apex/jobscheduler/service/aconfig/device_idle.aconfig
+++ b/apex/jobscheduler/service/aconfig/device_idle.aconfig
@@ -17,3 +17,13 @@
     description: "Disable wakelocks for background apps while Light Device Idle is active"
     bug: "326607666"
 }
+
+flag {
+    name: "use_cpu_time_for_temp_allowlist"
+    namespace: "backstage_power"
+    description: "Use CPU time for temporary allowlists"
+    bug: "376561328"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 3e650da..41fd4a2 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -620,8 +620,8 @@
      * the network and acquire wakelocks. Times are in milliseconds.
      */
     @GuardedBy("this")
-    private final SparseArray<Pair<MutableLong, String>> mTempWhitelistAppIdEndTimes
-            = new SparseArray<>();
+    @VisibleForTesting
+    final SparseArray<Pair<MutableLong, String>> mTempWhitelistAppIdEndTimes = new SparseArray<>();
 
     private NetworkPolicyManagerInternal mNetworkPolicyManagerInternal;
 
@@ -1941,7 +1941,8 @@
     private static final int MSG_REPORT_IDLE_ON_LIGHT = 3;
     private static final int MSG_REPORT_IDLE_OFF = 4;
     private static final int MSG_REPORT_ACTIVE = 5;
-    private static final int MSG_TEMP_APP_WHITELIST_TIMEOUT = 6;
+    @VisibleForTesting
+    static final int MSG_TEMP_APP_WHITELIST_TIMEOUT = 6;
     @VisibleForTesting
     static final int MSG_REPORT_STATIONARY_STATUS = 7;
     private static final int MSG_FINISH_IDLE_OP = 8;
@@ -2511,6 +2512,11 @@
             return SystemClock.elapsedRealtime();
         }
 
+        /** Returns the current elapsed realtime in milliseconds. */
+        long getUptimeMillis() {
+            return SystemClock.uptimeMillis();
+        }
+
         LocationManager getLocationManager() {
             if (mLocationManager == null) {
                 mLocationManager = mContext.getSystemService(LocationManager.class);
@@ -3264,7 +3270,8 @@
     void addPowerSaveTempWhitelistAppDirectInternal(int callingUid, int uid,
             long duration, @TempAllowListType int tempAllowListType, boolean sync,
             @ReasonCode int reasonCode, @Nullable String reason) {
-        final long timeNow = SystemClock.elapsedRealtime();
+        final long timeNow = Flags.useCpuTimeForTempAllowlist() ? mInjector.getUptimeMillis()
+                : mInjector.getElapsedRealtime();
         boolean informWhitelistChanged = false;
         int appId = UserHandle.getAppId(uid);
         synchronized (this) {
@@ -3350,7 +3357,8 @@
     }
 
     void checkTempAppWhitelistTimeout(int uid) {
-        final long timeNow = SystemClock.elapsedRealtime();
+        final long timeNow = Flags.useCpuTimeForTempAllowlist() ? mInjector.getUptimeMillis()
+                : mInjector.getElapsedRealtime();
         final int appId = UserHandle.getAppId(uid);
         if (DEBUG) {
             Slog.d(TAG, "checkTempAppWhitelistTimeout: uid=" + uid + ", timeNow=" + timeNow);
@@ -5219,6 +5227,17 @@
             }
         }
 
+        pw.println("  Flags:");
+        pw.print("    ");
+        pw.print(Flags.FLAG_USE_CPU_TIME_FOR_TEMP_ALLOWLIST);
+        pw.print("=");
+        pw.println(Flags.useCpuTimeForTempAllowlist());
+        pw.print("    ");
+        pw.print(Flags.FLAG_REMOVE_IDLE_LOCATION);
+        pw.print("=");
+        pw.println(Flags.removeIdleLocation());
+        pw.println();
+
         synchronized (this) {
             mConstants.dump(pw);
 
@@ -5449,7 +5468,8 @@
                 pw.println("  Temp whitelist schedule:");
                 prefix = "    ";
             }
-            final long timeNow = SystemClock.elapsedRealtime();
+            final long timeNow = Flags.useCpuTimeForTempAllowlist() ? mInjector.getUptimeMillis()
+                    : mInjector.getElapsedRealtime();
             for (int i = 0; i < size; i++) {
                 pw.print(prefix);
                 pw.print("UID=");
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index ba8e3e8..8f44698 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -1550,7 +1550,7 @@
                 mActivePkgStats.add(
                         jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(),
                         packageStats);
-                mService.resetPendingJobReasonCache(jobStatus);
+                mService.resetPendingJobReasonsCache(jobStatus);
             }
             if (mService.getPendingJobQueue().remove(jobStatus)) {
                 mService.mJobPackageTracker.noteNonpending(jobStatus);
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 4c1951a..f569388 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -460,10 +460,10 @@
     private final ArraySet<JobStatus> mChangedJobList = new ArraySet<>();
 
     /**
-     * Cached pending job reasons. Mapping from UID -> namespace -> job ID -> reason.
+     * Cached pending job reasons. Mapping from UID -> namespace -> job ID -> reasons.
      */
-    @GuardedBy("mPendingJobReasonCache") // Use its own lock to avoid blocking JS processing
-    private final SparseArrayMap<String, SparseIntArray> mPendingJobReasonCache =
+    @GuardedBy("mPendingJobReasonsCache") // Use its own lock to avoid blocking JS processing
+    private final SparseArrayMap<String, SparseArray<int[]>> mPendingJobReasonsCache =
             new SparseArrayMap<>();
 
     /**
@@ -2021,139 +2021,123 @@
         }
     }
 
-    @JobScheduler.PendingJobReason
-    private int getPendingJobReason(int uid, String namespace, int jobId) {
-        int reason;
+    @NonNull
+    private int[] getPendingJobReasons(int uid, String namespace, int jobId) {
+        int[] reasons;
         // Some apps may attempt to query this frequently, so cache the reason under a separate lock
         // so that the rest of JS processing isn't negatively impacted.
-        synchronized (mPendingJobReasonCache) {
-            SparseIntArray jobIdToReason = mPendingJobReasonCache.get(uid, namespace);
-            if (jobIdToReason != null) {
-                reason = jobIdToReason.get(jobId, JobScheduler.PENDING_JOB_REASON_UNDEFINED);
-                if (reason != JobScheduler.PENDING_JOB_REASON_UNDEFINED) {
-                    return reason;
+        synchronized (mPendingJobReasonsCache) {
+            SparseArray<int[]> jobIdToReasons = mPendingJobReasonsCache.get(uid, namespace);
+            if (jobIdToReasons != null) {
+                reasons = jobIdToReasons.get(jobId);
+                if (reasons != null) {
+                    return reasons;
                 }
             }
         }
         synchronized (mLock) {
-            reason = getPendingJobReasonLocked(uid, namespace, jobId);
+            reasons = getPendingJobReasonsLocked(uid, namespace, jobId);
             if (DEBUG) {
-                Slog.v(TAG, "getPendingJobReason("
-                        + uid + "," + namespace + "," + jobId + ")=" + reason);
+                Slog.v(TAG, "getPendingJobReasons("
+                        + uid + "," + namespace + "," + jobId + ")=" + Arrays.toString(reasons));
             }
         }
-        synchronized (mPendingJobReasonCache) {
-            SparseIntArray jobIdToReason = mPendingJobReasonCache.get(uid, namespace);
-            if (jobIdToReason == null) {
-                jobIdToReason = new SparseIntArray();
-                mPendingJobReasonCache.add(uid, namespace, jobIdToReason);
+        synchronized (mPendingJobReasonsCache) {
+            SparseArray<int[]> jobIdToReasons = mPendingJobReasonsCache.get(uid, namespace);
+            if (jobIdToReasons == null) {
+                jobIdToReasons = new SparseArray<>();
+                mPendingJobReasonsCache.add(uid, namespace, jobIdToReasons);
             }
-            jobIdToReason.put(jobId, reason);
+            jobIdToReasons.put(jobId, reasons);
         }
-        return reason;
+        return reasons;
     }
 
     @VisibleForTesting
     @JobScheduler.PendingJobReason
     int getPendingJobReason(JobStatus job) {
-        return getPendingJobReason(job.getUid(), job.getNamespace(), job.getJobId());
+        // keep original method to enable unit testing with flags
+        return getPendingJobReasons(job.getUid(), job.getNamespace(), job.getJobId())[0];
     }
 
-    @JobScheduler.PendingJobReason
-    @GuardedBy("mLock")
-    private int getPendingJobReasonLocked(int uid, String namespace, int jobId) {
-        // Very similar code to isReadyToBeExecutedLocked.
+    @VisibleForTesting
+    @NonNull
+    int[] getPendingJobReasons(JobStatus job) {
+        return getPendingJobReasons(job.getUid(), job.getNamespace(), job.getJobId());
+    }
 
-        JobStatus job = mJobs.getJobByUidAndJobId(uid, namespace, jobId);
+    @GuardedBy("mLock")
+    @NonNull
+    private int[] getPendingJobReasonsLocked(int uid, String namespace, int jobId) {
+        // Very similar code to isReadyToBeExecutedLocked.
+        final JobStatus job = mJobs.getJobByUidAndJobId(uid, namespace, jobId);
         if (job == null) {
             // Job doesn't exist.
-            return JobScheduler.PENDING_JOB_REASON_INVALID_JOB_ID;
+            return new int[] { JobScheduler.PENDING_JOB_REASON_INVALID_JOB_ID };
         }
-
         if (isCurrentlyRunningLocked(job)) {
-            return JobScheduler.PENDING_JOB_REASON_EXECUTING;
+            return new int[] { JobScheduler.PENDING_JOB_REASON_EXECUTING };
         }
 
+        final String debugPrefix = "getPendingJobReasonsLocked: " + job.toShortString();
         final boolean jobReady = job.isReady();
-
         if (DEBUG) {
-            Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
-                    + " ready=" + jobReady);
+            Slog.v(TAG, debugPrefix + " ready=" + jobReady);
         }
-
         if (!jobReady) {
-            return job.getPendingJobReason();
+            return job.getPendingJobReasons();
         }
 
         final boolean userStarted = areUsersStartedLocked(job);
-
         if (DEBUG) {
-            Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
-                    + " userStarted=" + userStarted);
+            Slog.v(TAG, debugPrefix + " userStarted=" + userStarted);
         }
         if (!userStarted) {
-            return JobScheduler.PENDING_JOB_REASON_USER;
+            return new int[] { JobScheduler.PENDING_JOB_REASON_USER };
         }
 
         final boolean backingUp = mBackingUpUids.get(job.getSourceUid());
         if (DEBUG) {
-            Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
-                    + " backingUp=" + backingUp);
+            Slog.v(TAG, debugPrefix + " backingUp=" + backingUp);
         }
-
         if (backingUp) {
             // TODO: Should we make a special reason for this?
-            return JobScheduler.PENDING_JOB_REASON_APP;
+            return new int[] { JobScheduler.PENDING_JOB_REASON_APP };
         }
 
-        JobRestriction restriction = checkIfRestricted(job);
+        final JobRestriction restriction = checkIfRestricted(job);
         if (DEBUG) {
-            Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
-                    + " restriction=" + restriction);
+            Slog.v(TAG, debugPrefix + " restriction=" + restriction);
         }
         if (restriction != null) {
-            return restriction.getPendingReason();
+            // Currently this will return _DEVICE_STATE because of thermal reasons.
+            // TODO (b/372031023): does it make sense to move this along with the
+            //  pendingJobReasons() call above and also get the pending reasons from
+            //  all of the restriction controllers?
+            return new int[] { restriction.getPendingReason() };
         }
 
-        // The following can be a little more expensive (especially jobActive, since we need to
-        // go through the array of all potentially active jobs), so we are doing them
-        // later...  but still before checking with the package manager!
+        // The following can be a little more expensive, so we are doing it later,
+        // but still before checking with the package manager!
         final boolean jobPending = mPendingJobQueue.contains(job);
-
-
         if (DEBUG) {
-            Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
-                    + " pending=" + jobPending);
+            Slog.v(TAG, debugPrefix + " pending=" + jobPending);
         }
-
         if (jobPending) {
-            // We haven't started the job for some reason. Presumably, there are too many jobs
-            // running.
-            return JobScheduler.PENDING_JOB_REASON_DEVICE_STATE;
-        }
-
-        final boolean jobActive = mConcurrencyManager.isJobRunningLocked(job);
-
-        if (DEBUG) {
-            Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
-                    + " active=" + jobActive);
-        }
-        if (jobActive) {
-            return JobScheduler.PENDING_JOB_REASON_UNDEFINED;
+            // We haven't started the job - presumably, there are too many jobs running.
+            return new int[] { JobScheduler.PENDING_JOB_REASON_DEVICE_STATE };
         }
 
         // Validate that the defined package+service is still present & viable.
         final boolean componentUsable = isComponentUsable(job);
-
         if (DEBUG) {
-            Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
-                    + " componentUsable=" + componentUsable);
+            Slog.v(TAG, debugPrefix + " componentUsable=" + componentUsable);
         }
         if (!componentUsable) {
-            return JobScheduler.PENDING_JOB_REASON_APP;
+            return new int[] { JobScheduler.PENDING_JOB_REASON_APP };
         }
 
-        return JobScheduler.PENDING_JOB_REASON_UNDEFINED;
+        return new int[] { JobScheduler.PENDING_JOB_REASON_UNDEFINED };
     }
 
     private JobInfo getPendingJob(int uid, @Nullable String namespace, int jobId) {
@@ -2195,15 +2179,16 @@
                 // The app process will be killed soon. There's no point keeping its jobs in
                 // the pending queue to try and start them.
                 if (mPendingJobQueue.remove(job)) {
-                    synchronized (mPendingJobReasonCache) {
-                        SparseIntArray jobIdToReason = mPendingJobReasonCache.get(
+                    synchronized (mPendingJobReasonsCache) {
+                        SparseArray<int[]> jobIdToReason = mPendingJobReasonsCache.get(
                                 job.getUid(), job.getNamespace());
                         if (jobIdToReason == null) {
-                            jobIdToReason = new SparseIntArray();
-                            mPendingJobReasonCache.add(job.getUid(), job.getNamespace(),
+                            jobIdToReason = new SparseArray<>();
+                            mPendingJobReasonsCache.add(job.getUid(), job.getNamespace(),
                                     jobIdToReason);
                         }
-                        jobIdToReason.put(job.getJobId(), JobScheduler.PENDING_JOB_REASON_USER);
+                        jobIdToReason.put(job.getJobId(),
+                                            new int[] { JobScheduler.PENDING_JOB_REASON_USER });
                     }
                 }
             }
@@ -2229,8 +2214,8 @@
         synchronized (mLock) {
             mJobs.removeJobsOfUnlistedUsers(umi.getUserIds());
         }
-        synchronized (mPendingJobReasonCache) {
-            mPendingJobReasonCache.clear();
+        synchronized (mPendingJobReasonsCache) {
+            mPendingJobReasonsCache.clear();
         }
     }
 
@@ -2875,7 +2860,7 @@
         final boolean update = lastJob != null;
         mJobs.add(jobStatus);
         // Clear potentially cached INVALID_JOB_ID reason.
-        resetPendingJobReasonCache(jobStatus);
+        resetPendingJobReasonsCache(jobStatus);
         if (mReadyToRock) {
             for (int i = 0; i < mControllers.size(); i++) {
                 StateController controller = mControllers.get(i);
@@ -2897,9 +2882,9 @@
         // Deal with any remaining work items in the old job.
         jobStatus.stopTrackingJobLocked(incomingJob);
 
-        synchronized (mPendingJobReasonCache) {
-            SparseIntArray reasonCache =
-                    mPendingJobReasonCache.get(jobStatus.getUid(), jobStatus.getNamespace());
+        synchronized (mPendingJobReasonsCache) {
+            SparseArray<int[]> reasonCache =
+                    mPendingJobReasonsCache.get(jobStatus.getUid(), jobStatus.getNamespace());
             if (reasonCache != null) {
                 reasonCache.delete(jobStatus.getJobId());
             }
@@ -2927,11 +2912,11 @@
         return removed;
     }
 
-    /** Remove the pending job reason for this job from the cache. */
-    void resetPendingJobReasonCache(@NonNull JobStatus jobStatus) {
-        synchronized (mPendingJobReasonCache) {
-            final SparseIntArray reasons =
-                    mPendingJobReasonCache.get(jobStatus.getUid(), jobStatus.getNamespace());
+    /** Remove the pending job reasons for this job from the cache. */
+    void resetPendingJobReasonsCache(@NonNull JobStatus jobStatus) {
+        synchronized (mPendingJobReasonsCache) {
+            final SparseArray<int[]> reasons =
+                    mPendingJobReasonsCache.get(jobStatus.getUid(), jobStatus.getNamespace());
             if (reasons != null) {
                 reasons.delete(jobStatus.getJobId());
             }
@@ -3313,18 +3298,18 @@
     public void onControllerStateChanged(@Nullable ArraySet<JobStatus> changedJobs) {
         if (changedJobs == null) {
             mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
-            synchronized (mPendingJobReasonCache) {
-                mPendingJobReasonCache.clear();
+            synchronized (mPendingJobReasonsCache) {
+                mPendingJobReasonsCache.clear();
             }
         } else if (changedJobs.size() > 0) {
             synchronized (mLock) {
                 mChangedJobList.addAll(changedJobs);
             }
             mHandler.obtainMessage(MSG_CHECK_CHANGED_JOB_LIST).sendToTarget();
-            synchronized (mPendingJobReasonCache) {
+            synchronized (mPendingJobReasonsCache) {
                 for (int i = changedJobs.size() - 1; i >= 0; --i) {
                     final JobStatus job = changedJobs.valueAt(i);
-                    resetPendingJobReasonCache(job);
+                    resetPendingJobReasonsCache(job);
                 }
             }
         }
@@ -3893,23 +3878,21 @@
             // Update the pending reason for any jobs that aren't going to be run.
             final int numRunnableJobs = runnableJobs.size();
             if (numRunnableJobs > 0 && numRunnableJobs != jobsToRun.size()) {
-                synchronized (mPendingJobReasonCache) {
+                synchronized (mPendingJobReasonsCache) {
                     for (int i = 0; i < numRunnableJobs; ++i) {
                         final JobStatus job = runnableJobs.get(i);
                         if (jobsToRun.contains(job)) {
-                            // We're running this job. Skip updating the pending reason.
-                            continue;
+                            continue; // we're running this job - skip updating the pending reason.
                         }
-                        SparseIntArray reasons =
-                                mPendingJobReasonCache.get(job.getUid(), job.getNamespace());
+                        SparseArray<int[]> reasons =
+                                mPendingJobReasonsCache.get(job.getUid(), job.getNamespace());
                         if (reasons == null) {
-                            reasons = new SparseIntArray();
-                            mPendingJobReasonCache.add(job.getUid(), job.getNamespace(), reasons);
+                            reasons = new SparseArray<>();
+                            mPendingJobReasonsCache.add(job.getUid(), job.getNamespace(), reasons);
                         }
-                        // We're force batching these jobs, so consider it an optimization
-                        // policy reason.
-                        reasons.put(job.getJobId(),
-                                JobScheduler.PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION);
+                        // we're force batching these jobs - note it as optimization.
+                        reasons.put(job.getJobId(), new int[]
+                                    { JobScheduler.PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION });
                     }
                 }
             }
@@ -5123,12 +5106,16 @@
 
         @Override
         public int getPendingJobReason(String namespace, int jobId) throws RemoteException {
-            final int uid = Binder.getCallingUid();
+            return getPendingJobReasons(validateNamespace(namespace), jobId)[0];
+        }
 
+        @Override
+        public int[] getPendingJobReasons(String namespace, int jobId) throws RemoteException {
+            final int uid = Binder.getCallingUid();
             final long ident = Binder.clearCallingIdentity();
             try {
-                return JobSchedulerService.this.getPendingJobReason(
-                        uid, validateNamespace(namespace), jobId);
+                return JobSchedulerService.this.getPendingJobReasons(
+                                                    uid, validateNamespace(namespace), jobId);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -5867,6 +5854,9 @@
             pw.print(android.app.job.Flags.FLAG_IGNORE_IMPORTANT_WHILE_FOREGROUND,
                     android.app.job.Flags.ignoreImportantWhileForeground());
             pw.println();
+            pw.print(android.app.job.Flags.FLAG_GET_PENDING_JOB_REASONS_API,
+                    android.app.job.Flags.getPendingJobReasonsApi());
+            pw.println();
             pw.decreaseIndent();
             pw.println();
 
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
index 68303e8..a4a3024 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
@@ -439,6 +439,9 @@
             case android.app.job.Flags.FLAG_IGNORE_IMPORTANT_WHILE_FOREGROUND:
                 pw.println(android.app.job.Flags.ignoreImportantWhileForeground());
                 break;
+            case android.app.job.Flags.FLAG_GET_PENDING_JOB_REASONS_API:
+                pw.println(android.app.job.Flags.getPendingJobReasonsApi());
+                break;
             default:
                 pw.println("Unknown flag: " + flagName);
                 break;
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 1dc5a71..58579eb 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
@@ -2067,12 +2067,11 @@
     }
 
     /**
-     * If {@link #isReady()} returns false, this will return a single reason why the job isn't
-     * ready. If {@link #isReady()} returns true, this will return
-     * {@link JobScheduler#PENDING_JOB_REASON_UNDEFINED}.
+     * This will return all potential reasons why the job is pending.
      */
-    @JobScheduler.PendingJobReason
-    public int getPendingJobReason() {
+    @NonNull
+    public int[] getPendingJobReasons() {
+        final ArrayList<Integer> reasons = new ArrayList<>();
         final int unsatisfiedConstraints = ~satisfiedConstraints
                 & (requiredConstraints | mDynamicConstraints | IMPLICIT_CONSTRAINTS);
         if ((CONSTRAINT_BACKGROUND_NOT_RESTRICTED & unsatisfiedConstraints) != 0) {
@@ -2084,78 +2083,99 @@
             // (they'll always get BACKGROUND_RESTRICTION) as the reason, regardless of
             // battery saver state.
             if (mIsUserBgRestricted) {
-                return JobScheduler.PENDING_JOB_REASON_BACKGROUND_RESTRICTION;
+                reasons.addLast(JobScheduler.PENDING_JOB_REASON_BACKGROUND_RESTRICTION);
+            } else {
+                reasons.addLast(JobScheduler.PENDING_JOB_REASON_DEVICE_STATE);
             }
-            return JobScheduler.PENDING_JOB_REASON_DEVICE_STATE;
         }
+        if ((CONSTRAINT_DEVICE_NOT_DOZING & unsatisfiedConstraints) != 0) {
+            if (!reasons.contains(JobScheduler.PENDING_JOB_REASON_DEVICE_STATE)) {
+                reasons.addLast(JobScheduler.PENDING_JOB_REASON_DEVICE_STATE);
+            }
+        }
+
         if ((CONSTRAINT_BATTERY_NOT_LOW & unsatisfiedConstraints) != 0) {
             if ((CONSTRAINT_BATTERY_NOT_LOW & requiredConstraints) != 0) {
                 // The developer requested this constraint, so it makes sense to return the
                 // explicit constraint reason.
-                return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_BATTERY_NOT_LOW;
+                reasons.addLast(JobScheduler.PENDING_JOB_REASON_CONSTRAINT_BATTERY_NOT_LOW);
+            } else {
+                // Hard-coding right now since the current dynamic constraint sets don't overlap
+                // TODO: return based on active dynamic constraint sets when they start overlapping
+                reasons.addLast(JobScheduler.PENDING_JOB_REASON_APP_STANDBY);
             }
-            // Hard-coding right now since the current dynamic constraint sets don't overlap
-            // TODO: return based on active dynamic constraint sets when they start overlapping
-            return JobScheduler.PENDING_JOB_REASON_APP_STANDBY;
         }
         if ((CONSTRAINT_CHARGING & unsatisfiedConstraints) != 0) {
             if ((CONSTRAINT_CHARGING & requiredConstraints) != 0) {
                 // The developer requested this constraint, so it makes sense to return the
                 // explicit constraint reason.
-                return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CHARGING;
+                reasons.addLast(JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CHARGING);
+            } else {
+                // Hard-coding right now since the current dynamic constraint sets don't overlap
+                // TODO: return based on active dynamic constraint sets when they start overlapping
+                if (!reasons.contains(JobScheduler.PENDING_JOB_REASON_APP_STANDBY)) {
+                    reasons.addLast(JobScheduler.PENDING_JOB_REASON_APP_STANDBY);
+                }
             }
-            // Hard-coding right now since the current dynamic constraint sets don't overlap
-            // TODO: return based on active dynamic constraint sets when they start overlapping
-            return JobScheduler.PENDING_JOB_REASON_APP_STANDBY;
-        }
-        if ((CONSTRAINT_CONNECTIVITY & unsatisfiedConstraints) != 0) {
-            return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CONNECTIVITY;
-        }
-        if ((CONSTRAINT_CONTENT_TRIGGER & unsatisfiedConstraints) != 0) {
-            return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CONTENT_TRIGGER;
-        }
-        if ((CONSTRAINT_DEVICE_NOT_DOZING & unsatisfiedConstraints) != 0) {
-            return JobScheduler.PENDING_JOB_REASON_DEVICE_STATE;
-        }
-        if ((CONSTRAINT_FLEXIBLE & unsatisfiedConstraints) != 0) {
-            return JobScheduler.PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION;
         }
         if ((CONSTRAINT_IDLE & unsatisfiedConstraints) != 0) {
             if ((CONSTRAINT_IDLE & requiredConstraints) != 0) {
                 // The developer requested this constraint, so it makes sense to return the
                 // explicit constraint reason.
-                return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_DEVICE_IDLE;
+                reasons.addLast(JobScheduler.PENDING_JOB_REASON_CONSTRAINT_DEVICE_IDLE);
+            } else {
+                // Hard-coding right now since the current dynamic constraint sets don't overlap
+                // TODO: return based on active dynamic constraint sets when they start overlapping
+                if (!reasons.contains(JobScheduler.PENDING_JOB_REASON_APP_STANDBY)) {
+                    reasons.addLast(JobScheduler.PENDING_JOB_REASON_APP_STANDBY);
+                }
             }
-            // Hard-coding right now since the current dynamic constraint sets don't overlap
-            // TODO: return based on active dynamic constraint sets when they start overlapping
-            return JobScheduler.PENDING_JOB_REASON_APP_STANDBY;
+        }
+
+        if ((CONSTRAINT_CONNECTIVITY & unsatisfiedConstraints) != 0) {
+            reasons.addLast(JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CONNECTIVITY);
+        }
+        if ((CONSTRAINT_CONTENT_TRIGGER & unsatisfiedConstraints) != 0) {
+            reasons.addLast(JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CONTENT_TRIGGER);
+        }
+        if ((CONSTRAINT_FLEXIBLE & unsatisfiedConstraints) != 0) {
+            reasons.addLast(JobScheduler.PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION);
         }
         if ((CONSTRAINT_PREFETCH & unsatisfiedConstraints) != 0) {
-            return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_PREFETCH;
+            reasons.addLast(JobScheduler.PENDING_JOB_REASON_CONSTRAINT_PREFETCH);
         }
         if ((CONSTRAINT_STORAGE_NOT_LOW & unsatisfiedConstraints) != 0) {
-            return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_STORAGE_NOT_LOW;
+            reasons.addLast(JobScheduler.PENDING_JOB_REASON_CONSTRAINT_STORAGE_NOT_LOW);
         }
         if ((CONSTRAINT_TIMING_DELAY & unsatisfiedConstraints) != 0) {
-            return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_MINIMUM_LATENCY;
+            reasons.addLast(JobScheduler.PENDING_JOB_REASON_CONSTRAINT_MINIMUM_LATENCY);
         }
         if ((CONSTRAINT_WITHIN_QUOTA & unsatisfiedConstraints) != 0) {
-            return JobScheduler.PENDING_JOB_REASON_QUOTA;
+            reasons.addLast(JobScheduler.PENDING_JOB_REASON_QUOTA);
+        }
+        if (android.app.job.Flags.getPendingJobReasonsApi()) {
+            if ((CONSTRAINT_DEADLINE & unsatisfiedConstraints) != 0) {
+                reasons.addLast(JobScheduler.PENDING_JOB_REASON_CONSTRAINT_DEADLINE);
+            }
         }
 
-        if (getEffectiveStandbyBucket() == NEVER_INDEX) {
-            Slog.wtf(TAG, "App in NEVER bucket querying pending job reason");
-            // The user hasn't officially launched this app.
-            return JobScheduler.PENDING_JOB_REASON_USER;
-        }
-        if (serviceProcessName != null) {
-            return JobScheduler.PENDING_JOB_REASON_APP;
+        if (reasons.isEmpty()) {
+            if (getEffectiveStandbyBucket() == NEVER_INDEX) {
+                Slog.wtf(TAG, "App in NEVER bucket querying pending job reason");
+                // The user hasn't officially launched this app.
+                reasons.add(JobScheduler.PENDING_JOB_REASON_USER);
+            } else if (serviceProcessName != null) {
+                reasons.add(JobScheduler.PENDING_JOB_REASON_APP);
+            } else {
+                reasons.add(JobScheduler.PENDING_JOB_REASON_UNDEFINED);
+            }
         }
 
-        if (!isReady()) {
-            Slog.wtf(TAG, "Unknown reason job isn't ready");
+        final int[] reasonsArr = new int[reasons.size()];
+        for (int i = 0; i < reasonsArr.length; i++) {
+            reasonsArr[i] = reasons.get(i);
         }
-        return JobScheduler.PENDING_JOB_REASON_UNDEFINED;
+        return reasonsArr;
     }
 
     /** @return whether or not the @param constraint is satisfied */
diff --git a/config/preloaded-classes-denylist b/config/preloaded-classes-denylist
index a413bbd..16f0693 100644
--- a/config/preloaded-classes-denylist
+++ b/config/preloaded-classes-denylist
@@ -1,13 +1,16 @@
 android.content.AsyncTaskLoader$LoadTask
+android.media.MediaCodecInfo$CodecCapabilities$FeatureList
 android.net.ConnectivityThread$Singleton
 android.os.FileObserver
 android.os.NullVibrator
+android.permission.PermissionManager
+android.provider.MediaStore
 android.speech.tts.TextToSpeech$Connection$SetupConnectionAsyncTask
+android.view.HdrRenderState
 android.widget.Magnifier
+com.android.internal.jank.InteractionJankMonitor$InstanceHolder
+com.android.internal.util.LatencyTracker$SLatencyTrackerHolder
 gov.nist.core.net.DefaultNetworkLayer
-android.net.rtp.AudioGroup
-android.net.rtp.AudioStream
-android.net.rtp.RtpStream
 java.util.concurrent.ThreadLocalRandom
 java.util.ImmutableCollections
-com.android.internal.jank.InteractionJankMonitor$InstanceHolder
+sun.nio.fs.UnixChannelFactory
diff --git a/core/api/current.txt b/core/api/current.txt
index 0153213..faecbf1 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -241,6 +241,7 @@
     field public static final String PROVIDE_REMOTE_CREDENTIALS = "android.permission.PROVIDE_REMOTE_CREDENTIALS";
     field @FlaggedApi("android.security.aapm_api") public static final String QUERY_ADVANCED_PROTECTION_MODE = "android.permission.QUERY_ADVANCED_PROTECTION_MODE";
     field public static final String QUERY_ALL_PACKAGES = "android.permission.QUERY_ALL_PACKAGES";
+    field @FlaggedApi("android.permission.flags.ranging_permission_enabled") public static final String RANGING = "android.permission.RANGING";
     field public static final String READ_ASSISTANT_APP_SEARCH_DATA = "android.permission.READ_ASSISTANT_APP_SEARCH_DATA";
     field public static final String READ_BASIC_PHONE_STATE = "android.permission.READ_BASIC_PHONE_STATE";
     field public static final String READ_CALENDAR = "android.permission.READ_CALENDAR";
@@ -1074,6 +1075,7 @@
     field public static final int layout = 16842994; // 0x10100f2
     field public static final int layoutAnimation = 16842988; // 0x10100ec
     field public static final int layoutDirection = 16843698; // 0x10103b2
+    field @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") public static final int layoutLabel;
     field public static final int layoutMode = 16843738; // 0x10103da
     field public static final int layout_above = 16843140; // 0x1010184
     field public static final int layout_alignBaseline = 16843142; // 0x1010186
@@ -8023,6 +8025,7 @@
     field @FlaggedApi("android.view.contentprotection.flags.manage_device_policy_enabled") public static final String CONTENT_PROTECTION_POLICY = "contentProtection";
     field public static final String KEYGUARD_DISABLED_FEATURES_POLICY = "keyguardDisabledFeatures";
     field public static final String LOCK_TASK_POLICY = "lockTask";
+    field @FlaggedApi("android.app.admin.flags.set_mte_policy_coexistence") public static final String MEMORY_TAGGING_POLICY = "memoryTagging";
     field public static final String PACKAGES_SUSPENDED_POLICY = "packagesSuspended";
     field public static final String PACKAGE_UNINSTALL_BLOCKED_POLICY = "packageUninstallBlocked";
     field public static final String PASSWORD_COMPLEXITY_POLICY = "passwordComplexity";
@@ -9245,6 +9248,7 @@
     method @Nullable public String getNamespace();
     method @Nullable public abstract android.app.job.JobInfo getPendingJob(int);
     method public int getPendingJobReason(int);
+    method @FlaggedApi("android.app.job.get_pending_job_reasons_api") @NonNull public int[] getPendingJobReasons(int);
     method @NonNull public java.util.Map<java.lang.String,java.util.List<android.app.job.JobInfo>> getPendingJobsInAllNamespaces();
     method public abstract int schedule(@NonNull android.app.job.JobInfo);
     field public static final int PENDING_JOB_REASON_APP = 1; // 0x1
@@ -9254,6 +9258,7 @@
     field public static final int PENDING_JOB_REASON_CONSTRAINT_CHARGING = 5; // 0x5
     field public static final int PENDING_JOB_REASON_CONSTRAINT_CONNECTIVITY = 6; // 0x6
     field public static final int PENDING_JOB_REASON_CONSTRAINT_CONTENT_TRIGGER = 7; // 0x7
+    field @FlaggedApi("android.app.job.get_pending_job_reasons_api") public static final int PENDING_JOB_REASON_CONSTRAINT_DEADLINE = 16; // 0x10
     field public static final int PENDING_JOB_REASON_CONSTRAINT_DEVICE_IDLE = 8; // 0x8
     field public static final int PENDING_JOB_REASON_CONSTRAINT_MINIMUM_LATENCY = 9; // 0x9
     field public static final int PENDING_JOB_REASON_CONSTRAINT_PREFETCH = 10; // 0xa
@@ -9687,10 +9692,8 @@
     method @Nullable public String getId();
     method @Nullable public android.net.Uri getThumbnail();
     method @Nullable public CharSequence getTitle();
-    method @NonNull public static android.app.wallpaper.WallpaperDescription readFromStream(@NonNull java.io.InputStream) throws java.io.IOException;
     method @NonNull public android.app.wallpaper.WallpaperDescription.Builder toBuilder();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
-    method public void writeToStream(@NonNull java.io.OutputStream) throws java.io.IOException;
     field @NonNull public static final android.os.Parcelable.Creator<android.app.wallpaper.WallpaperDescription> CREATOR;
   }
 
@@ -13701,7 +13704,7 @@
     field public static final int FLAG_STOP_WITH_TASK = 1; // 0x1
     field public static final int FLAG_USE_APP_ZYGOTE = 8; // 0x8
     field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_CAMERA}, anyOf={android.Manifest.permission.CAMERA}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_CAMERA = 64; // 0x40
-    field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE}, anyOf={android.Manifest.permission.BLUETOOTH_ADVERTISE, android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.CHANGE_NETWORK_STATE, android.Manifest.permission.CHANGE_WIFI_STATE, android.Manifest.permission.CHANGE_WIFI_MULTICAST_STATE, android.Manifest.permission.NFC, android.Manifest.permission.TRANSMIT_IR, android.Manifest.permission.UWB_RANGING}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE = 16; // 0x10
+    field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE}, anyOf={android.Manifest.permission.BLUETOOTH_ADVERTISE, android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.CHANGE_NETWORK_STATE, android.Manifest.permission.CHANGE_WIFI_STATE, android.Manifest.permission.CHANGE_WIFI_MULTICAST_STATE, android.Manifest.permission.NFC, android.Manifest.permission.TRANSMIT_IR, android.Manifest.permission.UWB_RANGING, android.Manifest.permission.RANGING}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE = 16; // 0x10
     field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_DATA_SYNC, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_DATA_SYNC = 1; // 0x1
     field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_HEALTH}, anyOf={android.Manifest.permission.ACTIVITY_RECOGNITION, android.Manifest.permission.BODY_SENSORS, android.Manifest.permission.HIGH_SAMPLING_RATE_SENSORS}) public static final int FOREGROUND_SERVICE_TYPE_HEALTH = 256; // 0x100
     field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_LOCATION}, anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_LOCATION = 8; // 0x8
@@ -16418,6 +16421,7 @@
     field public static final int FLEX_RGBA_8888 = 42; // 0x2a
     field public static final int FLEX_RGB_888 = 41; // 0x29
     field public static final int HEIC = 1212500294; // 0x48454946
+    field @FlaggedApi("com.android.internal.camera.flags.camera_heif_gainmap") public static final int HEIC_ULTRAHDR = 4102; // 0x1006
     field public static final int JPEG = 256; // 0x100
     field public static final int JPEG_R = 4101; // 0x1005
     field public static final int NV16 = 16; // 0x10
@@ -16844,6 +16848,7 @@
     field @FlaggedApi("com.android.text.flags.letter_spacing_justification") public static final int TEXT_RUN_FLAG_LEFT_EDGE = 8192; // 0x2000
     field @FlaggedApi("com.android.text.flags.letter_spacing_justification") public static final int TEXT_RUN_FLAG_RIGHT_EDGE = 16384; // 0x4000
     field public static final int UNDERLINE_TEXT_FLAG = 8; // 0x8
+    field @FlaggedApi("com.android.text.flags.vertical_text_layout") public static final int VERTICAL_TEXT_FLAG = 4096; // 0x1000
   }
 
   public enum Paint.Align {
@@ -18708,6 +18713,7 @@
     field public static final int DATASPACE_DISPLAY_P3 = 143261696; // 0x88a0000
     field public static final int DATASPACE_DYNAMIC_DEPTH = 4098; // 0x1002
     field public static final int DATASPACE_HEIF = 4100; // 0x1004
+    field @FlaggedApi("com.android.internal.camera.flags.camera_heif_gainmap") public static final int DATASPACE_HEIF_ULTRAHDR = 4102; // 0x1006
     field public static final int DATASPACE_JFIF = 146931712; // 0x8c20000
     field public static final int DATASPACE_JPEG_R = 4101; // 0x1005
     field public static final int DATASPACE_SCRGB = 411107328; // 0x18810000
@@ -20986,6 +20992,7 @@
     method @Deprecated public android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface();
     method public android.view.View onCreateInputView();
     method protected void onCurrentInputMethodSubtypeChanged(android.view.inputmethod.InputMethodSubtype);
+    method @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") public void onCustomImeSwitcherButtonRequestedVisible(boolean);
     method public void onDisplayCompletions(android.view.inputmethod.CompletionInfo[]);
     method public boolean onEvaluateFullscreenMode();
     method @CallSuper public boolean onEvaluateInputViewShown();
@@ -52997,7 +53004,7 @@
     method public void addOnUnhandledKeyEventListener(android.view.View.OnUnhandledKeyEventListener);
     method public void addTouchables(java.util.ArrayList<android.view.View>);
     method public android.view.ViewPropertyAnimator animate();
-    method public void announceForAccessibility(CharSequence);
+    method @Deprecated @FlaggedApi("android.view.accessibility.deprecate_accessibility_announcement_apis") public void announceForAccessibility(CharSequence);
     method public void autofill(android.view.autofill.AutofillValue);
     method public void autofill(@NonNull android.util.SparseArray<android.view.autofill.AutofillValue>);
     method protected boolean awakenScrollBars();
@@ -55247,7 +55254,7 @@
     field public static final int SPEECH_STATE_SPEAKING_END = 2; // 0x2
     field public static final int SPEECH_STATE_SPEAKING_START = 1; // 0x1
     field public static final int TYPES_ALL_MASK = -1; // 0xffffffff
-    field public static final int TYPE_ANNOUNCEMENT = 16384; // 0x4000
+    field @Deprecated @FlaggedApi("android.view.accessibility.deprecate_accessibility_announcement_apis") public static final int TYPE_ANNOUNCEMENT = 16384; // 0x4000
     field public static final int TYPE_ASSIST_READING_CONTEXT = 16777216; // 0x1000000
     field public static final int TYPE_GESTURE_DETECTION_END = 524288; // 0x80000
     field public static final int TYPE_GESTURE_DETECTION_START = 262144; // 0x40000
@@ -55573,6 +55580,7 @@
     field public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH";
     field public static final int EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_MAX_LENGTH = 20000; // 0x4e20
     field public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX";
+    field @FlaggedApi("android.view.accessibility.a11y_character_in_window_api") public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_IN_WINDOW_KEY = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_IN_WINDOW_KEY";
     field public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_KEY";
     field public static final int FLAG_PREFETCH_ANCESTORS = 1; // 0x1
     field public static final int FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST = 16; // 0x10
@@ -56975,6 +56983,9 @@
     method public String getExtraValueOf(String);
     method public int getIconResId();
     method @NonNull public String getLanguageTag();
+    method @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") @NonNull public CharSequence getLayoutDisplayName(@NonNull android.content.Context, @NonNull android.content.pm.ApplicationInfo);
+    method @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") @NonNull public CharSequence getLayoutLabelNonLocalized();
+    method @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") @StringRes public int getLayoutLabelResource();
     method @Deprecated @NonNull public String getLocale();
     method public String getMode();
     method @NonNull public CharSequence getNameOverride();
@@ -56994,6 +57005,8 @@
     method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setIsAsciiCapable(boolean);
     method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setIsAuxiliary(boolean);
     method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setLanguageTag(String);
+    method @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") @NonNull public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setLayoutLabelNonLocalized(@NonNull CharSequence);
+    method @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") @NonNull public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setLayoutLabelResource(@StringRes int);
     method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setOverridesImplicitlyEnabledSubtype(boolean);
     method @NonNull public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setPhysicalKeyboardHint(@Nullable android.icu.util.ULocale, @NonNull String);
     method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeExtraValue(String);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 02cd00d..2a01ca0 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -696,6 +696,7 @@
     field public static final String OPSTR_PLAY_AUDIO = "android:play_audio";
     field public static final String OPSTR_POST_NOTIFICATION = "android:post_notification";
     field public static final String OPSTR_PROJECT_MEDIA = "android:project_media";
+    field @FlaggedApi("android.permission.flags.ranging_permission_enabled") public static final String OPSTR_RANGING = "android:ranging";
     field @FlaggedApi("android.view.contentprotection.flags.rapid_clear_notifications_by_listener_app_op_enabled") public static final String OPSTR_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER = "android:rapid_clear_notifications_by_listener";
     field public static final String OPSTR_READ_CLIPBOARD = "android:read_clipboard";
     field @FlaggedApi("android.permission.flags.replace_body_sensor_permission_enabled") public static final String OPSTR_READ_HEART_RATE = "android:read_heart_rate";
@@ -3558,10 +3559,12 @@
     method @Deprecated public int getDefaultActivityPolicy();
     method @Deprecated public int getDefaultNavigationPolicy();
     method public int getDevicePolicy(int);
+    method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public java.time.Duration getDimDuration();
     method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") @Nullable public android.content.ComponentName getHomeComponent();
     method @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") @Nullable public android.content.ComponentName getInputMethodComponent();
     method public int getLockState();
     method @Nullable public String getName();
+    method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public java.time.Duration getScreenOffTimeout();
     method @NonNull public java.util.Set<android.os.UserHandle> getUsersWithMatchingAccounts();
     method @NonNull public java.util.List<android.companion.virtual.sensor.VirtualSensorConfig> getVirtualSensorConfigs();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -3579,6 +3582,7 @@
     field @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") public static final int POLICY_TYPE_BLOCKED_ACTIVITY = 6; // 0x6
     field @FlaggedApi("android.companion.virtual.flags.virtual_camera") public static final int POLICY_TYPE_CAMERA = 5; // 0x5
     field @FlaggedApi("android.companion.virtual.flags.cross_device_clipboard") public static final int POLICY_TYPE_CLIPBOARD = 4; // 0x4
+    field @FlaggedApi("android.companion.virtualdevice.flags.default_device_camera_access_policy") public static final int POLICY_TYPE_DEFAULT_DEVICE_CAMERA_ACCESS = 7; // 0x7
     field public static final int POLICY_TYPE_RECENTS = 2; // 0x2
     field public static final int POLICY_TYPE_SENSORS = 0; // 0x0
   }
@@ -3594,10 +3598,12 @@
     method @Deprecated @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedActivities(@NonNull java.util.Set<android.content.ComponentName>);
     method @Deprecated @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setDevicePolicy(int, int);
+    method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setDimDuration(@NonNull java.time.Duration);
     method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setHomeComponent(@Nullable android.content.ComponentName);
     method @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setInputMethodComponent(@Nullable android.content.ComponentName);
     method @NonNull @RequiresPermission(value=android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY, conditional=true) public android.companion.virtual.VirtualDeviceParams.Builder setLockState(int);
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setName(@NonNull String);
+    method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setScreenOffTimeout(@NonNull java.time.Duration);
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setUsersWithMatchingAccounts(@NonNull java.util.Set<android.os.UserHandle>);
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setVirtualSensorCallback(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.sensor.VirtualSensorCallback);
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setVirtualSensorDirectChannelCallback(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.sensor.VirtualSensorDirectChannelCallback);
@@ -5288,13 +5294,19 @@
     field public static final int VIRTUAL_DISPLAY_FLAG_TRUSTED = 1024; // 0x400
   }
 
+  public abstract static class VirtualDisplay.Callback {
+    method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") public void onRequestedBrightnessChanged(@FloatRange(from=0.0f, to=1.0f) float);
+  }
+
   public final class VirtualDisplayConfig implements android.os.Parcelable {
+    method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @FloatRange(from=0.0f, to=1.0f) public float getDefaultBrightness();
     method @FlaggedApi("android.companion.virtualdevice.flags.virtual_display_insets") @Nullable public android.view.DisplayCutout getDisplayCutout();
     method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") public boolean isHomeSupported();
     method @FlaggedApi("com.android.window.flags.vdm_force_app_universal_resizable_api") public boolean isIgnoreActivitySizeRestrictions();
   }
 
   public static final class VirtualDisplayConfig.Builder {
+    method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setDefaultBrightness(@FloatRange(from=0.0f, to=1.0f) float);
     method @FlaggedApi("android.companion.virtualdevice.flags.virtual_display_insets") @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setDisplayCutout(@Nullable android.view.DisplayCutout);
     method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setHomeSupported(boolean);
     method @FlaggedApi("com.android.window.flags.vdm_force_app_universal_resizable_api") @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setIgnoreActivitySizeRestrictions(boolean);
@@ -7022,7 +7034,7 @@
     method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setAllowMultipleTriggers(boolean);
     method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setAudioCapabilities(int);
     method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setCaptureRequested(boolean);
-    method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setData(@Nullable byte[]);
+    method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setData(@NonNull byte[]);
     method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setKeyphrases(@NonNull java.util.Collection<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra>);
   }
 
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 36fc65a..1b707f7 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1017,6 +1017,12 @@
     public static final int PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL = 1 << 6;
 
     /**
+     * @hide
+     * Process is guaranteed cpu time (IE. it will not be frozen).
+     */
+    public static final int PROCESS_CAPABILITY_CPU_TIME = 1 << 7;
+
+    /**
      * @hide all capabilities, the ORing of all flags in {@link ProcessCapability}.
      *
      * Don't expose it as TestApi -- we may add new capabilities any time, which could
@@ -1028,7 +1034,8 @@
             | PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK
             | PROCESS_CAPABILITY_BFSL
             | PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK
-            | PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL;
+            | PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL
+            | PROCESS_CAPABILITY_CPU_TIME;
 
     /**
      * All implicit capabilities. This capability set is currently only used for processes under
@@ -1053,6 +1060,7 @@
         pw.print((caps & PROCESS_CAPABILITY_BFSL) != 0 ? 'F' : '-');
         pw.print((caps & PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK) != 0 ? 'U' : '-');
         pw.print((caps & PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL) != 0 ? 'A' : '-');
+        pw.print((caps & PROCESS_CAPABILITY_CPU_TIME) != 0 ? 'T' : '-');
     }
 
     /** @hide */
@@ -1065,6 +1073,7 @@
         sb.append((caps & PROCESS_CAPABILITY_BFSL) != 0 ? 'F' : '-');
         sb.append((caps & PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK) != 0 ? 'U' : '-');
         sb.append((caps & PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL) != 0 ? 'A' : '-');
+        sb.append((caps & PROCESS_CAPABILITY_CPU_TIME) != 0 ? 'T' : '-');
     }
 
     /**
@@ -2764,14 +2773,19 @@
         /**
          * Information of organized child tasks.
          *
+         * @deprecated No longer used
          * @hide
          */
+        @Deprecated
         public ArrayList<RecentTaskInfo> childrenTaskInfos = new ArrayList<>();
 
         /**
          * Information about the last snapshot taken for this task.
+         *
+         * @deprecated No longer used
          * @hide
          */
+        @Deprecated
         public PersistedTaskSnapshotData lastSnapshotData = new PersistedTaskSnapshotData();
 
         public RecentTaskInfo() {
@@ -2793,7 +2807,7 @@
             lastSnapshotData.taskSize = source.readTypedObject(Point.CREATOR);
             lastSnapshotData.contentInsets = source.readTypedObject(Rect.CREATOR);
             lastSnapshotData.bufferSize = source.readTypedObject(Point.CREATOR);
-            super.readFromParcel(source);
+            super.readTaskFromParcel(source);
         }
 
         @Override
@@ -2804,7 +2818,7 @@
             dest.writeTypedObject(lastSnapshotData.taskSize, flags);
             dest.writeTypedObject(lastSnapshotData.contentInsets, flags);
             dest.writeTypedObject(lastSnapshotData.bufferSize, flags);
-            super.writeToParcel(dest, flags);
+            super.writeTaskToParcel(dest, flags);
         }
 
         public static final @android.annotation.NonNull Creator<RecentTaskInfo> CREATOR
@@ -2988,13 +3002,13 @@
 
         public void readFromParcel(Parcel source) {
             id = source.readInt();
-            super.readFromParcel(source);
+            super.readTaskFromParcel(source);
         }
 
         @Override
         public void writeToParcel(Parcel dest, int flags) {
             dest.writeInt(id);
-            super.writeToParcel(dest, flags);
+            super.writeTaskToParcel(dest, flags);
         }
 
         public static final @android.annotation.NonNull Creator<RunningTaskInfo> CREATOR = new Creator<RunningTaskInfo>() {
diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java
index 799df1f..16dcf2a 100644
--- a/core/java/android/app/ActivityTaskManager.java
+++ b/core/java/android/app/ActivityTaskManager.java
@@ -534,10 +534,9 @@
             dest.writeIntArray(childTaskUserIds);
             dest.writeInt(visible ? 1 : 0);
             dest.writeInt(position);
-            super.writeToParcel(dest, flags);
+            super.writeTaskToParcel(dest, flags);
         }
 
-        @Override
         void readFromParcel(Parcel source) {
             bounds = source.readTypedObject(Rect.CREATOR);
             childTaskIds = source.createIntArray();
@@ -546,7 +545,7 @@
             childTaskUserIds = source.createIntArray();
             visible = source.readInt() > 0;
             position = source.readInt();
-            super.readFromParcel(source);
+            super.readTaskFromParcel(source);
         }
 
         public static final @NonNull Creator<RootTaskInfo> CREATOR = new Creator<>() {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index ca98da7..60b8f80 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -3100,6 +3100,19 @@
         mResourcesManager = ResourcesManager.getInstance();
     }
 
+    /**
+     * Creates and initialize a new system activity thread, to be used for testing. This does not
+     * call {@link #attach}, so it does not modify static state.
+     */
+    @VisibleForTesting
+    @NonNull
+    public static ActivityThread createSystemActivityThreadForTesting() {
+        final var thread = new ActivityThread();
+        thread.mSystemThread = true;
+        initializeSystemThread(thread);
+        return thread;
+    }
+
     @UnsupportedAppUsage
     public ApplicationThread getApplicationThread()
     {
@@ -6806,6 +6819,16 @@
             LoadedApk.makePaths(this, resApk.getApplicationInfo(), oldPaths);
             resApk.updateApplicationInfo(ai, oldPaths);
         }
+        if (android.content.res.Flags.systemContextHandleAppInfoChanged() && mSystemThread) {
+            final var systemContext = getSystemContext();
+            if (systemContext.getPackageName().equals(ai.packageName)) {
+                // The system package is not tracked directly, but still needs to receive updates to
+                // its application info.
+                final ArrayList<String> oldPaths = new ArrayList<>();
+                LoadedApk.makePaths(this, systemContext.getApplicationInfo(), oldPaths);
+                systemContext.mPackageInfo.updateApplicationInfo(ai, oldPaths);
+            }
+        }
 
         ResourcesImpl beforeImpl = getApplication().getResources().getImpl();
 
@@ -8560,17 +8583,7 @@
             // we can't display an alert, we just want to die die die.
             android.ddm.DdmHandleAppName.setAppName("system_process",
                     UserHandle.myUserId());
-            try {
-                mInstrumentation = new Instrumentation();
-                mInstrumentation.basicInit(this);
-                ContextImpl context = ContextImpl.createAppContext(
-                        this, getSystemContext().mPackageInfo);
-                mInitialApplication = context.mPackageInfo.makeApplicationInner(true, null);
-                mInitialApplication.onCreate();
-            } catch (Exception e) {
-                throw new RuntimeException(
-                        "Unable to instantiate Application():" + e.toString(), e);
-            }
+            initializeSystemThread(this);
         }
 
         ViewRootImpl.ConfigChangedCallback configChangedCallback = (Configuration globalConfig) -> {
@@ -8595,6 +8608,28 @@
         ViewRootImpl.addConfigCallback(configChangedCallback);
     }
 
+    /**
+     * Initializes the given system activity thread, setting up its instrumentation and initial
+     * application. This only has an effect if the given thread is a {@link #mSystemThread}.
+     *
+     * @param thread the given system activity thread to initialize.
+     */
+    private static void initializeSystemThread(@NonNull ActivityThread thread) {
+        if (!thread.mSystemThread) {
+            return;
+        }
+        try {
+            thread.mInstrumentation = new Instrumentation();
+            thread.mInstrumentation.basicInit(thread);
+            ContextImpl context = ContextImpl.createAppContext(
+                    thread, thread.getSystemContext().mPackageInfo);
+            thread.mInitialApplication = context.mPackageInfo.makeApplicationInner(true, null);
+            thread.mInitialApplication.onCreate();
+        } catch (Exception e) {
+            throw new RuntimeException("Unable to instantiate Application():" + e, e);
+        }
+    }
+
     @UnsupportedAppUsage
     public static ActivityThread systemMain() {
         ThreadedRenderer.initForSystemProcess();
diff --git a/core/java/android/app/AppCompatTaskInfo.java b/core/java/android/app/AppCompatTaskInfo.java
index 8370c2e..6879458 100644
--- a/core/java/android/app/AppCompatTaskInfo.java
+++ b/core/java/android/app/AppCompatTaskInfo.java
@@ -167,10 +167,11 @@
     }
 
     /**
-     * @return {@code true} if top activity is pillarboxed.
+     * @return {@code true} if the top activity bounds are letterboxed with width <= height.
      */
-    public boolean isTopActivityPillarboxed() {
-        return topActivityLetterboxWidth < topActivityLetterboxHeight;
+    public boolean isTopActivityPillarboxShaped() {
+        return isTopActivityLetterboxed()
+                && topActivityLetterboxWidth <= topActivityLetterboxHeight;
     }
 
     /**
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 2b0e86c..38c8583 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -63,6 +63,7 @@
 import android.os.HandlerExecutor;
 import android.os.HandlerThread;
 import android.os.IBinder;
+import android.os.IpcDataCache;
 import android.os.Looper;
 import android.os.PackageTagsList;
 import android.os.Parcel;
@@ -78,12 +79,14 @@
 import android.permission.PermissionUsageHelper;
 import android.permission.flags.Flags;
 import android.provider.DeviceConfig;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.LongSparseArray;
 import android.util.LongSparseLongArray;
 import android.util.Pools;
 import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.Immutable;
@@ -910,159 +913,157 @@
 
     /** @hide No operation specified. */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public static final int OP_NONE = AppProtoEnums.APP_OP_NONE;
+    public static final int OP_NONE = AppOpEnums.APP_OP_NONE;
     /** @hide Access to coarse location information. */
     @UnsupportedAppUsage
     @TestApi
-    public static final int OP_COARSE_LOCATION = AppProtoEnums.APP_OP_COARSE_LOCATION;
+    public static final int OP_COARSE_LOCATION = AppOpEnums.APP_OP_COARSE_LOCATION;
     /** @hide Access to fine location information. */
     @UnsupportedAppUsage
-    public static final int OP_FINE_LOCATION = AppProtoEnums.APP_OP_FINE_LOCATION;
+    public static final int OP_FINE_LOCATION = AppOpEnums.APP_OP_FINE_LOCATION;
     /** @hide Causing GPS to run. */
     @UnsupportedAppUsage
-    public static final int OP_GPS = AppProtoEnums.APP_OP_GPS;
+    public static final int OP_GPS = AppOpEnums.APP_OP_GPS;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_VIBRATE = AppProtoEnums.APP_OP_VIBRATE;
+    public static final int OP_VIBRATE = AppOpEnums.APP_OP_VIBRATE;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_READ_CONTACTS = AppProtoEnums.APP_OP_READ_CONTACTS;
+    public static final int OP_READ_CONTACTS = AppOpEnums.APP_OP_READ_CONTACTS;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_WRITE_CONTACTS = AppProtoEnums.APP_OP_WRITE_CONTACTS;
+    public static final int OP_WRITE_CONTACTS = AppOpEnums.APP_OP_WRITE_CONTACTS;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_READ_CALL_LOG = AppProtoEnums.APP_OP_READ_CALL_LOG;
+    public static final int OP_READ_CALL_LOG = AppOpEnums.APP_OP_READ_CALL_LOG;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_WRITE_CALL_LOG = AppProtoEnums.APP_OP_WRITE_CALL_LOG;
+    public static final int OP_WRITE_CALL_LOG = AppOpEnums.APP_OP_WRITE_CALL_LOG;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_READ_CALENDAR = AppProtoEnums.APP_OP_READ_CALENDAR;
+    public static final int OP_READ_CALENDAR = AppOpEnums.APP_OP_READ_CALENDAR;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_WRITE_CALENDAR = AppProtoEnums.APP_OP_WRITE_CALENDAR;
+    public static final int OP_WRITE_CALENDAR = AppOpEnums.APP_OP_WRITE_CALENDAR;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_WIFI_SCAN = AppProtoEnums.APP_OP_WIFI_SCAN;
+    public static final int OP_WIFI_SCAN = AppOpEnums.APP_OP_WIFI_SCAN;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_POST_NOTIFICATION = AppProtoEnums.APP_OP_POST_NOTIFICATION;
+    public static final int OP_POST_NOTIFICATION = AppOpEnums.APP_OP_POST_NOTIFICATION;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_NEIGHBORING_CELLS = AppProtoEnums.APP_OP_NEIGHBORING_CELLS;
+    public static final int OP_NEIGHBORING_CELLS = AppOpEnums.APP_OP_NEIGHBORING_CELLS;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_CALL_PHONE = AppProtoEnums.APP_OP_CALL_PHONE;
+    public static final int OP_CALL_PHONE = AppOpEnums.APP_OP_CALL_PHONE;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_READ_SMS = AppProtoEnums.APP_OP_READ_SMS;
+    public static final int OP_READ_SMS = AppOpEnums.APP_OP_READ_SMS;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_WRITE_SMS = AppProtoEnums.APP_OP_WRITE_SMS;
+    public static final int OP_WRITE_SMS = AppOpEnums.APP_OP_WRITE_SMS;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_RECEIVE_SMS = AppProtoEnums.APP_OP_RECEIVE_SMS;
+    public static final int OP_RECEIVE_SMS = AppOpEnums.APP_OP_RECEIVE_SMS;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_RECEIVE_EMERGECY_SMS =
-            AppProtoEnums.APP_OP_RECEIVE_EMERGENCY_SMS;
+    public static final int OP_RECEIVE_EMERGECY_SMS = AppOpEnums.APP_OP_RECEIVE_EMERGENCY_SMS;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_RECEIVE_MMS = AppProtoEnums.APP_OP_RECEIVE_MMS;
+    public static final int OP_RECEIVE_MMS = AppOpEnums.APP_OP_RECEIVE_MMS;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_RECEIVE_WAP_PUSH = AppProtoEnums.APP_OP_RECEIVE_WAP_PUSH;
+    public static final int OP_RECEIVE_WAP_PUSH = AppOpEnums.APP_OP_RECEIVE_WAP_PUSH;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_SEND_SMS = AppProtoEnums.APP_OP_SEND_SMS;
+    public static final int OP_SEND_SMS = AppOpEnums.APP_OP_SEND_SMS;
     /** @hide */
-    public static final int OP_MANAGE_ONGOING_CALLS = AppProtoEnums.APP_OP_MANAGE_ONGOING_CALLS;
+    public static final int OP_MANAGE_ONGOING_CALLS = AppOpEnums.APP_OP_MANAGE_ONGOING_CALLS;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_READ_ICC_SMS = AppProtoEnums.APP_OP_READ_ICC_SMS;
+    public static final int OP_READ_ICC_SMS = AppOpEnums.APP_OP_READ_ICC_SMS;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_WRITE_ICC_SMS = AppProtoEnums.APP_OP_WRITE_ICC_SMS;
+    public static final int OP_WRITE_ICC_SMS = AppOpEnums.APP_OP_WRITE_ICC_SMS;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_WRITE_SETTINGS = AppProtoEnums.APP_OP_WRITE_SETTINGS;
+    public static final int OP_WRITE_SETTINGS = AppOpEnums.APP_OP_WRITE_SETTINGS;
     /** @hide Required to draw on top of other apps. */
     @UnsupportedAppUsage
     @TestApi
-    public static final int OP_SYSTEM_ALERT_WINDOW = AppProtoEnums.APP_OP_SYSTEM_ALERT_WINDOW;
+    public static final int OP_SYSTEM_ALERT_WINDOW = AppOpEnums.APP_OP_SYSTEM_ALERT_WINDOW;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_ACCESS_NOTIFICATIONS =
-            AppProtoEnums.APP_OP_ACCESS_NOTIFICATIONS;
+    public static final int OP_ACCESS_NOTIFICATIONS = AppOpEnums.APP_OP_ACCESS_NOTIFICATIONS;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_CAMERA = AppProtoEnums.APP_OP_CAMERA;
+    public static final int OP_CAMERA = AppOpEnums.APP_OP_CAMERA;
     /** @hide */
     @UnsupportedAppUsage
     @TestApi
-    public static final int OP_RECORD_AUDIO = AppProtoEnums.APP_OP_RECORD_AUDIO;
+    public static final int OP_RECORD_AUDIO = AppOpEnums.APP_OP_RECORD_AUDIO;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_PLAY_AUDIO = AppProtoEnums.APP_OP_PLAY_AUDIO;
+    public static final int OP_PLAY_AUDIO = AppOpEnums.APP_OP_PLAY_AUDIO;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_READ_CLIPBOARD = AppProtoEnums.APP_OP_READ_CLIPBOARD;
+    public static final int OP_READ_CLIPBOARD = AppOpEnums.APP_OP_READ_CLIPBOARD;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_WRITE_CLIPBOARD = AppProtoEnums.APP_OP_WRITE_CLIPBOARD;
+    public static final int OP_WRITE_CLIPBOARD = AppOpEnums.APP_OP_WRITE_CLIPBOARD;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_TAKE_MEDIA_BUTTONS = AppProtoEnums.APP_OP_TAKE_MEDIA_BUTTONS;
+    public static final int OP_TAKE_MEDIA_BUTTONS = AppOpEnums.APP_OP_TAKE_MEDIA_BUTTONS;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_TAKE_AUDIO_FOCUS = AppProtoEnums.APP_OP_TAKE_AUDIO_FOCUS;
+    public static final int OP_TAKE_AUDIO_FOCUS = AppOpEnums.APP_OP_TAKE_AUDIO_FOCUS;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_AUDIO_MASTER_VOLUME = AppProtoEnums.APP_OP_AUDIO_MASTER_VOLUME;
+    public static final int OP_AUDIO_MASTER_VOLUME = AppOpEnums.APP_OP_AUDIO_MASTER_VOLUME;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_AUDIO_VOICE_VOLUME = AppProtoEnums.APP_OP_AUDIO_VOICE_VOLUME;
+    public static final int OP_AUDIO_VOICE_VOLUME = AppOpEnums.APP_OP_AUDIO_VOICE_VOLUME;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_AUDIO_RING_VOLUME = AppProtoEnums.APP_OP_AUDIO_RING_VOLUME;
+    public static final int OP_AUDIO_RING_VOLUME = AppOpEnums.APP_OP_AUDIO_RING_VOLUME;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_AUDIO_MEDIA_VOLUME = AppProtoEnums.APP_OP_AUDIO_MEDIA_VOLUME;
+    public static final int OP_AUDIO_MEDIA_VOLUME = AppOpEnums.APP_OP_AUDIO_MEDIA_VOLUME;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_AUDIO_ALARM_VOLUME = AppProtoEnums.APP_OP_AUDIO_ALARM_VOLUME;
+    public static final int OP_AUDIO_ALARM_VOLUME = AppOpEnums.APP_OP_AUDIO_ALARM_VOLUME;
     /** @hide */
     @UnsupportedAppUsage
     public static final int OP_AUDIO_NOTIFICATION_VOLUME =
-            AppProtoEnums.APP_OP_AUDIO_NOTIFICATION_VOLUME;
+            AppOpEnums.APP_OP_AUDIO_NOTIFICATION_VOLUME;
     /** @hide */
     @UnsupportedAppUsage
     public static final int OP_AUDIO_BLUETOOTH_VOLUME =
-            AppProtoEnums.APP_OP_AUDIO_BLUETOOTH_VOLUME;
+            AppOpEnums.APP_OP_AUDIO_BLUETOOTH_VOLUME;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_WAKE_LOCK = AppProtoEnums.APP_OP_WAKE_LOCK;
+    public static final int OP_WAKE_LOCK = AppOpEnums.APP_OP_WAKE_LOCK;
     /** @hide Continually monitoring location data. */
     @UnsupportedAppUsage
     public static final int OP_MONITOR_LOCATION =
-            AppProtoEnums.APP_OP_MONITOR_LOCATION;
+            AppOpEnums.APP_OP_MONITOR_LOCATION;
     /** @hide Continually monitoring location data with a relatively high power request. */
     @UnsupportedAppUsage
     public static final int OP_MONITOR_HIGH_POWER_LOCATION =
-            AppProtoEnums.APP_OP_MONITOR_HIGH_POWER_LOCATION;
+            AppOpEnums.APP_OP_MONITOR_HIGH_POWER_LOCATION;
     /** @hide Retrieve current usage stats via {@link UsageStatsManager}. */
     @UnsupportedAppUsage
-    public static final int OP_GET_USAGE_STATS = AppProtoEnums.APP_OP_GET_USAGE_STATS;
+    public static final int OP_GET_USAGE_STATS = AppOpEnums.APP_OP_GET_USAGE_STATS;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_MUTE_MICROPHONE = AppProtoEnums.APP_OP_MUTE_MICROPHONE;
+    public static final int OP_MUTE_MICROPHONE = AppOpEnums.APP_OP_MUTE_MICROPHONE;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_TOAST_WINDOW = AppProtoEnums.APP_OP_TOAST_WINDOW;
+    public static final int OP_TOAST_WINDOW = AppOpEnums.APP_OP_TOAST_WINDOW;
     /** @hide Capture the device's display contents and/or audio */
     @UnsupportedAppUsage
-    public static final int OP_PROJECT_MEDIA = AppProtoEnums.APP_OP_PROJECT_MEDIA;
+    public static final int OP_PROJECT_MEDIA = AppOpEnums.APP_OP_PROJECT_MEDIA;
     /**
      * Start (without additional user intervention) a VPN connection, as used by {@link
      * android.net.VpnService} along with as Platform VPN connections, as used by {@link
@@ -1075,146 +1076,141 @@
      * @hide
      */
     @UnsupportedAppUsage
-    public static final int OP_ACTIVATE_VPN = AppProtoEnums.APP_OP_ACTIVATE_VPN;
+    public static final int OP_ACTIVATE_VPN = AppOpEnums.APP_OP_ACTIVATE_VPN;
     /** @hide Access the WallpaperManagerAPI to write wallpapers. */
     @UnsupportedAppUsage
-    public static final int OP_WRITE_WALLPAPER = AppProtoEnums.APP_OP_WRITE_WALLPAPER;
+    public static final int OP_WRITE_WALLPAPER = AppOpEnums.APP_OP_WRITE_WALLPAPER;
     /** @hide Received the assist structure from an app. */
     @UnsupportedAppUsage
-    public static final int OP_ASSIST_STRUCTURE = AppProtoEnums.APP_OP_ASSIST_STRUCTURE;
+    public static final int OP_ASSIST_STRUCTURE = AppOpEnums.APP_OP_ASSIST_STRUCTURE;
     /** @hide Received a screenshot from assist. */
     @UnsupportedAppUsage
-    public static final int OP_ASSIST_SCREENSHOT = AppProtoEnums.APP_OP_ASSIST_SCREENSHOT;
+    public static final int OP_ASSIST_SCREENSHOT = AppOpEnums.APP_OP_ASSIST_SCREENSHOT;
     /** @hide Read the phone state. */
     @UnsupportedAppUsage
-    public static final int OP_READ_PHONE_STATE = AppProtoEnums.APP_OP_READ_PHONE_STATE;
+    public static final int OP_READ_PHONE_STATE = AppOpEnums.APP_OP_READ_PHONE_STATE;
     /** @hide Add voicemail messages to the voicemail content provider. */
     @UnsupportedAppUsage
-    public static final int OP_ADD_VOICEMAIL = AppProtoEnums.APP_OP_ADD_VOICEMAIL;
+    public static final int OP_ADD_VOICEMAIL = AppOpEnums.APP_OP_ADD_VOICEMAIL;
     /** @hide Access APIs for SIP calling over VOIP or WiFi. */
     @UnsupportedAppUsage
-    public static final int OP_USE_SIP = AppProtoEnums.APP_OP_USE_SIP;
+    public static final int OP_USE_SIP = AppOpEnums.APP_OP_USE_SIP;
     /** @hide Intercept outgoing calls. */
     @UnsupportedAppUsage
-    public static final int OP_PROCESS_OUTGOING_CALLS =
-            AppProtoEnums.APP_OP_PROCESS_OUTGOING_CALLS;
+    public static final int OP_PROCESS_OUTGOING_CALLS = AppOpEnums.APP_OP_PROCESS_OUTGOING_CALLS;
     /** @hide User the fingerprint API. */
     @UnsupportedAppUsage
-    public static final int OP_USE_FINGERPRINT = AppProtoEnums.APP_OP_USE_FINGERPRINT;
+    public static final int OP_USE_FINGERPRINT = AppOpEnums.APP_OP_USE_FINGERPRINT;
     /** @hide Access to body sensors such as heart rate, etc. */
     @UnsupportedAppUsage
-    public static final int OP_BODY_SENSORS = AppProtoEnums.APP_OP_BODY_SENSORS;
+    public static final int OP_BODY_SENSORS = AppOpEnums.APP_OP_BODY_SENSORS;
     /** @hide Read previously received cell broadcast messages. */
     @UnsupportedAppUsage
-    public static final int OP_READ_CELL_BROADCASTS = AppProtoEnums.APP_OP_READ_CELL_BROADCASTS;
+    public static final int OP_READ_CELL_BROADCASTS = AppOpEnums.APP_OP_READ_CELL_BROADCASTS;
     /** @hide Inject mock location into the system. */
     @UnsupportedAppUsage
-    public static final int OP_MOCK_LOCATION = AppProtoEnums.APP_OP_MOCK_LOCATION;
+    public static final int OP_MOCK_LOCATION = AppOpEnums.APP_OP_MOCK_LOCATION;
     /** @hide Read external storage. */
     @UnsupportedAppUsage
-    public static final int OP_READ_EXTERNAL_STORAGE = AppProtoEnums.APP_OP_READ_EXTERNAL_STORAGE;
+    public static final int OP_READ_EXTERNAL_STORAGE = AppOpEnums.APP_OP_READ_EXTERNAL_STORAGE;
     /** @hide Write external storage. */
     @UnsupportedAppUsage
-    public static final int OP_WRITE_EXTERNAL_STORAGE =
-            AppProtoEnums.APP_OP_WRITE_EXTERNAL_STORAGE;
+    public static final int OP_WRITE_EXTERNAL_STORAGE = AppOpEnums.APP_OP_WRITE_EXTERNAL_STORAGE;
     /** @hide Turned on the screen. */
     @UnsupportedAppUsage
-    public static final int OP_TURN_SCREEN_ON = AppProtoEnums.APP_OP_TURN_SCREEN_ON;
+    public static final int OP_TURN_SCREEN_ON = AppOpEnums.APP_OP_TURN_SCREEN_ON;
     /** @hide Get device accounts. */
     @UnsupportedAppUsage
-    public static final int OP_GET_ACCOUNTS = AppProtoEnums.APP_OP_GET_ACCOUNTS;
+    public static final int OP_GET_ACCOUNTS = AppOpEnums.APP_OP_GET_ACCOUNTS;
     /** @hide Control whether an application is allowed to run in the background. */
     @UnsupportedAppUsage
-    public static final int OP_RUN_IN_BACKGROUND =
-            AppProtoEnums.APP_OP_RUN_IN_BACKGROUND;
+    public static final int OP_RUN_IN_BACKGROUND = AppOpEnums.APP_OP_RUN_IN_BACKGROUND;
     /** @hide */
     @UnsupportedAppUsage
     public static final int OP_AUDIO_ACCESSIBILITY_VOLUME =
-            AppProtoEnums.APP_OP_AUDIO_ACCESSIBILITY_VOLUME;
+             AppOpEnums.APP_OP_AUDIO_ACCESSIBILITY_VOLUME;
     /** @hide Read the phone number. */
     @UnsupportedAppUsage
-    public static final int OP_READ_PHONE_NUMBERS = AppProtoEnums.APP_OP_READ_PHONE_NUMBERS;
+    public static final int OP_READ_PHONE_NUMBERS = AppOpEnums.APP_OP_READ_PHONE_NUMBERS;
     /** @hide Request package installs through package installer */
     @UnsupportedAppUsage
     public static final int OP_REQUEST_INSTALL_PACKAGES =
-            AppProtoEnums.APP_OP_REQUEST_INSTALL_PACKAGES;
+            AppOpEnums.APP_OP_REQUEST_INSTALL_PACKAGES;
     /** @hide Enter picture-in-picture. */
     @UnsupportedAppUsage
-    public static final int OP_PICTURE_IN_PICTURE = AppProtoEnums.APP_OP_PICTURE_IN_PICTURE;
+    public static final int OP_PICTURE_IN_PICTURE = AppOpEnums.APP_OP_PICTURE_IN_PICTURE;
     /** @hide Instant app start foreground service. */
     @UnsupportedAppUsage
     public static final int OP_INSTANT_APP_START_FOREGROUND =
-            AppProtoEnums.APP_OP_INSTANT_APP_START_FOREGROUND;
+            AppOpEnums.APP_OP_INSTANT_APP_START_FOREGROUND;
     /** @hide Answer incoming phone calls */
     @UnsupportedAppUsage
-    public static final int OP_ANSWER_PHONE_CALLS = AppProtoEnums.APP_OP_ANSWER_PHONE_CALLS;
+    public static final int OP_ANSWER_PHONE_CALLS = AppOpEnums.APP_OP_ANSWER_PHONE_CALLS;
     /** @hide Run jobs when in background */
     @UnsupportedAppUsage
-    public static final int OP_RUN_ANY_IN_BACKGROUND = AppProtoEnums.APP_OP_RUN_ANY_IN_BACKGROUND;
+    public static final int OP_RUN_ANY_IN_BACKGROUND = AppOpEnums.APP_OP_RUN_ANY_IN_BACKGROUND;
     /** @hide Change Wi-Fi connectivity state */
     @UnsupportedAppUsage
-    public static final int OP_CHANGE_WIFI_STATE = AppProtoEnums.APP_OP_CHANGE_WIFI_STATE;
+    public static final int OP_CHANGE_WIFI_STATE = AppOpEnums.APP_OP_CHANGE_WIFI_STATE;
     /** @hide Request package deletion through package installer */
     @UnsupportedAppUsage
-    public static final int OP_REQUEST_DELETE_PACKAGES =
-            AppProtoEnums.APP_OP_REQUEST_DELETE_PACKAGES;
+    public static final int OP_REQUEST_DELETE_PACKAGES = AppOpEnums.APP_OP_REQUEST_DELETE_PACKAGES;
     /** @hide Bind an accessibility service. */
     @UnsupportedAppUsage
     public static final int OP_BIND_ACCESSIBILITY_SERVICE =
-            AppProtoEnums.APP_OP_BIND_ACCESSIBILITY_SERVICE;
+            AppOpEnums.APP_OP_BIND_ACCESSIBILITY_SERVICE;
     /** @hide Continue handover of a call from another app */
     @UnsupportedAppUsage
-    public static final int OP_ACCEPT_HANDOVER = AppProtoEnums.APP_OP_ACCEPT_HANDOVER;
+    public static final int OP_ACCEPT_HANDOVER = AppOpEnums.APP_OP_ACCEPT_HANDOVER;
     /** @hide Create and Manage IPsec Tunnels */
     @UnsupportedAppUsage
-    public static final int OP_MANAGE_IPSEC_TUNNELS = AppProtoEnums.APP_OP_MANAGE_IPSEC_TUNNELS;
+    public static final int OP_MANAGE_IPSEC_TUNNELS = AppOpEnums.APP_OP_MANAGE_IPSEC_TUNNELS;
     /** @hide Any app start foreground service. */
     @UnsupportedAppUsage
     @TestApi
-    public static final int OP_START_FOREGROUND = AppProtoEnums.APP_OP_START_FOREGROUND;
+    public static final int OP_START_FOREGROUND = AppOpEnums.APP_OP_START_FOREGROUND;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_BLUETOOTH_SCAN = AppProtoEnums.APP_OP_BLUETOOTH_SCAN;
+    public static final int OP_BLUETOOTH_SCAN = AppOpEnums.APP_OP_BLUETOOTH_SCAN;
     /** @hide */
-    public static final int OP_BLUETOOTH_CONNECT = AppProtoEnums.APP_OP_BLUETOOTH_CONNECT;
+    public static final int OP_BLUETOOTH_CONNECT = AppOpEnums.APP_OP_BLUETOOTH_CONNECT;
     /** @hide */
-    public static final int OP_BLUETOOTH_ADVERTISE = AppProtoEnums.APP_OP_BLUETOOTH_ADVERTISE;
+    public static final int OP_BLUETOOTH_ADVERTISE = AppOpEnums.APP_OP_BLUETOOTH_ADVERTISE;
     /** @hide Use the BiometricPrompt/BiometricManager APIs. */
-    public static final int OP_USE_BIOMETRIC = AppProtoEnums.APP_OP_USE_BIOMETRIC;
+    public static final int OP_USE_BIOMETRIC = AppOpEnums.APP_OP_USE_BIOMETRIC;
     /** @hide Physical activity recognition. */
-    public static final int OP_ACTIVITY_RECOGNITION = AppProtoEnums.APP_OP_ACTIVITY_RECOGNITION;
+    public static final int OP_ACTIVITY_RECOGNITION = AppOpEnums.APP_OP_ACTIVITY_RECOGNITION;
     /** @hide Financial app sms read. */
     public static final int OP_SMS_FINANCIAL_TRANSACTIONS =
-            AppProtoEnums.APP_OP_SMS_FINANCIAL_TRANSACTIONS;
+            AppOpEnums.APP_OP_SMS_FINANCIAL_TRANSACTIONS;
     /** @hide Read media of audio type. */
-    public static final int OP_READ_MEDIA_AUDIO = AppProtoEnums.APP_OP_READ_MEDIA_AUDIO;
+    public static final int OP_READ_MEDIA_AUDIO = AppOpEnums.APP_OP_READ_MEDIA_AUDIO;
     /** @hide Write media of audio type. */
-    public static final int OP_WRITE_MEDIA_AUDIO = AppProtoEnums.APP_OP_WRITE_MEDIA_AUDIO;
+    public static final int OP_WRITE_MEDIA_AUDIO = AppOpEnums.APP_OP_WRITE_MEDIA_AUDIO;
     /** @hide Read media of video type. */
-    public static final int OP_READ_MEDIA_VIDEO = AppProtoEnums.APP_OP_READ_MEDIA_VIDEO;
+    public static final int OP_READ_MEDIA_VIDEO = AppOpEnums.APP_OP_READ_MEDIA_VIDEO;
     /** @hide Write media of video type. */
-    public static final int OP_WRITE_MEDIA_VIDEO = AppProtoEnums.APP_OP_WRITE_MEDIA_VIDEO;
+    public static final int OP_WRITE_MEDIA_VIDEO = AppOpEnums.APP_OP_WRITE_MEDIA_VIDEO;
     /** @hide Read media of image type. */
-    public static final int OP_READ_MEDIA_IMAGES = AppProtoEnums.APP_OP_READ_MEDIA_IMAGES;
+    public static final int OP_READ_MEDIA_IMAGES = AppOpEnums.APP_OP_READ_MEDIA_IMAGES;
     /** @hide Write media of image type. */
-    public static final int OP_WRITE_MEDIA_IMAGES = AppProtoEnums.APP_OP_WRITE_MEDIA_IMAGES;
+    public static final int OP_WRITE_MEDIA_IMAGES = AppOpEnums.APP_OP_WRITE_MEDIA_IMAGES;
     /** @hide Has a legacy (non-isolated) view of storage. */
-    public static final int OP_LEGACY_STORAGE = AppProtoEnums.APP_OP_LEGACY_STORAGE;
+    public static final int OP_LEGACY_STORAGE = AppOpEnums.APP_OP_LEGACY_STORAGE;
     /** @hide Accessing accessibility features */
-    public static final int OP_ACCESS_ACCESSIBILITY = AppProtoEnums.APP_OP_ACCESS_ACCESSIBILITY;
+    public static final int OP_ACCESS_ACCESSIBILITY = AppOpEnums.APP_OP_ACCESS_ACCESSIBILITY;
     /** @hide Read the device identifiers (IMEI / MEID, IMSI, SIM / Build serial) */
     public static final int OP_READ_DEVICE_IDENTIFIERS =
-            AppProtoEnums.APP_OP_READ_DEVICE_IDENTIFIERS;
+            AppOpEnums.APP_OP_READ_DEVICE_IDENTIFIERS;
     /** @hide Read location metadata from media */
-    public static final int OP_ACCESS_MEDIA_LOCATION = AppProtoEnums.APP_OP_ACCESS_MEDIA_LOCATION;
+    public static final int OP_ACCESS_MEDIA_LOCATION = AppOpEnums.APP_OP_ACCESS_MEDIA_LOCATION;
     /** @hide Query all apps on device, regardless of declarations in the calling app manifest */
-    public static final int OP_QUERY_ALL_PACKAGES = AppProtoEnums.APP_OP_QUERY_ALL_PACKAGES;
+    public static final int OP_QUERY_ALL_PACKAGES = AppOpEnums.APP_OP_QUERY_ALL_PACKAGES;
     /** @hide Access all external storage */
-    public static final int OP_MANAGE_EXTERNAL_STORAGE =
-            AppProtoEnums.APP_OP_MANAGE_EXTERNAL_STORAGE;
+    public static final int OP_MANAGE_EXTERNAL_STORAGE = AppOpEnums.APP_OP_MANAGE_EXTERNAL_STORAGE;
     /** @hide Communicate cross-profile within the same profile group. */
     public static final int OP_INTERACT_ACROSS_PROFILES =
-            AppProtoEnums.APP_OP_INTERACT_ACROSS_PROFILES;
+            AppOpEnums.APP_OP_INTERACT_ACROSS_PROFILES;
     /**
      * Start (without additional user intervention) a Platform VPN connection, as used by {@link
      * android.net.VpnManager}
@@ -1225,16 +1221,16 @@
      *
      * @hide
      */
-    public static final int OP_ACTIVATE_PLATFORM_VPN = AppProtoEnums.APP_OP_ACTIVATE_PLATFORM_VPN;
+    public static final int OP_ACTIVATE_PLATFORM_VPN = AppOpEnums.APP_OP_ACTIVATE_PLATFORM_VPN;
     /** @hide Controls whether or not read logs are available for incremental installations. */
-    public static final int OP_LOADER_USAGE_STATS = AppProtoEnums.APP_OP_LOADER_USAGE_STATS;
+    public static final int OP_LOADER_USAGE_STATS = AppOpEnums.APP_OP_LOADER_USAGE_STATS;
 
     // App op deprecated/removed.
-    private static final int OP_DEPRECATED_1 = AppProtoEnums.APP_OP_DEPRECATED_1;
+    private static final int OP_DEPRECATED_1 = AppOpEnums.APP_OP_DEPRECATED_1;
 
     /** @hide Auto-revoke app permissions if app is unused for an extended period */
     public static final int OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED =
-            AppProtoEnums.APP_OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED;
+            AppOpEnums.APP_OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED;
 
     /**
      * Whether {@link #OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED} is allowed to be changed by
@@ -1243,55 +1239,55 @@
      * @hide
      */
     public static final int OP_AUTO_REVOKE_MANAGED_BY_INSTALLER =
-            AppProtoEnums.APP_OP_AUTO_REVOKE_MANAGED_BY_INSTALLER;
+            AppOpEnums.APP_OP_AUTO_REVOKE_MANAGED_BY_INSTALLER;
 
     /** @hide */
-    public static final int OP_NO_ISOLATED_STORAGE = AppProtoEnums.APP_OP_NO_ISOLATED_STORAGE;
+    public static final int OP_NO_ISOLATED_STORAGE = AppOpEnums.APP_OP_NO_ISOLATED_STORAGE;
 
     /**
      * Phone call is using microphone
      *
      * @hide
      */
-    public static final int OP_PHONE_CALL_MICROPHONE = AppProtoEnums.APP_OP_PHONE_CALL_MICROPHONE;
+    public static final int OP_PHONE_CALL_MICROPHONE = AppOpEnums.APP_OP_PHONE_CALL_MICROPHONE;
     /**
      * Phone call is using camera
      *
      * @hide
      */
-    public static final int OP_PHONE_CALL_CAMERA = AppProtoEnums.APP_OP_PHONE_CALL_CAMERA;
+    public static final int OP_PHONE_CALL_CAMERA = AppOpEnums.APP_OP_PHONE_CALL_CAMERA;
 
     /**
      * Audio is being recorded for hotword detection.
      *
      * @hide
      */
-    public static final int OP_RECORD_AUDIO_HOTWORD = AppProtoEnums.APP_OP_RECORD_AUDIO_HOTWORD;
+    public static final int OP_RECORD_AUDIO_HOTWORD = AppOpEnums.APP_OP_RECORD_AUDIO_HOTWORD;
 
     /**
      * Manage credentials in the system KeyChain.
      *
      * @hide
      */
-    public static final int OP_MANAGE_CREDENTIALS = AppProtoEnums.APP_OP_MANAGE_CREDENTIALS;
+    public static final int OP_MANAGE_CREDENTIALS = AppOpEnums.APP_OP_MANAGE_CREDENTIALS;
 
     /** @hide */
     public static final int OP_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER =
-            AppProtoEnums.APP_OP_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER;
+            AppOpEnums.APP_OP_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER;
 
     /**
      * App output audio is being recorded
      *
      * @hide
      */
-    public static final int OP_RECORD_AUDIO_OUTPUT = AppProtoEnums.APP_OP_RECORD_AUDIO_OUTPUT;
+    public static final int OP_RECORD_AUDIO_OUTPUT = AppOpEnums.APP_OP_RECORD_AUDIO_OUTPUT;
 
     /**
      * App can schedule exact alarm to perform timing based background work
      *
      * @hide
      */
-    public static final int OP_SCHEDULE_EXACT_ALARM = AppProtoEnums.APP_OP_SCHEDULE_EXACT_ALARM;
+    public static final int OP_SCHEDULE_EXACT_ALARM = AppOpEnums.APP_OP_SCHEDULE_EXACT_ALARM;
 
     /**
      * Fine location being accessed by a location source, which is
@@ -1301,7 +1297,7 @@
      *
      * @hide
      */
-    public static final int OP_FINE_LOCATION_SOURCE = AppProtoEnums.APP_OP_FINE_LOCATION_SOURCE;
+    public static final int OP_FINE_LOCATION_SOURCE = AppOpEnums.APP_OP_FINE_LOCATION_SOURCE;
 
     /**
      * Coarse location being accessed by a location source, which is
@@ -1311,7 +1307,7 @@
      *
      * @hide
      */
-    public static final int OP_COARSE_LOCATION_SOURCE = AppProtoEnums.APP_OP_COARSE_LOCATION_SOURCE;
+    public static final int OP_COARSE_LOCATION_SOURCE = AppOpEnums.APP_OP_COARSE_LOCATION_SOURCE;
 
     /**
      * Allow apps to create the requests to manage the media files without user confirmation.
@@ -1323,13 +1319,13 @@
      *
      * @hide
      */
-    public static final int OP_MANAGE_MEDIA = AppProtoEnums.APP_OP_MANAGE_MEDIA;
+    public static final int OP_MANAGE_MEDIA = AppOpEnums.APP_OP_MANAGE_MEDIA;
 
     /** @hide */
-    public static final int OP_UWB_RANGING = AppProtoEnums.APP_OP_UWB_RANGING;
+    public static final int OP_UWB_RANGING = AppOpEnums.APP_OP_UWB_RANGING;
 
     /** @hide */
-    public static final int OP_NEARBY_WIFI_DEVICES = AppProtoEnums.APP_OP_NEARBY_WIFI_DEVICES;
+    public static final int OP_NEARBY_WIFI_DEVICES = AppOpEnums.APP_OP_NEARBY_WIFI_DEVICES;
 
     /**
      * Activity recognition being accessed by an activity recognition source, which
@@ -1339,7 +1335,7 @@
      * @hide
      */
     public static final int OP_ACTIVITY_RECOGNITION_SOURCE =
-            AppProtoEnums.APP_OP_ACTIVITY_RECOGNITION_SOURCE;
+            AppOpEnums.APP_OP_ACTIVITY_RECOGNITION_SOURCE;
 
     /**
      * Incoming phone audio is being recorded
@@ -1347,21 +1343,21 @@
      * @hide
      */
     public static final int OP_RECORD_INCOMING_PHONE_AUDIO =
-            AppProtoEnums.APP_OP_RECORD_INCOMING_PHONE_AUDIO;
+            AppOpEnums.APP_OP_RECORD_INCOMING_PHONE_AUDIO;
 
     /**
      * VPN app establishes a connection through the VpnService API.
      *
      * @hide
      */
-    public static final int OP_ESTABLISH_VPN_SERVICE = AppProtoEnums.APP_OP_ESTABLISH_VPN_SERVICE;
+    public static final int OP_ESTABLISH_VPN_SERVICE = AppOpEnums.APP_OP_ESTABLISH_VPN_SERVICE;
 
     /**
      * VPN app establishes a connection through the VpnManager API.
      *
      * @hide
      */
-    public static final int OP_ESTABLISH_VPN_MANAGER = AppProtoEnums.APP_OP_ESTABLISH_VPN_MANAGER;
+    public static final int OP_ESTABLISH_VPN_MANAGER = AppOpEnums.APP_OP_ESTABLISH_VPN_MANAGER;
 
     /**
      * Access restricted settings.
@@ -1369,7 +1365,7 @@
      * @hide
      */
     public static final int OP_ACCESS_RESTRICTED_SETTINGS =
-            AppProtoEnums.APP_OP_ACCESS_RESTRICTED_SETTINGS;
+            AppOpEnums.APP_OP_ACCESS_RESTRICTED_SETTINGS;
 
     /**
      * Receive microphone audio from an ambient sound detection event
@@ -1377,7 +1373,7 @@
      * @hide
      */
     public static final int OP_RECEIVE_AMBIENT_TRIGGER_AUDIO =
-            AppProtoEnums.APP_OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
+            AppOpEnums.APP_OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
 
      /**
       * Receive audio from near-field mic (ie. TV remote)
@@ -1387,15 +1383,14 @@
       * @hide
       */
     public static final int OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO =
-            AppProtoEnums.APP_OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO;
+            AppOpEnums.APP_OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO;
 
     /**
      * App can schedule user-initiated jobs.
      *
      * @hide
      */
-    public static final int OP_RUN_USER_INITIATED_JOBS =
-            AppProtoEnums.APP_OP_RUN_USER_INITIATED_JOBS;
+    public static final int OP_RUN_USER_INITIATED_JOBS = AppOpEnums.APP_OP_RUN_USER_INITIATED_JOBS;
 
     /**
      * Notify apps that they have been granted URI permission photos
@@ -1403,7 +1398,7 @@
      * @hide
      */
     public static final int OP_READ_MEDIA_VISUAL_USER_SELECTED =
-            AppProtoEnums.APP_OP_READ_MEDIA_VISUAL_USER_SELECTED;
+            AppOpEnums.APP_OP_READ_MEDIA_VISUAL_USER_SELECTED;
 
     /**
      * Prevent an app from being suspended.
@@ -1413,7 +1408,7 @@
      * @hide
      */
     public static final int OP_SYSTEM_EXEMPT_FROM_SUSPENSION =
-            AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_SUSPENSION;
+            AppOpEnums.APP_OP_SYSTEM_EXEMPT_FROM_SUSPENSION;
 
     /**
      * Prevent an app from dismissible notifications. Starting from Android U, notifications with
@@ -1425,14 +1420,14 @@
      * @hide
      */
     public static final int OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS =
-            AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS;
+            AppOpEnums.APP_OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS;
 
     /**
      * An app op for reading/writing health connect data.
      *
      * @hide
      */
-    public static final int OP_READ_WRITE_HEALTH_DATA = AppProtoEnums.APP_OP_READ_WRITE_HEALTH_DATA;
+    public static final int OP_READ_WRITE_HEALTH_DATA = AppOpEnums.APP_OP_READ_WRITE_HEALTH_DATA;
 
     /**
      * Use foreground service with the type
@@ -1441,7 +1436,7 @@
      * @hide
      */
     public static final int OP_FOREGROUND_SERVICE_SPECIAL_USE =
-            AppProtoEnums.APP_OP_FOREGROUND_SERVICE_SPECIAL_USE;
+            AppOpEnums.APP_OP_FOREGROUND_SERVICE_SPECIAL_USE;
 
     /**
      * Exempt an app from all power-related restrictions, including app standby and doze.
@@ -1453,7 +1448,7 @@
      * @hide
      */
     public static final int OP_SYSTEM_EXEMPT_FROM_POWER_RESTRICTIONS =
-            AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_POWER_RESTRICTIONS;
+            AppOpEnums.APP_OP_SYSTEM_EXEMPT_FROM_POWER_RESTRICTIONS;
 
     /**
      * Prevent an app from being placed into hibernation.
@@ -1463,7 +1458,7 @@
      * @hide
      */
     public static final int OP_SYSTEM_EXEMPT_FROM_HIBERNATION =
-            AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_HIBERNATION;
+            AppOpEnums.APP_OP_SYSTEM_EXEMPT_FROM_HIBERNATION;
 
     /**
      * Allows an application to start an activity while running in the background.
@@ -1473,7 +1468,7 @@
      * @hide
      */
     public static final int OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION =
-            AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION;
+            AppOpEnums.APP_OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION;
 
     /**
      * Allows an application to capture bugreport directly without consent dialog when using the
@@ -1482,33 +1477,31 @@
      * @hide
      */
     public static final int OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD =
-            AppProtoEnums.APP_OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD;
+            AppOpEnums.APP_OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD;
 
     // App op deprecated/removed.
-    private static final int OP_DEPRECATED_2 = AppProtoEnums.APP_OP_BODY_SENSORS_WRIST_TEMPERATURE;
+    private static final int OP_DEPRECATED_2 = AppOpEnums.APP_OP_BODY_SENSORS_WRIST_TEMPERATURE;
 
     /**
      * Send an intent to launch instead of posting the notification to the status bar.
      *
      * @hide
      */
-    public static final int OP_USE_FULL_SCREEN_INTENT = AppProtoEnums.APP_OP_USE_FULL_SCREEN_INTENT;
+    public static final int OP_USE_FULL_SCREEN_INTENT = AppOpEnums.APP_OP_USE_FULL_SCREEN_INTENT;
 
     /**
      * Hides camera indicator for sandboxed detection apps that directly access the service.
      *
      * @hide
      */
-    public static final int OP_CAMERA_SANDBOXED =
-            AppProtoEnums.APP_OP_CAMERA_SANDBOXED;
+    public static final int OP_CAMERA_SANDBOXED = AppOpEnums.APP_OP_CAMERA_SANDBOXED;
 
     /**
      * Hides microphone indicator for sandboxed detection apps that directly access the service.
      *
      * @hide
      */
-    public static final int OP_RECORD_AUDIO_SANDBOXED =
-            AppProtoEnums.APP_OP_RECORD_AUDIO_SANDBOXED;
+    public static final int OP_RECORD_AUDIO_SANDBOXED = AppOpEnums.APP_OP_RECORD_AUDIO_SANDBOXED;
 
     /**
      * Allows the assistant app to be voice-triggered by detected hotwords from a trusted detection
@@ -1517,14 +1510,14 @@
      * @hide
      */
     public static final int OP_RECEIVE_SANDBOX_TRIGGER_AUDIO =
-            AppProtoEnums.APP_OP_RECEIVE_SANDBOX_TRIGGER_AUDIO;
+            AppOpEnums.APP_OP_RECEIVE_SANDBOX_TRIGGER_AUDIO;
 
     /**
      * This op has been deprecated.
      *
      */
     private static final int OP_DEPRECATED_3 =
-            AppProtoEnums.APP_OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA;
+            AppOpEnums.APP_OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA;
 
     /**
      * Creation of an overlay using accessibility services
@@ -1532,20 +1525,20 @@
      * @hide
      */
     public static final int OP_CREATE_ACCESSIBILITY_OVERLAY =
-            AppProtoEnums.APP_OP_CREATE_ACCESSIBILITY_OVERLAY;
+            AppOpEnums.APP_OP_CREATE_ACCESSIBILITY_OVERLAY;
 
     /**
      * Indicate that the user has enabled or disabled mobile data
      * @hide
      */
     public static final int OP_ENABLE_MOBILE_DATA_BY_USER =
-            AppProtoEnums.APP_OP_ENABLE_MOBILE_DATA_BY_USER;
+            AppOpEnums.APP_OP_ENABLE_MOBILE_DATA_BY_USER;
 
     /**
      * See {@link #OPSTR_MEDIA_ROUTING_CONTROL}.
      * @hide
      */
-    public static final int OP_MEDIA_ROUTING_CONTROL = AppProtoEnums.APP_OP_MEDIA_ROUTING_CONTROL;
+    public static final int OP_MEDIA_ROUTING_CONTROL = AppOpEnums.APP_OP_MEDIA_ROUTING_CONTROL;
 
     /**
      * Op code for use by tests to avoid interfering history logs that the wider system might
@@ -1553,7 +1546,7 @@
      *
      * @hide
      */
-    public static final int OP_RESERVED_FOR_TESTING = AppProtoEnums.APP_OP_RESERVED_FOR_TESTING;
+    public static final int OP_RESERVED_FOR_TESTING = AppOpEnums.APP_OP_RESERVED_FOR_TESTING;
 
     /**
      * Rapid clearing of notifications by a notification listener
@@ -1562,28 +1555,28 @@
      */
     // See b/289080543 for more details
     public static final int OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER =
-            AppProtoEnums.APP_OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER;
+            AppOpEnums.APP_OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER;
 
     /**
      * See {@link #OPSTR_READ_SYSTEM_GRAMMATICAL_GENDER}.
      * @hide
      */
     public static final int OP_READ_SYSTEM_GRAMMATICAL_GENDER =
-            AppProtoEnums.APP_OP_READ_SYSTEM_GRAMMATICAL_GENDER;
+            AppOpEnums.APP_OP_READ_SYSTEM_GRAMMATICAL_GENDER;
 
     /**
      * This app has been removed..
      *
      * @hide
      */
-    private static final int OP_DEPRECATED_4 = AppProtoEnums.APP_OP_RUN_BACKUP_JOBS;
+    private static final int OP_DEPRECATED_4 = AppOpEnums.APP_OP_RUN_BACKUP_JOBS;
 
     /**
      * Whether the app has enabled to receive the icon overlay for fetching archived apps.
      *
      * @hide
      */
-    public static final int OP_ARCHIVE_ICON_OVERLAY = AppProtoEnums.APP_OP_ARCHIVE_ICON_OVERLAY;
+    public static final int OP_ARCHIVE_ICON_OVERLAY = AppOpEnums.APP_OP_ARCHIVE_ICON_OVERLAY;
 
     /**
      * Whether the app has enabled compatibility support for unarchival.
@@ -1591,7 +1584,7 @@
      * @hide
      */
     public static final int OP_UNARCHIVAL_CONFIRMATION =
-            AppProtoEnums.APP_OP_UNARCHIVAL_CONFIRMATION;
+            AppOpEnums.APP_OP_UNARCHIVAL_CONFIRMATION;
 
     /**
      * Allows an app to access location without the traditional location permissions and while the
@@ -1603,7 +1596,7 @@
      *
      * @hide
      */
-    public static final int OP_EMERGENCY_LOCATION = AppProtoEnums.APP_OP_EMERGENCY_LOCATION;
+    public static final int OP_EMERGENCY_LOCATION = AppOpEnums.APP_OP_EMERGENCY_LOCATION;
 
     /**
      * Allows apps with a NotificationListenerService to receive notifications with sensitive
@@ -1613,17 +1606,24 @@
      * @hide
      */
     public static final int OP_RECEIVE_SENSITIVE_NOTIFICATIONS =
-            AppProtoEnums.APP_OP_RECEIVE_SENSITIVE_NOTIFICATIONS;
+            AppOpEnums.APP_OP_RECEIVE_SENSITIVE_NOTIFICATIONS;
 
     /** @hide Access to read heart rate sensor. */
-    public static final int OP_READ_HEART_RATE = AppProtoEnums.APP_OP_READ_HEART_RATE;
+    public static final int OP_READ_HEART_RATE = AppOpEnums.APP_OP_READ_HEART_RATE;
 
     /** @hide Access to read skin temperature. */
-    public static final int OP_READ_SKIN_TEMPERATURE = AppProtoEnums.APP_OP_READ_SKIN_TEMPERATURE;
+    public static final int OP_READ_SKIN_TEMPERATURE = AppOpEnums.APP_OP_READ_SKIN_TEMPERATURE;
+
+    /**
+     * Allows an app to range with nearby devices using any ranging technology available.
+     *
+     * @hide
+     */
+    public static final int OP_RANGING = AppOpEnums.APP_OP_RANGING;
 
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public static final int _NUM_OP = 151;
+    public static final int _NUM_OP = 152;
 
     /**
      * All app ops represented as strings.
@@ -1778,6 +1778,7 @@
             OPSTR_RECEIVE_SENSITIVE_NOTIFICATIONS,
             OPSTR_READ_HEART_RATE,
             OPSTR_READ_SKIN_TEMPERATURE,
+            OPSTR_RANGING,
     })
     public @interface AppOpString {}
 
@@ -2525,6 +2526,11 @@
     @FlaggedApi(Flags.FLAG_PLATFORM_SKIN_TEMPERATURE_ENABLED)
     public static final String OPSTR_READ_SKIN_TEMPERATURE = "android:read_skin_temperature";
 
+    /** @hide Access to ranging */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_RANGING_PERMISSION_ENABLED)
+    public static final String OPSTR_RANGING = "android:ranging";
+
     /** {@link #sAppOpsToNote} not initialized yet for this op */
     private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
     /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
@@ -2596,6 +2602,7 @@
             OP_BLUETOOTH_ADVERTISE,
             OP_UWB_RANGING,
             OP_NEARBY_WIFI_DEVICES,
+            Flags.rangingPermissionEnabled() ? OP_RANGING : OP_NONE,
             // Notifications
             OP_POST_NOTIFICATION,
             // Health
@@ -3118,6 +3125,10 @@
                 Flags.platformSkinTemperatureEnabled()
                     ? HealthPermissions.READ_SKIN_TEMPERATURE : null)
             .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_RANGING, OPSTR_RANGING, "RANGING")
+            .setPermission(Flags.rangingPermissionEnabled()?
+                Manifest.permission.RANGING : null)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
     };
 
     // The number of longs needed to form a full bitmask of app ops
@@ -7807,6 +7818,116 @@
         }
     }
 
+    private static final String APP_OP_MODE_CACHING_API = "getAppOpMode";
+    private static final String APP_OP_MODE_CACHING_NAME = "appOpModeCache";
+    private static final int APP_OP_MODE_CACHING_SIZE = 2048;
+
+    private static final IpcDataCache.QueryHandler<AppOpModeQuery, Integer> sGetAppOpModeQuery =
+            new IpcDataCache.QueryHandler<>() {
+                @Override
+                public Integer apply(AppOpModeQuery query) {
+                    IAppOpsService service = getService();
+                    try {
+                        return service.checkOperationRawForDevice(query.op, query.uid,
+                                query.packageName, query.attributionTag, query.virtualDeviceId);
+                    } catch (RemoteException e) {
+                        throw e.rethrowFromSystemServer();
+                    }
+                }
+
+                @Override
+                public boolean shouldBypassCache(@NonNull AppOpModeQuery query) {
+                    // If the flag to enable the new caching behavior is off, bypass the cache.
+                    return !Flags.appopModeCachingEnabled();
+                }
+            };
+
+    // A LRU cache on binder clients that caches AppOp mode by uid, packageName, virtualDeviceId
+    // and attributionTag.
+    private static final IpcDataCache<AppOpModeQuery, Integer> sAppOpModeCache =
+            new IpcDataCache<>(APP_OP_MODE_CACHING_SIZE, IpcDataCache.MODULE_SYSTEM,
+                    APP_OP_MODE_CACHING_API, APP_OP_MODE_CACHING_NAME, sGetAppOpModeQuery);
+
+    // Ops that we don't want to cache due to:
+    // 1) Discrepancy of attributionTag support in checkOp and noteOp that determines if a package
+    //    can bypass user restriction of an op: b/240617242. COARSE_LOCATION and FINE_LOCATION are
+    //    the only two ops that are impacted.
+    private static final SparseBooleanArray OPS_WITHOUT_CACHING = new SparseBooleanArray();
+    static {
+        OPS_WITHOUT_CACHING.put(OP_COARSE_LOCATION, true);
+        OPS_WITHOUT_CACHING.put(OP_FINE_LOCATION, true);
+    }
+
+    private static boolean isAppOpModeCachingEnabled(int opCode) {
+        if (!Flags.appopModeCachingEnabled()) {
+            return false;
+        }
+        return !OPS_WITHOUT_CACHING.get(opCode, false);
+    }
+
+    /**
+     * @hide
+     */
+    public static void invalidateAppOpModeCache() {
+        if (Flags.appopModeCachingEnabled()) {
+            IpcDataCache.invalidateCache(IpcDataCache.MODULE_SYSTEM, APP_OP_MODE_CACHING_API);
+        }
+    }
+
+    /**
+     * Bypass AppOpModeCache in the local process
+     *
+     * @hide
+     */
+    public static void disableAppOpModeCache() {
+        if (Flags.appopModeCachingEnabled()) {
+            sAppOpModeCache.disableLocal();
+        }
+    }
+
+    private static final class AppOpModeQuery {
+        final int op;
+        final int uid;
+        final String packageName;
+        final int virtualDeviceId;
+        final String attributionTag;
+        final String methodName;
+
+        AppOpModeQuery(int op, int uid, @Nullable String packageName, int virtualDeviceId,
+                @Nullable String attributionTag, @Nullable String methodName) {
+            this.op = op;
+            this.uid = uid;
+            this.packageName = packageName;
+            this.virtualDeviceId = virtualDeviceId;
+            this.attributionTag = attributionTag;
+            this.methodName = methodName;
+        }
+
+        @Override
+        public String toString() {
+            return TextUtils.formatSimple("AppOpModeQuery(op=%d, uid=%d, packageName=%s, "
+                            + "virtualDeviceId=%d, attributionTag=%s, methodName=%s", op, uid,
+                    packageName, virtualDeviceId, attributionTag, methodName);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(op, uid, packageName, virtualDeviceId, attributionTag);
+        }
+
+        @Override
+        public boolean equals(@Nullable Object o) {
+            if (this == o) return true;
+            if (o == null) return false;
+            if (this.getClass() != o.getClass()) return false;
+
+            AppOpModeQuery other = (AppOpModeQuery) o;
+            return op == other.op && uid == other.uid && Objects.equals(packageName,
+                    other.packageName) && virtualDeviceId == other.virtualDeviceId
+                    && Objects.equals(attributionTag, other.attributionTag);
+        }
+    }
+
     AppOpsManager(Context context, IAppOpsService service) {
         mContext = context;
         mService = service;
@@ -8861,12 +8982,16 @@
     private int unsafeCheckOpRawNoThrow(int op, int uid, @NonNull String packageName,
             int virtualDeviceId) {
         try {
-            if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) {
-                return mService.checkOperationRaw(op, uid, packageName, null);
+            int mode;
+            if (isAppOpModeCachingEnabled(op)) {
+                mode = sAppOpModeCache.query(
+                        new AppOpModeQuery(op, uid, packageName, virtualDeviceId, null,
+                                "unsafeCheckOpRawNoThrow"));
             } else {
-                return mService.checkOperationRawForDevice(
+                mode = mService.checkOperationRawForDevice(
                         op, uid, packageName, null, virtualDeviceId);
             }
+            return mode;
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -9051,7 +9176,7 @@
             SyncNotedAppOp syncOp;
             if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) {
                 syncOp = mService.noteOperation(op, uid, packageName, attributionTag,
-                    collectionMode == COLLECT_ASYNC, message, shouldCollectMessage);
+                        collectionMode == COLLECT_ASYNC, message, shouldCollectMessage);
             } else {
                 syncOp = mService.noteOperationForDevice(op, uid, packageName, attributionTag,
                     virtualDeviceId, collectionMode == COLLECT_ASYNC, message,
@@ -9294,8 +9419,21 @@
     @UnsupportedAppUsage
     public int checkOp(int op, int uid, String packageName) {
         try {
-            int mode = mService.checkOperationForDevice(op, uid, packageName,
-                Context.DEVICE_ID_DEFAULT);
+            int mode;
+            if (isAppOpModeCachingEnabled(op)) {
+                mode = sAppOpModeCache.query(
+                        new AppOpModeQuery(op, uid, packageName, Context.DEVICE_ID_DEFAULT, null,
+                                "checkOp"));
+                if (mode == MODE_FOREGROUND) {
+                    // We only cache raw mode. If the mode is FOREGROUND, we need another binder
+                    // call to fetch translated value based on the process state.
+                    mode = mService.checkOperationForDevice(op, uid, packageName,
+                            Context.DEVICE_ID_DEFAULT);
+                }
+            } else {
+                mode = mService.checkOperationForDevice(op, uid, packageName,
+                        Context.DEVICE_ID_DEFAULT);
+            }
             if (mode == MODE_ERRORED) {
                 throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName));
             }
@@ -9334,13 +9472,19 @@
     private int checkOpNoThrow(int op, int uid, String packageName, int virtualDeviceId) {
         try {
             int mode;
-            if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) {
-                mode = mService.checkOperation(op, uid, packageName);
+            if (isAppOpModeCachingEnabled(op)) {
+                mode = sAppOpModeCache.query(
+                        new AppOpModeQuery(op, uid, packageName, virtualDeviceId, null,
+                                "checkOpNoThrow"));
+                if (mode == MODE_FOREGROUND) {
+                    // We only cache raw mode. If the mode is FOREGROUND, we need another binder
+                    // call to fetch translated value based on the process state.
+                    mode = mService.checkOperationForDevice(op, uid, packageName, virtualDeviceId);
+                }
             } else {
                 mode = mService.checkOperationForDevice(op, uid, packageName, virtualDeviceId);
             }
-
-            return mode == AppOpsManager.MODE_FOREGROUND ? AppOpsManager.MODE_ALLOWED : mode;
+            return mode;
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/ForegroundServiceTypePolicy.java b/core/java/android/app/ForegroundServiceTypePolicy.java
index d1e517b..16444dc 100644
--- a/core/java/android/app/ForegroundServiceTypePolicy.java
+++ b/core/java/android/app/ForegroundServiceTypePolicy.java
@@ -398,6 +398,7 @@
                 new RegularPermission(Manifest.permission.NFC),
                 new RegularPermission(Manifest.permission.TRANSMIT_IR),
                 new RegularPermission(Manifest.permission.UWB_RANGING),
+                new RegularPermission(Manifest.permission.RANGING),
                 new UsbDevicePermission(),
                 new UsbAccessoryPermission(),
             }, false),
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index 038dcdb..f432a22 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -717,12 +717,10 @@
         // The shared memory.
         private volatile NonceStore mStore;
 
-        // The index of the nonce in shared memory.
+        // The index of the nonce in shared memory.  This changes from INVALID only when the local
+        // object is completely initialized.
         private volatile int mHandle = NonceStore.INVALID_NONCE_INDEX;
 
-        // True if the string has been stored, ever.
-        private volatile boolean mRecorded = false;
-
         // A short name that is saved in shared memory.  This is the portion of the property name
         // that follows the prefix.
         private final String mShortName;
@@ -736,48 +734,63 @@
             }
         }
 
+        // Initialize the mStore and mHandle variables.  This function does nothing if the
+        // variables are already initialized.  Synchronization ensures that initialization happens
+        // no more than once.  The function returns the new value of mHandle.
+        //
+        // If the "update" boolean is true, then the property is registered with the nonce store
+        // before the associated handle is fetched.
+        private int initialize(boolean update) {
+            synchronized (mLock) {
+                int handle = mHandle;
+                if (handle == NonceStore.INVALID_NONCE_INDEX) {
+                    if (mStore == null) {
+                        mStore = NonceStore.getInstance();
+                        if (mStore == null) {
+                            return NonceStore.INVALID_NONCE_INDEX;
+                        }
+                    }
+                    if (update) {
+                        mStore.storeName(mShortName);
+                    }
+                    handle = mStore.getHandleForName(mShortName);
+                    if (handle == NonceStore.INVALID_NONCE_INDEX) {
+                        return NonceStore.INVALID_NONCE_INDEX;
+                    }
+                    // The handle must be valid.
+                    mHandle = handle;
+                }
+                return handle;
+            }
+        }
+
         // Fetch the nonce from shared memory.  If the shared memory is not available, return
         // UNSET.  If the shared memory is available but the nonce name is not known (it may not
         // have been invalidated by the server yet), return UNSET.
         @Override
         long getNonceInternal() {
-            if (mHandle == NonceStore.INVALID_NONCE_INDEX) {
-                if (mStore == null) {
-                    mStore = NonceStore.getInstance();
-                    if (mStore == null) {
-                        return NONCE_UNSET;
-                    }
-                }
-                mHandle = mStore.getHandleForName(mShortName);
-                if (mHandle == NonceStore.INVALID_NONCE_INDEX) {
+            int handle = mHandle;
+            if (handle == NonceStore.INVALID_NONCE_INDEX) {
+                handle = initialize(false);
+                if (handle == NonceStore.INVALID_NONCE_INDEX) {
                     return NONCE_UNSET;
                 }
             }
-            return mStore.getNonce(mHandle);
+            return mStore.getNonce(handle);
         }
 
-        // Set the nonce in shared mmory.  If the shared memory is not available, throw an
-        // exception.  Otherwise, if the nonce name has never been recorded, record it now and
-        // fetch the handle for the name.  If the handle cannot be created, throw an exception.
+        // Set the nonce in shared memory.  If the shared memory is not available or if the nonce
+        // cannot be registered in shared memory, throw an exception.
         @Override
         void setNonceInternal(long value) {
-            if (mHandle == NonceStore.INVALID_NONCE_INDEX || !mRecorded) {
-                if (mStore == null) {
-                    mStore = NonceStore.getInstance();
-                    if (mStore == null) {
-                        throw new IllegalStateException("setNonce: shared memory not ready");
-                    }
-                }
-                // Always store the name before fetching the handle.  storeName() is idempotent
-                // but does take a little time, so this code calls it just once.
-                mStore.storeName(mShortName);
-                mRecorded = true;
-                mHandle = mStore.getHandleForName(mShortName);
-                if (mHandle == NonceStore.INVALID_NONCE_INDEX) {
-                    throw new IllegalStateException("setNonce: shared memory store failed");
+            int handle = mHandle;
+            if (handle == NonceStore.INVALID_NONCE_INDEX) {
+                handle = initialize(true);
+                if (handle == NonceStore.INVALID_NONCE_INDEX) {
+                    throw new IllegalStateException("unable to assign nonce handle: " + mName);
                 }
             }
-            mStore.setNonce(mHandle, value);
+            mStore.setNonce(handle, value);
         }
     }
 
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 63e03914..b7285c3 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -602,6 +602,15 @@
     @LoggingOnly
     private static final long MEDIA_CONTROL_BLANK_TITLE = 274775190L;
 
+    /**
+     * Media controls based on {@link android.app.Notification.MediaStyle} notifications will have
+     * actions from the associated {@link androidx.media3.MediaController}, if available.
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT)
+    // TODO(b/360196209): Set target SDK to Baklava once available
+    private static final long MEDIA_CONTROL_MEDIA3_ACTIONS = 360196209L;
+
     @UnsupportedAppUsage
     private Context mContext;
     private IStatusBarService mService;
@@ -1270,6 +1279,21 @@
     }
 
     /**
+     * Checks whether the media controls for a given package should use a Media3 controller
+     *
+     * @param packageName App posting media controls
+     * @param user Current user handle
+     * @return true if Media3 should be used
+     *
+     * @hide
+     */
+    @RequiresPermission(allOf = {android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG,
+            android.Manifest.permission.LOG_COMPAT_CHANGE})
+    public static boolean useMedia3ControllerForApp(String packageName, UserHandle user) {
+        return CompatChanges.isChangeEnabled(MEDIA_CONTROL_MEDIA3_ACTIONS, packageName, user);
+    }
+
+    /**
      * Checks whether the supplied activity can {@link Activity#startActivityForResult(Intent, int)}
      * a system activity that captures content on the screen to take a screenshot.
      *
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 0a4d8f2..aac963a 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -296,6 +296,12 @@
     public boolean isVisibleRequested;
 
     /**
+     * Whether the top activity is to be displayed. See {@link android.R.attr#windowNoDisplay}.
+     * @hide
+     */
+    public boolean isTopActivityNoDisplay;
+
+    /**
      * Whether this task is sleeping due to sleeping display.
      * @hide
      */
@@ -308,12 +314,6 @@
     public boolean isTopActivityTransparent;
 
     /**
-     * Whether the top activity has specified style floating.
-     * @hide
-     */
-    public boolean isTopActivityStyleFloating;
-
-    /**
      * The last non-fullscreen bounds the task was launched in or resized to.
      * @hide
      */
@@ -364,8 +364,9 @@
         // Do nothing
     }
 
-    private TaskInfo(Parcel source) {
-        readFromParcel(source);
+    /** @hide */
+    public TaskInfo(Parcel source) {
+        readTaskFromParcel(source);
     }
 
     /**
@@ -482,13 +483,13 @@
                 && isFocused == that.isFocused
                 && isVisible == that.isVisible
                 && isVisibleRequested == that.isVisibleRequested
+                && isTopActivityNoDisplay == that.isTopActivityNoDisplay
                 && isSleeping == that.isSleeping
                 && Objects.equals(mTopActivityLocusId, that.mTopActivityLocusId)
                 && parentTaskId == that.parentTaskId
                 && Objects.equals(topActivity, that.topActivity)
                 && isTopActivityTransparent == that.isTopActivityTransparent
-                && isTopActivityStyleFloating == that.isTopActivityStyleFloating
-                && lastNonFullscreenBounds == this.lastNonFullscreenBounds
+                && Objects.equals(lastNonFullscreenBounds, that.lastNonFullscreenBounds)
                 && Objects.equals(capturedLink, that.capturedLink)
                 && capturedLinkTimestamp == that.capturedLinkTimestamp
                 && requestedVisibleTypes == that.requestedVisibleTypes
@@ -524,7 +525,7 @@
     /**
      * Reads the TaskInfo from a parcel.
      */
-    void readFromParcel(Parcel source) {
+    void readTaskFromParcel(Parcel source) {
         userId = source.readInt();
         taskId = source.readInt();
         effectiveUid = source.readInt();
@@ -561,11 +562,11 @@
         isFocused = source.readBoolean();
         isVisible = source.readBoolean();
         isVisibleRequested = source.readBoolean();
+        isTopActivityNoDisplay = source.readBoolean();
         isSleeping = source.readBoolean();
         mTopActivityLocusId = source.readTypedObject(LocusId.CREATOR);
         displayAreaFeatureId = source.readInt();
         isTopActivityTransparent = source.readBoolean();
-        isTopActivityStyleFloating = source.readBoolean();
         lastNonFullscreenBounds = source.readTypedObject(Rect.CREATOR);
         capturedLink = source.readTypedObject(Uri.CREATOR);
         capturedLinkTimestamp = source.readLong();
@@ -577,8 +578,9 @@
 
     /**
      * Writes the TaskInfo to a parcel.
+     * @hide
      */
-    void writeToParcel(Parcel dest, int flags) {
+    public void writeTaskToParcel(Parcel dest, int flags) {
         dest.writeInt(userId);
         dest.writeInt(taskId);
         dest.writeInt(effectiveUid);
@@ -616,11 +618,11 @@
         dest.writeBoolean(isFocused);
         dest.writeBoolean(isVisible);
         dest.writeBoolean(isVisibleRequested);
+        dest.writeBoolean(isTopActivityNoDisplay);
         dest.writeBoolean(isSleeping);
         dest.writeTypedObject(mTopActivityLocusId, flags);
         dest.writeInt(displayAreaFeatureId);
         dest.writeBoolean(isTopActivityTransparent);
-        dest.writeBoolean(isTopActivityStyleFloating);
         dest.writeTypedObject(lastNonFullscreenBounds, flags);
         dest.writeTypedObject(capturedLink, flags);
         dest.writeLong(capturedLinkTimestamp);
@@ -661,11 +663,11 @@
                 + " isFocused=" + isFocused
                 + " isVisible=" + isVisible
                 + " isVisibleRequested=" + isVisibleRequested
+                + " isTopActivityNoDisplay=" + isTopActivityNoDisplay
                 + " isSleeping=" + isSleeping
                 + " locusId=" + mTopActivityLocusId
                 + " displayAreaFeatureId=" + displayAreaFeatureId
                 + " isTopActivityTransparent=" + isTopActivityTransparent
-                + " isTopActivityStyleFloating=" + isTopActivityStyleFloating
                 + " lastNonFullscreenBounds=" + lastNonFullscreenBounds
                 + " capturedLink=" + capturedLink
                 + " capturedLinkTimestamp=" + capturedLinkTimestamp
diff --git a/core/java/android/app/admin/DevicePolicyIdentifiers.java b/core/java/android/app/admin/DevicePolicyIdentifiers.java
index c0e435c..35149b5 100644
--- a/core/java/android/app/admin/DevicePolicyIdentifiers.java
+++ b/core/java/android/app/admin/DevicePolicyIdentifiers.java
@@ -191,6 +191,12 @@
     public static final String PASSWORD_COMPLEXITY_POLICY = "passwordComplexity";
 
     /**
+     * String identifier for {@link DevicePolicyManager#setMtePolicy(int)}.
+     */
+    @FlaggedApi(android.app.admin.flags.Flags.FLAG_SET_MTE_POLICY_COEXISTENCE)
+    public static final String MEMORY_TAGGING_POLICY = "memoryTagging";
+
+    /**
      * @hide
      */
     public static final String USER_RESTRICTION_PREFIX = "userRestriction_";
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 3aaca25..04a9d13 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -276,6 +276,16 @@
 }
 
 flag {
+    name: "suspend_packages_coexistence"
+    namespace: "enterprise"
+    description: "Migrate setPackagesSuspended for unmanaged mode"
+    bug: "335624297"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "backup_connected_apps_settings"
     namespace: "enterprise"
     description: "backup and restore connected work and personal apps user settings across devices"
diff --git a/core/java/android/app/jank/JankTracker.java b/core/java/android/app/jank/JankTracker.java
new file mode 100644
index 0000000..df422e0
--- /dev/null
+++ b/core/java/android/app/jank/JankTracker.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.jank;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.view.AttachedSurfaceControl;
+import android.view.Choreographer;
+import android.view.SurfaceControl;
+import android.view.View;
+import android.view.ViewTreeObserver;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+
+/**
+ * This class is responsible for registering callbacks that will receive JankData batches.
+ * It handles managing the background thread that JankData will be processed on. As well as acting
+ * as an intermediary between widgets and the state tracker, routing state changes to the tracker.
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+public class JankTracker {
+
+    // Tracks states reported by widgets.
+    private StateTracker mStateTracker;
+    // Processes JankData batches and associates frames to widget states.
+    private JankDataProcessor mJankDataProcessor;
+
+    // Background thread responsible for processing JankData batches.
+    private HandlerThread mHandlerThread = new HandlerThread("AppJankTracker");
+    private Handler mHandler = null;
+
+    // Needed so we know when the view is attached to a window.
+    private ViewTreeObserver mViewTreeObserver;
+
+    // Handle to a registered OnJankData listener.
+    private SurfaceControl.OnJankDataListenerRegistration mJankDataListenerRegistration;
+
+    // The interface to the windowing system that enables us to register for JankData.
+    private AttachedSurfaceControl mSurfaceControl;
+    // Name of the activity that is currently tracking Jank metrics.
+    private String mActivityName;
+    // The apps uid.
+    private int mAppUid;
+    // View that gives us access to ViewTreeObserver.
+    private View mDecorView;
+
+    /**
+     * Set by the activity to enable or disable jank tracking. Activities may disable tracking if
+     * they are paused or not enable tracking if they are not visible or if the app category is not
+     * set.
+     */
+    private boolean mTrackingEnabled = false;
+    /**
+     * Set to true once listeners are registered and JankData will start to be received. Both
+     * mTrackingEnabled and mListenersRegistered need to be true for JankData to be processed.
+     */
+    private boolean mListenersRegistered = false;
+
+
+    public JankTracker(Choreographer choreographer, View decorView) {
+        mStateTracker = new StateTracker(choreographer);
+        mJankDataProcessor = new JankDataProcessor(mStateTracker);
+        mDecorView = decorView;
+        mHandlerThread.start();
+        registerWindowListeners();
+    }
+
+    public void setActivityName(@NonNull String activityName) {
+        mActivityName = activityName;
+    }
+
+    public void setAppUid(int uid) {
+        mAppUid = uid;
+    }
+
+    /**
+     * Will add the widget category, id and state as a UI state to associate frames to it.
+     * @param widgetCategory preselected general widget category
+     * @param widgetId developer defined widget id if available.
+     * @param widgetState the current active widget state.
+     */
+    public void addUiState(String widgetCategory, String widgetId, String widgetState) {
+        if (!shouldTrack()) return;
+
+        mStateTracker.putState(widgetCategory, widgetId, widgetState);
+    }
+
+    /**
+     * Will remove the widget category, id and state as a ui state and no longer attribute frames
+     * to it.
+     * @param widgetCategory preselected general widget category
+     * @param widgetId developer defined widget id if available.
+     * @param widgetState no longer active widget state.
+     */
+    public void removeUiState(String widgetCategory, String widgetId, String widgetState) {
+        if (!shouldTrack()) return;
+
+        mStateTracker.removeState(widgetCategory, widgetId, widgetState);
+    }
+
+    /**
+     * Call to update a jank state to a different state.
+     * @param widgetCategory preselected general widget category.
+     * @param widgetId developer defined widget id if available.
+     * @param currentState current state of the widget.
+     * @param nextState the state the widget will be in.
+     */
+    public void updateUiState(String widgetCategory, String widgetId, String currentState,
+            String nextState) {
+        if (!shouldTrack()) return;
+
+        mStateTracker.updateState(widgetCategory, widgetId, currentState, nextState);
+    }
+
+    /**
+     * Will enable jank tracking, and add the activity as a state to associate frames to.
+     */
+    public void enableAppJankTracking() {
+        // Add the activity as a state, this will ensure we track frames to the activity without the
+        // need of a decorated widget to be used.
+        // TODO b/376116199 replace "NONE" with UNSPECIFIED once the API changes are merged.
+        mStateTracker.putState("NONE", mActivityName, "NONE");
+        mTrackingEnabled = true;
+    }
+
+    /**
+     * Will disable jank tracking, and remove the activity as a state to associate frames to.
+     */
+    public void disableAppJankTracking() {
+        mTrackingEnabled = false;
+        // TODO b/376116199 replace "NONE" with UNSPECIFIED once the API changes are merged.
+        mStateTracker.removeState("NONE", mActivityName, "NONE");
+    }
+
+    /**
+     * Retrieve all pending widget states, this is intended for testing purposes only.
+     * @param stateDataList the ArrayList that will be populated with the pending states.
+     */
+    @VisibleForTesting
+    public void getAllUiStates(@NonNull ArrayList<StateTracker.StateData> stateDataList) {
+        mStateTracker.retrieveAllStates(stateDataList);
+    }
+
+    /**
+     * Only intended to be used by tests, the runnable that registers the listeners may not run
+     * in time for tests to pass. This forces them to run immediately.
+     */
+    @VisibleForTesting
+    public void forceListenerRegistration() {
+        mSurfaceControl = mDecorView.getRootSurfaceControl();
+        registerForJankData();
+        // TODO b/376116199 Check if registration is good.
+        mListenersRegistered = true;
+    }
+
+    private void registerForJankData() {
+        if (mSurfaceControl == null) return;
+        /*
+        TODO b/376115668 Register for JankData batches from new JankTracking API
+         */
+    }
+
+    private boolean shouldTrack() {
+        return mTrackingEnabled && mListenersRegistered;
+    }
+
+    /**
+     * Need to know when the decor view gets attached to the window in order to get
+     * AttachedSurfaceControl. In order to register a callback for OnJankDataListener
+     * AttachedSurfaceControl needs to be created which only happens after onWindowAttached is
+     * called. This is why there is a delay in posting the runnable.
+     */
+    private void registerWindowListeners() {
+        if (mDecorView == null) return;
+        mViewTreeObserver = mDecorView.getViewTreeObserver();
+        mViewTreeObserver.addOnWindowAttachListener(new ViewTreeObserver.OnWindowAttachListener() {
+            @Override
+            public void onWindowAttached() {
+                getHandler().postDelayed(new Runnable() {
+                    @Override
+                    public void run() {
+                        forceListenerRegistration();
+                    }
+                }, 1000);
+            }
+
+            @Override
+            public void onWindowDetached() {
+                // TODO b/376116199  do we un-register the callback or just not process the data.
+            }
+        });
+    }
+
+    private Handler getHandler() {
+        if (mHandler == null) {
+            mHandler = new Handler(mHandlerThread.getLooper());
+        }
+        return mHandler;
+    }
+}
diff --git a/core/java/android/app/wallpaper/WallpaperDescription.java b/core/java/android/app/wallpaper/WallpaperDescription.java
index c3d6340..8ffda72 100644
--- a/core/java/android/app/wallpaper/WallpaperDescription.java
+++ b/core/java/android/app/wallpaper/WallpaperDescription.java
@@ -18,8 +18,6 @@
 
 import static android.app.Flags.FLAG_LIVE_WALLPAPER_CONTENT_HANDLING;
 
-import static java.nio.charset.StandardCharsets.UTF_8;
-
 import android.annotation.FlaggedApi;
 import android.app.WallpaperInfo;
 import android.content.ComponentName;
@@ -31,7 +29,6 @@
 import android.text.Spanned;
 import android.text.SpannedString;
 import android.util.Log;
-import android.util.Xml;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -43,8 +40,6 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -154,46 +149,6 @@
         return Objects.hash(mComponent, mId);
     }
 
-    ////// Stream read/write
-
-    /**
-     * Writes the content of the {@link WallpaperDescription} to a {@link OutputStream}.
-     *
-     * <p>The content can be read by {@link #readFromStream}. This method is intended for use by
-     * trusted apps only, and the format is not guaranteed to be stable.</p>
-     */
-    public void writeToStream(@NonNull OutputStream outputStream) throws IOException {
-        TypedXmlSerializer serializer = Xml.newFastSerializer();
-        serializer.setOutput(outputStream, UTF_8.name());
-        serializer.startTag(null, "description");
-        try {
-            saveToXml(serializer);
-        } catch (XmlPullParserException e) {
-            throw new IOException(e);
-        }
-        serializer.endTag(null, "description");
-        serializer.flush();
-    }
-
-    /**
-     * Reads a {@link PersistableBundle} from an {@link InputStream}.
-     *
-     * <p>The stream must be generated by {@link #writeToStream}. This method is intended for use by
-     * trusted apps only, and the format is not guaranteed to be stable.</p>
-     */
-    @NonNull
-    public static WallpaperDescription readFromStream(@NonNull InputStream inputStream)
-            throws IOException {
-        try {
-            TypedXmlPullParser parser = Xml.newFastPullParser();
-            parser.setInput(inputStream, UTF_8.name());
-            parser.next();
-            return WallpaperDescription.restoreFromXml(parser);
-        } catch (XmlPullParserException e) {
-            throw new IOException(e);
-        }
-    }
-
     ////// XML storage
 
     /** @hide */
diff --git a/core/java/android/appwidget/flags.aconfig b/core/java/android/appwidget/flags.aconfig
index 940a4d2..ce51576 100644
--- a/core/java/android/appwidget/flags.aconfig
+++ b/core/java/android/appwidget/flags.aconfig
@@ -55,7 +55,7 @@
   name: "remote_views_proto"
   namespace: "app_widgets"
   description: "Enable support for persisting RemoteViews previews to Protobuf"
-  bug: "306546610"
+  bug: "364420494"
 }
 
 flag {
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index 65f9cbe..2be27da 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -160,7 +160,7 @@
      */
     @IntDef(prefix = "POLICY_TYPE_", value = {POLICY_TYPE_SENSORS, POLICY_TYPE_AUDIO,
             POLICY_TYPE_RECENTS, POLICY_TYPE_ACTIVITY, POLICY_TYPE_CLIPBOARD, POLICY_TYPE_CAMERA,
-            POLICY_TYPE_BLOCKED_ACTIVITY})
+            POLICY_TYPE_BLOCKED_ACTIVITY, POLICY_TYPE_DEFAULT_DEVICE_CAMERA_ACCESS})
     @Retention(RetentionPolicy.SOURCE)
     @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
     public @interface PolicyType {}
@@ -301,6 +301,21 @@
     @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_ACTIVITY_CONTROL_API)
     public static final int POLICY_TYPE_BLOCKED_ACTIVITY = 6;
 
+    /**
+     * Tells the virtual device framework how to handle camera access of the default device by apps
+     * running on the virtual device.
+     *
+     * <ul>
+     *     <li>{@link #DEVICE_POLICY_DEFAULT}: Default device camera access will be allowed.
+     *     <li>{@link #DEVICE_POLICY_CUSTOM}: Default device camera access will be blocked.
+     * </ul>
+     *
+     * @see Context#DEVICE_ID_DEFAULT
+     */
+    @FlaggedApi(android.companion.virtualdevice.flags.Flags
+            .FLAG_DEFAULT_DEVICE_CAMERA_ACCESS_POLICY)
+    public static final int POLICY_TYPE_DEFAULT_DEVICE_CAMERA_ACCESS = 7;
+
     private final int mLockState;
     @NonNull private final ArraySet<UserHandle> mUsersWithMatchingAccounts;
     @NavigationPolicy
@@ -318,6 +333,8 @@
     @Nullable private final IVirtualSensorCallback mVirtualSensorCallback;
     private final int mAudioPlaybackSessionId;
     private final int mAudioRecordingSessionId;
+    private final long mDimDuration;
+    private final long mScreenOffTimeout;
 
     private VirtualDeviceParams(
             @LockState int lockState,
@@ -333,7 +350,9 @@
             @NonNull List<VirtualSensorConfig> virtualSensorConfigs,
             @Nullable IVirtualSensorCallback virtualSensorCallback,
             int audioPlaybackSessionId,
-            int audioRecordingSessionId) {
+            int audioRecordingSessionId,
+            long dimDuration,
+            long screenOffTimeout) {
         mLockState = lockState;
         mUsersWithMatchingAccounts =
                 new ArraySet<>(Objects.requireNonNull(usersWithMatchingAccounts));
@@ -351,6 +370,8 @@
         mVirtualSensorCallback = virtualSensorCallback;
         mAudioPlaybackSessionId = audioPlaybackSessionId;
         mAudioRecordingSessionId = audioRecordingSessionId;
+        mDimDuration = dimDuration;
+        mScreenOffTimeout = screenOffTimeout;
     }
 
     @SuppressWarnings("unchecked")
@@ -371,6 +392,8 @@
         mAudioRecordingSessionId = parcel.readInt();
         mHomeComponent = parcel.readTypedObject(ComponentName.CREATOR);
         mInputMethodComponent = parcel.readTypedObject(ComponentName.CREATOR);
+        mDimDuration = parcel.readLong();
+        mScreenOffTimeout = parcel.readLong();
     }
 
     /**
@@ -382,6 +405,26 @@
     }
 
     /**
+     * Returns the dim duration for the displays of this device.
+     *
+     * @see Builder#setDimDuration(Duration)
+     */
+    @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+    public @NonNull Duration getDimDuration() {
+        return Duration.ofMillis(mDimDuration);
+    }
+
+    /**
+     * Returns the screen off timeout of the displays of this device.
+     *
+     * @see Builder#setDimDuration(Duration)
+     */
+    @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+    public @NonNull Duration getScreenOffTimeout() {
+        return Duration.ofMillis(mScreenOffTimeout);
+    }
+
+    /**
      * Returns the custom component used as home on all displays owned by this virtual device that
      * support home activities.
      *
@@ -604,6 +647,8 @@
         dest.writeInt(mAudioRecordingSessionId);
         dest.writeTypedObject(mHomeComponent, flags);
         dest.writeTypedObject(mInputMethodComponent, flags);
+        dest.writeLong(mDimDuration);
+        dest.writeLong(mScreenOffTimeout);
     }
 
     @Override
@@ -638,7 +683,9 @@
                 && Objects.equals(mHomeComponent, that.mHomeComponent)
                 && Objects.equals(mInputMethodComponent, that.mInputMethodComponent)
                 && mAudioPlaybackSessionId == that.mAudioPlaybackSessionId
-                && mAudioRecordingSessionId == that.mAudioRecordingSessionId;
+                && mAudioRecordingSessionId == that.mAudioRecordingSessionId
+                && mDimDuration == that.mDimDuration
+                && mScreenOffTimeout == that.mScreenOffTimeout;
     }
 
     @Override
@@ -647,7 +694,7 @@
                 mLockState, mUsersWithMatchingAccounts, mCrossTaskNavigationExemptions,
                 mDefaultNavigationPolicy, mActivityPolicyExemptions, mDefaultActivityPolicy, mName,
                 mDevicePolicies, mHomeComponent, mInputMethodComponent, mAudioPlaybackSessionId,
-                mAudioRecordingSessionId);
+                mAudioRecordingSessionId, mDimDuration, mScreenOffTimeout);
         for (int i = 0; i < mDevicePolicies.size(); i++) {
             hashCode = 31 * hashCode + mDevicePolicies.keyAt(i);
             hashCode = 31 * hashCode + mDevicePolicies.valueAt(i);
@@ -671,6 +718,8 @@
                 + " mInputMethodComponent=" + mInputMethodComponent
                 + " mAudioPlaybackSessionId=" + mAudioPlaybackSessionId
                 + " mAudioRecordingSessionId=" + mAudioRecordingSessionId
+                + " mDimDuration=" + mDimDuration
+                + " mScreenOffTimeout=" + mScreenOffTimeout
                 + ")";
     }
 
@@ -692,11 +741,13 @@
         pw.println(prefix + "mInputMethodComponent=" + mInputMethodComponent);
         pw.println(prefix + "mAudioPlaybackSessionId=" + mAudioPlaybackSessionId);
         pw.println(prefix + "mAudioRecordingSessionId=" + mAudioRecordingSessionId);
+        pw.println(prefix + "mDimDuration=" + mDimDuration);
+        pw.println(prefix + "mScreenOffTimeout=" + mScreenOffTimeout);
     }
 
     @NonNull
     public static final Parcelable.Creator<VirtualDeviceParams> CREATOR =
-            new Parcelable.Creator<VirtualDeviceParams>() {
+            new Parcelable.Creator<>() {
                 public VirtualDeviceParams createFromParcel(Parcel in) {
                     return new VirtualDeviceParams(in);
                 }
@@ -711,6 +762,8 @@
      */
     public static final class Builder {
 
+        private static final Duration INFINITE_TIMEOUT = Duration.ofDays(365 * 1000);
+
         private @LockState int mLockState = LOCK_STATE_DEFAULT;
         @NonNull private Set<UserHandle> mUsersWithMatchingAccounts = Collections.emptySet();
         @NonNull private Set<ComponentName> mCrossTaskNavigationExemptions = Collections.emptySet();
@@ -733,6 +786,8 @@
         @Nullable private VirtualSensorDirectChannelCallback mVirtualSensorDirectChannelCallback;
         @Nullable private ComponentName mHomeComponent;
         @Nullable private ComponentName mInputMethodComponent;
+        private Duration mDimDuration = Duration.ZERO;
+        private Duration mScreenOffTimeout = Duration.ZERO;
 
         private static class VirtualSensorCallbackDelegate extends IVirtualSensorCallback.Stub {
             @NonNull
@@ -810,6 +865,57 @@
         }
 
         /**
+         * Sets the dim duration for all trusted non-mirror displays of the device.
+         *
+         * <p>The system will reduce the display brightness for the specified duration if there
+         * has been no interaction just before the displays turn off.</p>
+         *
+         * <p>If set, the screen off timeout must also be set to a value larger than the dim
+         * duration. If left unset or set to zero, then the display brightness will not be reduced.
+         * </p>
+         *
+         * @throws IllegalArgumentException if the dim duration is negative or if the dim duration
+         *   is longer than the screen off timeout.
+         * @see android.hardware.display.DisplayManager#VIRTUAL_DISPLAY_FLAG_TRUSTED
+         * @see android.hardware.display.DisplayManager#VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR
+         * @see #setScreenOffTimeout
+         */
+        @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+        @NonNull
+        public Builder setDimDuration(@NonNull Duration dimDuration) {
+            if (Objects.requireNonNull(dimDuration).compareTo(Duration.ZERO) < 0) {
+                throw new IllegalArgumentException("The dim duration cannot be negative");
+            }
+            mDimDuration = dimDuration;
+            return this;
+        }
+
+        /**
+         * Sets the timeout, after which all trusted non-mirror displays of the device will turn
+         * off, if there has been no interaction with the device.
+         *
+         * <p>If dim duration is set, the screen off timeout must be set to a value larger than the
+         * dim duration. If left unset or set to zero, then the displays will never be turned off
+         * due to inactivity.</p>
+         *
+         * @throws IllegalArgumentException if the screen off timeout is negative or if the dim
+         *   duration is longer than the screen off timeout.
+         * @see android.hardware.display.DisplayManager#VIRTUAL_DISPLAY_FLAG_TRUSTED
+         * @see android.hardware.display.DisplayManager#VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR
+         * @see #setDimDuration
+         * @see VirtualDeviceManager.VirtualDevice#goToSleep()
+         */
+        @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+        @NonNull
+        public Builder setScreenOffTimeout(@NonNull Duration screenOffTimeout) {
+            if (Objects.requireNonNull(screenOffTimeout).compareTo(Duration.ZERO) < 0) {
+                throw new IllegalArgumentException("The screen off timeout cannot be negative");
+            }
+            mScreenOffTimeout = screenOffTimeout;
+            return this;
+        }
+
+        /**
          * Specifies a component to be used as home on all displays owned by this virtual device
          * that support home activities.
          * *
@@ -1205,6 +1311,14 @@
                 }
             }
 
+            if (mDimDuration.compareTo(mScreenOffTimeout) > 0) {
+                throw new IllegalArgumentException(
+                        "The dim duration cannot be greater than the screen off timeout.");
+            }
+            if (mScreenOffTimeout.compareTo(Duration.ZERO) == 0) {
+                mScreenOffTimeout = INFINITE_TIMEOUT;
+            }
+
             if (!Flags.crossDeviceClipboard()) {
                 mDevicePolicies.delete(POLICY_TYPE_CLIPBOARD);
             }
@@ -1213,6 +1327,10 @@
                 mDevicePolicies.delete(POLICY_TYPE_CAMERA);
             }
 
+            if (!android.companion.virtualdevice.flags.Flags.defaultDeviceCameraAccessPolicy()) {
+                mDevicePolicies.delete(POLICY_TYPE_DEFAULT_DEVICE_CAMERA_ACCESS);
+            }
+
             if (!android.companion.virtualdevice.flags.Flags.activityControlApi()) {
                 mDevicePolicies.delete(POLICY_TYPE_BLOCKED_ACTIVITY);
             }
@@ -1250,7 +1368,9 @@
                     mVirtualSensorConfigs,
                     virtualSensorCallbackDelegate,
                     mAudioPlaybackSessionId,
-                    mAudioRecordingSessionId);
+                    mAudioRecordingSessionId,
+                    mDimDuration.toMillis(),
+                    mScreenOffTimeout.toMillis());
         }
     }
 }
diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags.aconfig
index fc9c94d..3e6919b 100644
--- a/core/java/android/companion/virtual/flags.aconfig
+++ b/core/java/android/companion/virtual/flags.aconfig
@@ -67,13 +67,6 @@
 }
 
 flag {
-  name: "stream_camera"
-  namespace: "virtual_devices"
-  description: "Enable streaming camera to Virtual Devices"
-  bug: "291740640"
-}
-
-flag {
   name: "persistent_device_id_api"
   is_exported: true
   namespace: "virtual_devices"
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 5b0cee7..4285b0a 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -251,6 +251,7 @@
      * {@link android.Manifest.permission#NFC},
      * {@link android.Manifest.permission#TRANSMIT_IR},
      * {@link android.Manifest.permission#UWB_RANGING},
+     * {@link android.Manifest.permission#RANGING},
      * or has been granted the access to one of the attached USB devices/accessories.
      */
     @RequiresPermission(
@@ -267,6 +268,7 @@
                 Manifest.permission.NFC,
                 Manifest.permission.TRANSMIT_IR,
                 Manifest.permission.UWB_RANGING,
+                Manifest.permission.RANGING,
             },
             conditional = true
     )
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 6f70586..fff980f 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -349,3 +349,12 @@
     bug: "364760703"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "cloud_compilation_pm"
+    is_exported: true
+    namespace: "package_manager_service"
+    description: "Feature flag to enable the Cloud Compilation support on the package manager side."
+    bug: "377474232"
+    is_fixed_read_only: true
+}
diff --git a/core/java/android/content/res/TEST_MAPPING b/core/java/android/content/res/TEST_MAPPING
index c2febae..e8893e4 100644
--- a/core/java/android/content/res/TEST_MAPPING
+++ b/core/java/android/content/res/TEST_MAPPING
@@ -5,6 +5,9 @@
     },
     {
       "path": "frameworks/base/core/tests/coretests/src/com/android/internal/content/res"
+    },
+    {
+      "path": "platform_testing/libraries/screenshot"
     }
   ],
   "presubmit": [
diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig
index e98fc0c..26ecbd1 100644
--- a/core/java/android/content/res/flags.aconfig
+++ b/core/java/android/content/res/flags.aconfig
@@ -83,3 +83,15 @@
     bug: "364035303"
 }
 
+flag {
+    name: "system_context_handle_app_info_changed"
+    is_exported: true
+    namespace: "resource_manager"
+    description: "Feature flag for allowing system context to handle application info changes"
+    bug: "362420029"
+    # This flag is read at boot time.
+    is_fixed_read_only: true
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/core/java/android/database/BulkCursorNative.java b/core/java/android/database/BulkCursorNative.java
index 41585b3..7d6e7ad 100644
--- a/core/java/android/database/BulkCursorNative.java
+++ b/core/java/android/database/BulkCursorNative.java
@@ -215,7 +215,7 @@
             // If close() is being called from the finalizer thread, do not wait for a reply from
             // the remote side.
             final boolean fromFinalizer =
-                    android.database.sqlite.Flags.onewayFinalizerClose()
+                    android.database.sqlite.Flags.onewayFinalizerCloseFixed()
                     && "FinalizerDaemon".equals(Thread.currentThread().getName());
             mRemote.transact(CLOSE_TRANSACTION, data, reply,
                     fromFinalizer ? IBinder.FLAG_ONEWAY: 0);
diff --git a/core/java/android/database/sqlite/flags.aconfig b/core/java/android/database/sqlite/flags.aconfig
index 826b908..d43a669 100644
--- a/core/java/android/database/sqlite/flags.aconfig
+++ b/core/java/android/database/sqlite/flags.aconfig
@@ -2,8 +2,9 @@
 container: "system"
 
 flag {
-     name: "oneway_finalizer_close"
+     name: "oneway_finalizer_close_fixed"
      namespace: "system_performance"
+     is_fixed_read_only: true
      description: "Make BuildCursorNative.close oneway if in the the finalizer"
      bug: "368221351"
 }
diff --git a/core/java/android/hardware/DataSpace.java b/core/java/android/hardware/DataSpace.java
index 6117384..1cd9244 100644
--- a/core/java/android/hardware/DataSpace.java
+++ b/core/java/android/hardware/DataSpace.java
@@ -420,18 +420,38 @@
     public static final int DATASPACE_HEIF = 4100;
 
     /**
-     * ISO/IEC TBD
+     * Ultra HDR
      *
-     * JPEG image with embedded recovery map following the Jpeg/R specification.
+     * JPEG image with embedded HDR gain map following the Ultra HDR specification and
+     * starting with Android version {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM V}
+     * ISO/CD 21496‐1
      *
-     * <p>This value must always remain aligned with the public ImageFormat Jpeg/R definition and is
-     * valid with formats:
-     *    HAL_PIXEL_FORMAT_BLOB: JPEG image encoded by Jpeg/R encoder according to ISO/IEC TBD.
-     * The image contains a standard SDR JPEG and a recovery map. Jpeg/R decoders can use the
-     * map to recover the input image.</p>
+     * <p>This value is valid with formats:</p>
+     * <ul>
+     *    <li>HAL_PIXEL_FORMAT_BLOB: JPEG image encoded by Jpeg/R encoder according to
+     *    ISO/CD 21496‐1</li>
+     * </ul>
+     * <p>
+     * The image contains a standard SDR JPEG and a gain map. Ultra HDR decoders can use the
+     * gain map to boost the brightness of the rendered image.</p>
      */
      public static final int DATASPACE_JPEG_R = 4101;
 
+    /**
+     * ISO/IEC 23008-12:2024
+     *
+     * High Efficiency Image File Format (HEIF) with embedded HDR gain map
+     *
+     * <p>This value is valid with formats:</p>
+     * <ul>
+     *    <li>HAL_PIXEL_FORMAT_BLOB: A HEIC image encoded by HEVC encoder
+     *    according to ISO/IEC 23008-12:2024 that includes an HDR gain map and
+     *    metadata according to ISO/CD 21496‐1.</li>
+     * </ul>
+     */
+    @FlaggedApi(com.android.internal.camera.flags.Flags.FLAG_CAMERA_HEIF_GAINMAP)
+    public static final int DATASPACE_HEIF_ULTRAHDR = 4102;
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(flag = true, value = {
@@ -660,6 +680,7 @@
         DATASPACE_DEPTH,
         DATASPACE_DYNAMIC_DEPTH,
         DATASPACE_HEIF,
+        DATASPACE_HEIF_ULTRAHDR,
         DATASPACE_JPEG_R,
         DATASPACE_UNKNOWN,
         DATASPACE_SCRGB_LINEAR,
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 16d82ca..a37648f7 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -5775,6 +5775,122 @@
             new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.heic.availableHeicStallDurationsMaximumResolution", android.hardware.camera2.params.StreamConfigurationDuration[].class);
 
     /**
+     * <p>The available HEIC (ISO/IEC 23008-12/24) UltraHDR stream
+     * configurations that this camera device supports
+     * (i.e. format, width, height, output/input stream).</p>
+     * <p>The configurations are listed as <code>(format, width, height, input?)</code> tuples.</p>
+     * <p>All the static, control, and dynamic metadata tags related to JPEG apply to HEIC formats.
+     * Configuring JPEG and HEIC streams at the same time is not supported.</p>
+     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     * <p><b>Limited capability</b> -
+     * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
+     * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
+     *
+     * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_CAMERA_HEIF_GAINMAP)
+    public static final Key<android.hardware.camera2.params.StreamConfiguration[]> HEIC_AVAILABLE_HEIC_ULTRA_HDR_STREAM_CONFIGURATIONS =
+            new Key<android.hardware.camera2.params.StreamConfiguration[]>("android.heic.availableHeicUltraHdrStreamConfigurations", android.hardware.camera2.params.StreamConfiguration[].class);
+
+    /**
+     * <p>This lists the minimum frame duration for each
+     * format/size combination for HEIC UltraHDR output formats.</p>
+     * <p>This should correspond to the frame duration when only that
+     * stream is active, with all processing (typically in android.*.mode)
+     * set to either OFF or FAST.</p>
+     * <p>When multiple streams are used in a request, the minimum frame
+     * duration will be max(individual stream min durations).</p>
+     * <p>See {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration} and
+     * android.scaler.availableStallDurations for more details about
+     * calculating the max frame rate.</p>
+     * <p><b>Units</b>: (format, width, height, ns) x n</p>
+     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     * <p><b>Limited capability</b> -
+     * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
+     * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
+     *
+     * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+     * @see CaptureRequest#SENSOR_FRAME_DURATION
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_CAMERA_HEIF_GAINMAP)
+    public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> HEIC_AVAILABLE_HEIC_ULTRA_HDR_MIN_FRAME_DURATIONS =
+            new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.heic.availableHeicUltraHdrMinFrameDurations", android.hardware.camera2.params.StreamConfigurationDuration[].class);
+
+    /**
+     * <p>This lists the maximum stall duration for each
+     * output format/size combination for HEIC UltraHDR streams.</p>
+     * <p>A stall duration is how much extra time would get added
+     * to the normal minimum frame duration for a repeating request
+     * that has streams with non-zero stall.</p>
+     * <p>This functions similarly to
+     * android.scaler.availableStallDurations for HEIC UltraHDR
+     * streams.</p>
+     * <p>All HEIC output stream formats may have a nonzero stall
+     * duration.</p>
+     * <p><b>Units</b>: (format, width, height, ns) x n</p>
+     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     * <p><b>Limited capability</b> -
+     * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
+     * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
+     *
+     * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_CAMERA_HEIF_GAINMAP)
+    public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> HEIC_AVAILABLE_HEIC_ULTRA_HDR_STALL_DURATIONS =
+            new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.heic.availableHeicUltraHdrStallDurations", android.hardware.camera2.params.StreamConfigurationDuration[].class);
+
+    /**
+     * <p>The available HEIC (ISO/IEC 23008-12/24) UltraHDR stream
+     * configurations that this camera device supports
+     * (i.e. format, width, height, output/input stream) for CaptureRequests where
+     * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+     * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
+     * <p>Refer to android.heic.availableHeicStreamConfigurations for details.</p>
+     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     *
+     * @see CaptureRequest#SENSOR_PIXEL_MODE
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_CAMERA_HEIF_GAINMAP)
+    public static final Key<android.hardware.camera2.params.StreamConfiguration[]> HEIC_AVAILABLE_HEIC_ULTRA_HDR_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION =
+            new Key<android.hardware.camera2.params.StreamConfiguration[]>("android.heic.availableHeicUltraHdrStreamConfigurationsMaximumResolution", android.hardware.camera2.params.StreamConfiguration[].class);
+
+    /**
+     * <p>This lists the minimum frame duration for each
+     * format/size combination for HEIC UltraHDR output formats for CaptureRequests where
+     * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+     * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
+     * <p>Refer to android.heic.availableHeicMinFrameDurations for details.</p>
+     * <p><b>Units</b>: (format, width, height, ns) x n</p>
+     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     *
+     * @see CaptureRequest#SENSOR_PIXEL_MODE
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_CAMERA_HEIF_GAINMAP)
+    public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> HEIC_AVAILABLE_HEIC_ULTRA_HDR_MIN_FRAME_DURATIONS_MAXIMUM_RESOLUTION =
+            new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.heic.availableHeicUltraHdrMinFrameDurationsMaximumResolution", android.hardware.camera2.params.StreamConfigurationDuration[].class);
+
+    /**
+     * <p>This lists the maximum stall duration for each
+     * output format/size combination for HEIC UltraHDR streams for CaptureRequests where
+     * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+     * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
+     * <p>Refer to android.heic.availableHeicStallDurations for details.</p>
+     * <p><b>Units</b>: (format, width, height, ns) x n</p>
+     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     *
+     * @see CaptureRequest#SENSOR_PIXEL_MODE
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_CAMERA_HEIF_GAINMAP)
+    public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> HEIC_AVAILABLE_HEIC_ULTRA_HDR_STALL_DURATIONS_MAXIMUM_RESOLUTION =
+            new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.heic.availableHeicUltraHdrStallDurationsMaximumResolution", android.hardware.camera2.params.StreamConfigurationDuration[].class);
+
+    /**
      * <p>The direction of the camera faces relative to the vehicle body frame and the
      * passenger seats.</p>
      * <p>This enum defines the lens facing characteristic of the cameras on the automotive
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index ef7f3f8..e22c263 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -1385,6 +1385,9 @@
                             /*jpegRconfiguration*/ null,
                             /*jpegRminduration*/ null,
                             /*jpegRstallduration*/ null,
+                            /*heicUltraHDRconfiguration*/ null,
+                            /*heicUltraHDRminduration*/ null,
+                            /*heicUltraHDRstallduration*/ null,
                             /*highspeedvideoconfigurations*/ null,
                             /*inputoutputformatsmap*/ null, listHighResolution, supportsPrivate[i]);
                     break;
@@ -1402,6 +1405,9 @@
                             /*jpegRconfiguration*/ null,
                             /*jpegRminduration*/ null,
                             /*jpegRstallduration*/ null,
+                            /*heicUltraHDRconfiguration*/ null,
+                            /*heicUltraHDRminduration*/ null,
+                            /*heicUltraHDRstallduration*/ null,
                             highSpeedVideoConfigurations,
                             /*inputoutputformatsmap*/ null, listHighResolution, supportsPrivate[i]);
                     break;
@@ -1419,6 +1425,9 @@
                             /*jpegRconfiguration*/ null,
                             /*jpegRminduration*/ null,
                             /*jpegRstallduration*/ null,
+                            /*heicUltraHDRcconfiguration*/ null,
+                            /*heicUltraHDRminduration*/ null,
+                            /*heicUltraHDRstallduration*/ null,
                             /*highSpeedVideoConfigurations*/ null,
                             inputOutputFormatsMap, listHighResolution, supportsPrivate[i]);
                     break;
@@ -1436,6 +1445,9 @@
                             /*jpegRconfiguration*/ null,
                             /*jpegRminduration*/ null,
                             /*jpegRstallduration*/ null,
+                            /*heicUltraHDRcconfiguration*/ null,
+                            /*heicUltraHDRminduration*/ null,
+                            /*heicUltraHDRstallduration*/ null,
                             /*highSpeedVideoConfigurations*/ null,
                             /*inputOutputFormatsMap*/ null, listHighResolution, supportsPrivate[i]);
             }
@@ -1607,6 +1619,17 @@
                 CameraCharacteristics.HEIC_AVAILABLE_HEIC_MIN_FRAME_DURATIONS);
         StreamConfigurationDuration[] heicStallDurations = getBase(
                 CameraCharacteristics.HEIC_AVAILABLE_HEIC_STALL_DURATIONS);
+        StreamConfiguration[] heicUltraHDRConfigurations = null;
+        StreamConfigurationDuration[] heicUltraHDRMinFrameDurations = null;
+        StreamConfigurationDuration[] heicUltraHDRStallDurations = null;
+        if (Flags.cameraHeifGainmap()) {
+            heicUltraHDRConfigurations = getBase(
+                    CameraCharacteristics.HEIC_AVAILABLE_HEIC_ULTRA_HDR_STREAM_CONFIGURATIONS);
+            heicUltraHDRMinFrameDurations = getBase(
+                    CameraCharacteristics.HEIC_AVAILABLE_HEIC_ULTRA_HDR_MIN_FRAME_DURATIONS);
+            heicUltraHDRStallDurations = getBase(
+                    CameraCharacteristics.HEIC_AVAILABLE_HEIC_ULTRA_HDR_STALL_DURATIONS);
+        }
         StreamConfiguration[] jpegRConfigurations = getBase(
                 CameraCharacteristics.JPEGR_AVAILABLE_JPEG_R_STREAM_CONFIGURATIONS);
         StreamConfigurationDuration[] jpegRMinFrameDurations = getBase(
@@ -1625,7 +1648,8 @@
                 dynamicDepthStallDurations, heicConfigurations,
                 heicMinFrameDurations, heicStallDurations,
                 jpegRConfigurations, jpegRMinFrameDurations, jpegRStallDurations,
-                highSpeedVideoConfigurations, inputOutputFormatsMap,
+                heicUltraHDRConfigurations, heicUltraHDRMinFrameDurations,
+                heicUltraHDRStallDurations, highSpeedVideoConfigurations, inputOutputFormatsMap,
                 listHighResolution);
     }
 
@@ -1662,6 +1686,17 @@
                 CameraCharacteristics.HEIC_AVAILABLE_HEIC_MIN_FRAME_DURATIONS_MAXIMUM_RESOLUTION);
         StreamConfigurationDuration[] heicStallDurations = getBase(
                 CameraCharacteristics.HEIC_AVAILABLE_HEIC_STALL_DURATIONS_MAXIMUM_RESOLUTION);
+        StreamConfiguration[] heicUltraHDRConfigurations = null;
+        StreamConfigurationDuration[] heicUltraHDRMinFrameDurations = null;
+        StreamConfigurationDuration[] heicUltraHDRStallDurations = null;
+        if (Flags.cameraHeifGainmap()) {
+            heicUltraHDRConfigurations = getBase(
+                    CameraCharacteristics.HEIC_AVAILABLE_HEIC_ULTRA_HDR_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION);
+            heicUltraHDRMinFrameDurations = getBase(
+                    CameraCharacteristics.HEIC_AVAILABLE_HEIC_ULTRA_HDR_MIN_FRAME_DURATIONS_MAXIMUM_RESOLUTION);
+            heicUltraHDRStallDurations = getBase(
+                    CameraCharacteristics.HEIC_AVAILABLE_HEIC_ULTRA_HDR_STALL_DURATIONS_MAXIMUM_RESOLUTION);
+        }
         StreamConfiguration[] jpegRConfigurations = getBase(
                 CameraCharacteristics.JPEGR_AVAILABLE_JPEG_R_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION);
         StreamConfigurationDuration[] jpegRMinFrameDurations = getBase(
@@ -1681,7 +1716,8 @@
                 dynamicDepthStallDurations, heicConfigurations,
                 heicMinFrameDurations, heicStallDurations,
                 jpegRConfigurations, jpegRMinFrameDurations, jpegRStallDurations,
-                highSpeedVideoConfigurations, inputOutputFormatsMap,
+                heicUltraHDRConfigurations, heicUltraHDRMinFrameDurations,
+                heicUltraHDRStallDurations, highSpeedVideoConfigurations, inputOutputFormatsMap,
                 listHighResolution, false);
     }
 
diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
index e3dbb2b..ec028bf 100644
--- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
+++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
@@ -20,6 +20,7 @@
 
 import android.graphics.ImageFormat;
 import android.graphics.PixelFormat;
+import android.hardware.DataSpace;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CameraMetadata;
@@ -31,6 +32,8 @@
 import android.util.SparseIntArray;
 import android.view.Surface;
 
+import com.android.internal.camera.flags.Flags;
+
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Objects;
@@ -100,6 +103,12 @@
      *        {@link StreamConfigurationDuration}
      * @param jpegRStallDurations a non-{@code null} array of Jpeg/R
      *        {@link StreamConfigurationDuration}
+     * @param heicUltraHDRConfigurations a non-{@code null} array of Heic UltraHDR
+     *        {@link StreamConfiguration}
+     * @param heicUltraHDRMinFrameDurations a non-{@code null} array of Heic UltraHDR
+     *        {@link StreamConfigurationDuration}
+     * @param heicUltraHDRStallDurations a non-{@code null} array of Heic UltraHDR
+     *        {@link StreamConfigurationDuration}
      * @param highSpeedVideoConfigurations an array of {@link HighSpeedVideoConfiguration}, null if
      *        camera device does not support high speed video recording
      * @param listHighResolution a flag indicating whether the device supports BURST_CAPTURE
@@ -125,6 +134,9 @@
             StreamConfiguration[] jpegRConfigurations,
             StreamConfigurationDuration[] jpegRMinFrameDurations,
             StreamConfigurationDuration[] jpegRStallDurations,
+            StreamConfiguration[] heicUltraHDRConfigurations,
+            StreamConfigurationDuration[] heicUltraHDRMinFrameDurations,
+            StreamConfigurationDuration[] heicUltraHDRStallDurations,
             HighSpeedVideoConfiguration[] highSpeedVideoConfigurations,
             ReprocessFormatsMap inputOutputFormatsMap,
             boolean listHighResolution) {
@@ -134,8 +146,9 @@
                     dynamicDepthStallDurations,
                     heicConfigurations, heicMinFrameDurations, heicStallDurations,
                     jpegRConfigurations, jpegRMinFrameDurations, jpegRStallDurations,
-                    highSpeedVideoConfigurations, inputOutputFormatsMap, listHighResolution,
-                    /*enforceImplementationDefined*/ true);
+                    heicUltraHDRConfigurations, heicUltraHDRMinFrameDurations,
+                    heicUltraHDRStallDurations, highSpeedVideoConfigurations, inputOutputFormatsMap,
+                    listHighResolution, /*enforceImplementationDefined*/ true);
     }
 
     /**
@@ -168,6 +181,12 @@
      *        {@link StreamConfigurationDuration}
      * @param jpegRStallDurations a non-{@code null} array of Jpeg/R
      *        {@link StreamConfigurationDuration}
+     * @param heicUltraHDRConfigurations an array of Heic UltraHDR
+     *        {@link StreamConfiguration}, {@code null} if camera doesn't support the format
+     * @param heicUltraHDRMinFrameDurations an array of Heic UltraHDR
+     *        {@link StreamConfigurationDuration}, {@code null} if camera doesn't support the format
+     * @param heicUltraHDRStallDurations an array of Heic UltraHDR
+     *        {@link StreamConfigurationDuration}, {@code null} if camera doesn't support the format
      * @param highSpeedVideoConfigurations an array of {@link HighSpeedVideoConfiguration}, null if
      *        camera device does not support high speed video recording
      * @param listHighResolution a flag indicating whether the device supports BURST_CAPTURE
@@ -195,6 +214,9 @@
             StreamConfiguration[] jpegRConfigurations,
             StreamConfigurationDuration[] jpegRMinFrameDurations,
             StreamConfigurationDuration[] jpegRStallDurations,
+            StreamConfiguration[] heicUltraHDRConfigurations,
+            StreamConfigurationDuration[] heicUltraHDRMinFrameDurations,
+            StreamConfigurationDuration[] heicUltraHDRStallDurations,
             HighSpeedVideoConfiguration[] highSpeedVideoConfigurations,
             ReprocessFormatsMap inputOutputFormatsMap,
             boolean listHighResolution,
@@ -259,6 +281,18 @@
                     "heicStallDurations");
         }
 
+        if (heicUltraHDRConfigurations == null || (!Flags.cameraHeifGainmap())) {
+            mHeicUltraHDRConfigurations = new StreamConfiguration[0];
+            mHeicUltraHDRMinFrameDurations = new StreamConfigurationDuration[0];
+            mHeicUltraHDRStallDurations = new StreamConfigurationDuration[0];
+        } else {
+            mHeicUltraHDRConfigurations = checkArrayElementsNotNull(heicUltraHDRConfigurations,
+                    "heicUltraHDRConfigurations");
+            mHeicUltraHDRMinFrameDurations = checkArrayElementsNotNull(
+                    heicUltraHDRMinFrameDurations, "heicUltraHDRMinFrameDurations");
+            mHeicUltraHDRStallDurations = checkArrayElementsNotNull(heicUltraHDRStallDurations,
+                    "heicUltraHDRStallDurations");
+        }
 
         if (jpegRConfigurations == null) {
             mJpegRConfigurations = new StreamConfiguration[0];
@@ -336,6 +370,19 @@
                     mHeicOutputFormats.get(config.getFormat()) + 1);
         }
 
+        if (Flags.cameraHeifGainmap()) {
+            // For each Heic UlrtaHDR format, track how many sizes there are available to configure
+            for (StreamConfiguration config : mHeicUltraHDRConfigurations) {
+                if (!config.isOutput()) {
+                    // Ignoring input Heic UltraHDR configs
+                    continue;
+                }
+
+                mHeicUltraHDROutputFormats.put(config.getFormat(),
+                        mHeicUltraHDROutputFormats.get(config.getFormat()) + 1);
+            }
+        }
+
         // For each Jpeg/R format, track how many sizes there are available to configure
         for (StreamConfiguration config : mJpegRConfigurations) {
             if (!config.isOutput()) {
@@ -483,6 +530,11 @@
 
         int internalFormat = imageFormatToInternal(format);
         int dataspace = imageFormatToDataspace(format);
+        if (Flags.cameraHeifGainmap()) {
+            if (dataspace == DataSpace.DATASPACE_HEIF_ULTRAHDR) {
+                return mHeicUltraHDROutputFormats.indexOfKey(internalFormat) >= 0;
+            }
+        }
         if (dataspace == HAL_DATASPACE_DEPTH) {
             return mDepthOutputFormats.indexOfKey(internalFormat) >= 0;
         } else if (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) {
@@ -607,6 +659,11 @@
                 surfaceDataspace == HAL_DATASPACE_HEIF ? mHeicConfigurations :
                 surfaceDataspace == HAL_DATASPACE_JPEG_R ? mJpegRConfigurations :
                 mConfigurations;
+        if (Flags.cameraHeifGainmap()) {
+            if (surfaceDataspace == DataSpace.DATASPACE_HEIF_ULTRAHDR) {
+                    configs = mHeicUltraHDRConfigurations;
+            }
+        }
         for (StreamConfiguration config : configs) {
             if (config.getFormat() == surfaceFormat && config.isOutput()) {
                 // Matching format, either need exact size match, or a flexible consumer
@@ -646,6 +703,11 @@
                 dataspace == HAL_DATASPACE_HEIF ? mHeicConfigurations :
                 dataspace == HAL_DATASPACE_JPEG_R ? mJpegRConfigurations :
                 mConfigurations;
+        if (Flags.cameraHeifGainmap()) {
+            if (dataspace == DataSpace.DATASPACE_HEIF_ULTRAHDR ) {
+                configs = mHeicUltraHDRConfigurations;
+            }
+        }
         for (StreamConfiguration config : configs) {
             if ((config.getFormat() == internalFormat) && config.isOutput() &&
                     config.getSize().equals(size)) {
@@ -1176,6 +1238,10 @@
                     Arrays.equals(mHeicConfigurations, other.mHeicConfigurations) &&
                     Arrays.equals(mHeicMinFrameDurations, other.mHeicMinFrameDurations) &&
                     Arrays.equals(mHeicStallDurations, other.mHeicStallDurations) &&
+                    Arrays.equals(mHeicUltraHDRConfigurations, other.mHeicUltraHDRConfigurations) &&
+                    Arrays.equals(mHeicUltraHDRMinFrameDurations,
+                            other.mHeicUltraHDRMinFrameDurations) &&
+                    Arrays.equals(mHeicUltraHDRStallDurations, other.mHeicUltraHDRStallDurations) &&
                     Arrays.equals(mJpegRConfigurations, other.mJpegRConfigurations) &&
                     Arrays.equals(mJpegRMinFrameDurations, other.mJpegRMinFrameDurations) &&
                     Arrays.equals(mJpegRStallDurations, other.mJpegRStallDurations) &&
@@ -1197,8 +1263,9 @@
                 mDynamicDepthConfigurations, mDynamicDepthMinFrameDurations,
                 mDynamicDepthStallDurations, mHeicConfigurations,
                 mHeicMinFrameDurations, mHeicStallDurations,
-                mJpegRConfigurations, mJpegRMinFrameDurations, mJpegRStallDurations,
-                mHighSpeedVideoConfigurations);
+                mHeicUltraHDRConfigurations, mHeicUltraHDRMinFrameDurations,
+                mHeicUltraHDRStallDurations, mJpegRConfigurations, mJpegRMinFrameDurations,
+                mJpegRStallDurations, mHighSpeedVideoConfigurations);
     }
 
     // Check that the argument is supported by #getOutputFormats or #getInputFormats
@@ -1209,6 +1276,13 @@
         int internalDataspace = imageFormatToDataspace(format);
 
         if (output) {
+            if (Flags.cameraHeifGainmap()) {
+                if (internalDataspace == DataSpace.DATASPACE_HEIF_ULTRAHDR) {
+                    if (mHeicUltraHDROutputFormats.indexOfKey(internalFormat) >= 0) {
+                        return format;
+                    }
+                }
+            }
             if (internalDataspace == HAL_DATASPACE_DEPTH) {
                 if (mDepthOutputFormats.indexOfKey(internalFormat) >= 0) {
                     return format;
@@ -1429,6 +1503,7 @@
      * <li>ImageFormat.DEPTH_POINT_CLOUD => HAL_PIXEL_FORMAT_BLOB
      * <li>ImageFormat.DEPTH_JPEG => HAL_PIXEL_FORMAT_BLOB
      * <li>ImageFormat.HEIC => HAL_PIXEL_FORMAT_BLOB
+     * <li>ImageFormat.HEIC_ULTRAHDR => HAL_PIXEL_FORMAT_BLOB
      * <li>ImageFormat.JPEG_R => HAL_PIXEL_FORMAT_BLOB
      * <li>ImageFormat.DEPTH16 => HAL_PIXEL_FORMAT_Y16
      * </ul>
@@ -1451,6 +1526,11 @@
      *              if {@code format} was {@code HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED}
      */
     static int imageFormatToInternal(int format) {
+        if (Flags.cameraHeifGainmap()) {
+           if (format == ImageFormat.HEIC_ULTRAHDR) {
+               return HAL_PIXEL_FORMAT_BLOB;
+           }
+        }
         switch (format) {
             case ImageFormat.JPEG:
             case ImageFormat.DEPTH_POINT_CLOUD:
@@ -1480,6 +1560,7 @@
      * <li>ImageFormat.DEPTH16 => HAL_DATASPACE_DEPTH
      * <li>ImageFormat.DEPTH_JPEG => HAL_DATASPACE_DYNAMIC_DEPTH
      * <li>ImageFormat.HEIC => HAL_DATASPACE_HEIF
+     * <li>ImageFormat.HEIC_ULTRAHDR => DATASPACE_HEIF_ULTRAHDR
      * <li>ImageFormat.JPEG_R => HAL_DATASPACE_JPEG_R
      * <li>ImageFormat.YUV_420_888 => HAL_DATASPACE_JFIF
      * <li>ImageFormat.RAW_SENSOR => HAL_DATASPACE_ARBITRARY
@@ -1508,6 +1589,11 @@
      *              if {@code format} was {@code HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED}
      */
     static int imageFormatToDataspace(int format) {
+        if (Flags.cameraHeifGainmap()) {
+            if (format == ImageFormat.HEIC_ULTRAHDR) {
+                return DataSpace.DATASPACE_HEIF_ULTRAHDR;
+            }
+        }
         switch (format) {
             case ImageFormat.JPEG:
                 return HAL_DATASPACE_V0_JFIF;
@@ -1584,13 +1670,21 @@
                 dataspace == HAL_DATASPACE_JPEG_R ? mJpegROutputFormats :
                 highRes ? mHighResOutputFormats :
                 mOutputFormats;
-
+        boolean isDataSpaceHeifUltraHDR = false;
+        if (Flags.cameraHeifGainmap()) {
+            if (dataspace == DataSpace.DATASPACE_HEIF_ULTRAHDR) {
+                formatsMap = mHeicUltraHDROutputFormats;
+                isDataSpaceHeifUltraHDR = true;
+            }
+        }
         int sizesCount = formatsMap.get(format);
         if ( ((!output || (dataspace == HAL_DATASPACE_DEPTH || dataspace == HAL_DATASPACE_JPEG_R ||
                             dataspace == HAL_DATASPACE_DYNAMIC_DEPTH ||
-                            dataspace == HAL_DATASPACE_HEIF)) && sizesCount == 0) ||
+                            dataspace == HAL_DATASPACE_HEIF ||
+                            isDataSpaceHeifUltraHDR)) && sizesCount == 0) ||
                 (output && (dataspace != HAL_DATASPACE_DEPTH && dataspace != HAL_DATASPACE_JPEG_R &&
                             dataspace != HAL_DATASPACE_DYNAMIC_DEPTH &&
+                            !isDataSpaceHeifUltraHDR &&
                             dataspace != HAL_DATASPACE_HEIF) &&
                  mAllOutputFormats.get(format) == 0)) {
             return null;
@@ -1604,12 +1698,14 @@
                 (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ? mDynamicDepthConfigurations :
                 (dataspace == HAL_DATASPACE_HEIF) ? mHeicConfigurations :
                 (dataspace == HAL_DATASPACE_JPEG_R) ? mJpegRConfigurations :
+                (isDataSpaceHeifUltraHDR) ? mHeicUltraHDRConfigurations :
                 mConfigurations;
         StreamConfigurationDuration[] minFrameDurations =
                 (dataspace == HAL_DATASPACE_DEPTH) ? mDepthMinFrameDurations :
                 (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ? mDynamicDepthMinFrameDurations :
                 (dataspace == HAL_DATASPACE_HEIF) ? mHeicMinFrameDurations :
                 (dataspace == HAL_DATASPACE_JPEG_R) ? mJpegRMinFrameDurations :
+                (isDataSpaceHeifUltraHDR) ? mHeicUltraHDRMinFrameDurations :
                 mMinFrameDurations;
 
         for (StreamConfiguration config : configurations) {
@@ -1639,7 +1735,8 @@
 
         // Dynamic depth streams can have both fast and also high res modes.
         if ((sizeIndex != sizesCount) && (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH ||
-                dataspace == HAL_DATASPACE_HEIF) || (dataspace == HAL_DATASPACE_JPEG_R)) {
+                dataspace == HAL_DATASPACE_HEIF) || (dataspace == HAL_DATASPACE_JPEG_R) ||
+                isDataSpaceHeifUltraHDR) {
 
             if (sizeIndex > sizesCount) {
                 throw new AssertionError(
@@ -1682,6 +1779,11 @@
             if (mHeicOutputFormats.size() > 0) {
                 formats[i++] = ImageFormat.HEIC;
             }
+            if (Flags.cameraHeifGainmap()) {
+                if (mHeicUltraHDROutputFormats.size() > 0) {
+                    formats[i++] = ImageFormat.HEIC_ULTRAHDR;
+                }
+            }
             if (mJpegROutputFormats.size() > 0) {
                 formats[i++] = ImageFormat.JPEG_R;
             }
@@ -1725,12 +1827,19 @@
      * @see #DURATION_STALL
      * */
     private StreamConfigurationDuration[] getDurations(int duration, int dataspace) {
+        boolean isDataSpaceHeifUltraHDR = false;
+        if (Flags.cameraHeifGainmap()) {
+            if (dataspace == DataSpace.DATASPACE_HEIF_ULTRAHDR) {
+                isDataSpaceHeifUltraHDR = true;
+            }
+        }
         switch (duration) {
             case DURATION_MIN_FRAME:
                 return (dataspace == HAL_DATASPACE_DEPTH) ? mDepthMinFrameDurations :
                         (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ?
                         mDynamicDepthMinFrameDurations :
                         (dataspace == HAL_DATASPACE_HEIF) ? mHeicMinFrameDurations :
+                        isDataSpaceHeifUltraHDR ? mHeicUltraHDRMinFrameDurations :
                         (dataspace == HAL_DATASPACE_JPEG_R) ? mJpegRMinFrameDurations :
                         mMinFrameDurations;
 
@@ -1738,6 +1847,7 @@
                 return (dataspace == HAL_DATASPACE_DEPTH) ? mDepthStallDurations :
                         (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ? mDynamicDepthStallDurations :
                         (dataspace == HAL_DATASPACE_HEIF) ? mHeicStallDurations :
+                        isDataSpaceHeifUltraHDR ? mHeicUltraHDRStallDurations :
                         (dataspace == HAL_DATASPACE_JPEG_R) ? mJpegRStallDurations :
                         mStallDurations;
             default:
@@ -1754,6 +1864,7 @@
             size += mDynamicDepthOutputFormats.size();
             size += mHeicOutputFormats.size();
             size += mJpegROutputFormats.size();
+            size += mHeicUltraHDROutputFormats.size();
         }
 
         return size;
@@ -1774,11 +1885,18 @@
     }
 
     private boolean isSupportedInternalConfiguration(int format, int dataspace, Size size) {
+        boolean isDataSpaceHeifUltraHDR = false;
+        if (Flags.cameraHeifGainmap()) {
+            if (dataspace == DataSpace.DATASPACE_HEIF_ULTRAHDR) {
+                isDataSpaceHeifUltraHDR = true;
+            }
+        }
         StreamConfiguration[] configurations =
                 (dataspace == HAL_DATASPACE_DEPTH) ? mDepthConfigurations :
                 (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ? mDynamicDepthConfigurations :
                 (dataspace == HAL_DATASPACE_HEIF) ? mHeicConfigurations :
                 (dataspace == HAL_DATASPACE_JPEG_R) ? mJpegRConfigurations :
+                isDataSpaceHeifUltraHDR ? mHeicUltraHDRConfigurations :
                 mConfigurations;
 
         for (int i = 0; i < configurations.length; i++) {
@@ -1954,6 +2072,11 @@
      * @hide
      */
     public static String formatToString(int format) {
+        if (Flags.cameraHeifGainmap()) {
+            if (format == ImageFormat.HEIC_ULTRAHDR) {
+                return "HEIC_ULTRAHDR";
+            }
+        }
         switch (format) {
             case ImageFormat.YV12:
                 return "YV12";
@@ -2078,6 +2201,10 @@
     private final StreamConfigurationDuration[] mHeicMinFrameDurations;
     private final StreamConfigurationDuration[] mHeicStallDurations;
 
+    private final StreamConfiguration[] mHeicUltraHDRConfigurations;
+    private final StreamConfigurationDuration[] mHeicUltraHDRMinFrameDurations;
+    private final StreamConfigurationDuration[] mHeicUltraHDRStallDurations;
+
     private final StreamConfiguration[] mJpegRConfigurations;
     private final StreamConfigurationDuration[] mJpegRMinFrameDurations;
     private final StreamConfigurationDuration[] mJpegRStallDurations;
@@ -2103,6 +2230,8 @@
     private final SparseIntArray mDynamicDepthOutputFormats = new SparseIntArray();
     /** internal format -> num heic output sizes mapping, for HAL_DATASPACE_HEIF */
     private final SparseIntArray mHeicOutputFormats = new SparseIntArray();
+    /** internal format -> num heic output sizes mapping, for DATASPACE_HEIF_GAINMAP */
+    private final SparseIntArray mHeicUltraHDROutputFormats = new SparseIntArray();
     /** internal format -> num Jpeg/R output sizes mapping, for HAL_DATASPACE_JPEG_R */
     private final SparseIntArray mJpegROutputFormats = new SparseIntArray();
 
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 3c6841c..56307ae 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -1432,6 +1432,13 @@
                 mExecutor.execute(mCallback::onStopped);
             }
         }
+
+        @Override // Binder call
+        public void onRequestedBrightnessChanged(float brightness) {
+            if (mCallback != null) {
+                mExecutor.execute(() -> mCallback.onRequestedBrightnessChanged(brightness));
+            }
+        }
     }
 
     /**
diff --git a/core/java/android/hardware/display/IVirtualDisplayCallback.aidl b/core/java/android/hardware/display/IVirtualDisplayCallback.aidl
index c3490d1..9cc0364 100644
--- a/core/java/android/hardware/display/IVirtualDisplayCallback.aidl
+++ b/core/java/android/hardware/display/IVirtualDisplayCallback.aidl
@@ -38,4 +38,9 @@
      * of the application to release() the virtual display.
      */
     void onStopped();
+
+    /**
+     * Called when the virtual display's requested brightness has changed.
+     */
+    void onRequestedBrightnessChanged(float brightness);
 }
diff --git a/core/java/android/hardware/display/VirtualDisplay.java b/core/java/android/hardware/display/VirtualDisplay.java
index 32b6405..3b573ea 100644
--- a/core/java/android/hardware/display/VirtualDisplay.java
+++ b/core/java/android/hardware/display/VirtualDisplay.java
@@ -16,6 +16,8 @@
 package android.hardware.display;
 
 import android.annotation.FlaggedApi;
+import android.annotation.FloatRange;
+import android.annotation.SystemApi;
 import android.view.Display;
 import android.view.Surface;
 
@@ -164,5 +166,25 @@
          * of the application to release() the virtual display.
          */
         public void onStopped() { }
+
+        /**
+         * Called when the requested brightness of the display has changed.
+         *
+         * <p>The system may adjust the display's brightness based on user or app activity. This
+         * callback will only be invoked if the display has an explicitly specified default
+         * brightness value.</p>
+         *
+         * <p>Value of {@code 0.0} indicates the minimum supported brightness and value of
+         * {@code 1.0} indicates the maximum supported brightness.</p>
+         *
+         * @see android.view.View#setKeepScreenOn(boolean)
+         * @see android.view.WindowManager.LayoutParams#screenBrightness
+         * @see VirtualDisplayConfig.Builder#setDefaultBrightness(float)
+         * @hide
+         */
+        @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+        @SystemApi
+        public void onRequestedBrightnessChanged(
+                @FloatRange(from = 0.0f, to = 1.0f) float brightness) {}
     }
 }
diff --git a/core/java/android/hardware/display/VirtualDisplayConfig.java b/core/java/android/hardware/display/VirtualDisplayConfig.java
index 49944c7..57d9d28 100644
--- a/core/java/android/hardware/display/VirtualDisplayConfig.java
+++ b/core/java/android/hardware/display/VirtualDisplayConfig.java
@@ -29,6 +29,7 @@
 import android.os.Handler;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.PowerManager;
 import android.util.ArraySet;
 import android.view.Display;
 import android.view.DisplayCutout;
@@ -61,6 +62,7 @@
     private final boolean mIsHomeSupported;
     private final DisplayCutout mDisplayCutout;
     private final boolean mIgnoreActivitySizeRestrictions;
+    private final float mDefaultBrightness;
 
     private VirtualDisplayConfig(
             @NonNull String name,
@@ -76,7 +78,8 @@
             float requestedRefreshRate,
             boolean isHomeSupported,
             @Nullable DisplayCutout displayCutout,
-            boolean ignoreActivitySizeRestrictions) {
+            boolean ignoreActivitySizeRestrictions,
+            @FloatRange(from = 0.0f, to = 1.0f) float defaultBrightness) {
         mName = name;
         mWidth = width;
         mHeight = height;
@@ -91,6 +94,7 @@
         mIsHomeSupported = isHomeSupported;
         mDisplayCutout = displayCutout;
         mIgnoreActivitySizeRestrictions = ignoreActivitySizeRestrictions;
+        mDefaultBrightness = defaultBrightness;
     }
 
     /**
@@ -157,6 +161,22 @@
     }
 
     /**
+     * Returns the default brightness of the display.
+     *
+     * <p>Value of {@code 0.0} indicates the minimum supported brightness and value of {@code 1.0}
+     * indicates the maximum supported brightness.</p>
+     *
+     * @see Builder#setDefaultBrightness(float)
+     * @hide
+     */
+    @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+    @SystemApi
+    public @FloatRange(from = 0.0f, to = 1.0f) float getDefaultBrightness() {
+        return mDefaultBrightness;
+    }
+
+
+    /**
      * Returns the unique identifier for the display. Shouldn't be displayed to the user.
      * @hide
      */
@@ -245,6 +265,7 @@
         dest.writeBoolean(mIsHomeSupported);
         DisplayCutout.ParcelableWrapper.writeCutoutToParcel(mDisplayCutout, dest, flags);
         dest.writeBoolean(mIgnoreActivitySizeRestrictions);
+        dest.writeFloat(mDefaultBrightness);
     }
 
     @Override
@@ -272,7 +293,8 @@
                 && mRequestedRefreshRate == that.mRequestedRefreshRate
                 && mIsHomeSupported == that.mIsHomeSupported
                 && mIgnoreActivitySizeRestrictions == that.mIgnoreActivitySizeRestrictions
-                && Objects.equals(mDisplayCutout, that.mDisplayCutout);
+                && Objects.equals(mDisplayCutout, that.mDisplayCutout)
+                && mDefaultBrightness == that.mDefaultBrightness;
     }
 
     @Override
@@ -281,7 +303,7 @@
                 mName, mWidth, mHeight, mDensityDpi, mFlags, mSurface, mUniqueId,
                 mDisplayIdToMirror, mWindowManagerMirroringEnabled, mDisplayCategories,
                 mRequestedRefreshRate, mIsHomeSupported, mDisplayCutout,
-                mIgnoreActivitySizeRestrictions);
+                mIgnoreActivitySizeRestrictions, mDefaultBrightness);
         return hashCode;
     }
 
@@ -303,6 +325,7 @@
                 + " mIsHomeSupported=" + mIsHomeSupported
                 + " mDisplayCutout=" + mDisplayCutout
                 + " mIgnoreActivitySizeRestrictions=" + mIgnoreActivitySizeRestrictions
+                + " mDefaultBrightness=" + mDefaultBrightness
                 + ")";
     }
 
@@ -321,6 +344,7 @@
         mIsHomeSupported = in.readBoolean();
         mDisplayCutout = DisplayCutout.ParcelableWrapper.readCutoutFromParcel(in);
         mIgnoreActivitySizeRestrictions = in.readBoolean();
+        mDefaultBrightness = in.readFloat();
     }
 
     @NonNull
@@ -355,6 +379,7 @@
         private boolean mIsHomeSupported = false;
         private DisplayCutout mDisplayCutout = null;
         private boolean mIgnoreActivitySizeRestrictions = false;
+        private float mDefaultBrightness = 0.0f;
 
         /**
          * Creates a new Builder.
@@ -547,6 +572,35 @@
         }
 
         /**
+         * Sets the default brightness of the display.
+         *
+         * <p>The system will use this brightness value whenever the display should be bright, i.e.
+         * it is powered on and not dimmed due to user activity or app activity.</p>
+         *
+         * <p>Value of {@code 0.0} indicates the minimum supported brightness and value of
+         * {@code 1.0} indicates the maximum supported brightness.</p>
+         *
+         * <p>If unset, defaults to {@code 0.0}</p>
+         *
+         * @see android.view.View#setKeepScreenOn(boolean)
+         * @see Builder#setDefaultBrightness(float)
+         * @see VirtualDisplay.Callback#onRequestedBrightnessChanged(float)
+         * @hide
+         */
+        @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+        @SystemApi
+        @NonNull
+        public Builder setDefaultBrightness(@FloatRange(from = 0.0f, to = 1.0f) float brightness) {
+            if (brightness < PowerManager.BRIGHTNESS_MIN
+                    || brightness > PowerManager.BRIGHTNESS_MAX) {
+                throw new IllegalArgumentException(
+                        "Virtual display default brightness must be in range [0.0, 1.0]");
+            }
+            mDefaultBrightness = brightness;
+            return this;
+        }
+
+        /**
          * Builds the {@link VirtualDisplayConfig} instance.
          */
         @NonNull
@@ -565,7 +619,8 @@
                     mRequestedRefreshRate,
                     mIsHomeSupported,
                     mDisplayCutout,
-                    mIgnoreActivitySizeRestrictions);
+                    mIgnoreActivitySizeRestrictions,
+                    mDefaultBrightness);
         }
     }
 }
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index bce9518..39dddb7 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -279,4 +279,6 @@
     void removeAllCustomInputGestures();
 
     AidlInputGestureData[] getCustomInputGestures();
+
+    AidlInputGestureData[] getAppLaunchBookmarks();
 }
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 876ba10..2051dbe 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1494,9 +1494,8 @@
         try {
             return mIm.addCustomInputGesture(inputGestureData.getAidlData());
         } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
+            throw e.rethrowFromSystemServer();
         }
-        return CUSTOM_INPUT_GESTURE_RESULT_ERROR_OTHER;
     }
 
     /** Removes an existing custom gesture
@@ -1517,9 +1516,8 @@
         try {
             return mIm.removeCustomInputGesture(inputGestureData.getAidlData());
         } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
+            throw e.rethrowFromSystemServer();
         }
-        return CUSTOM_INPUT_GESTURE_RESULT_ERROR_OTHER;
     }
 
     /** Removes all custom input gestures
@@ -1534,7 +1532,7 @@
         try {
             mIm.removeAllCustomInputGestures();
         } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -1552,12 +1550,32 @@
                 result.add(new InputGestureData(data));
             }
         } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
+            throw e.rethrowFromSystemServer();
         }
         return result;
     }
 
     /**
+     * Return the set of application launch bookmarks handled by the input framework.
+     *
+     * @return list of {@link InputGestureData} containing the application launch shortcuts parsed
+     * at boot time from {@code bookmarks.xml}.
+     *
+     * @hide
+     */
+    public List<InputGestureData> getAppLaunchBookmarks() {
+        try {
+            List<InputGestureData> result = new ArrayList<>();
+            for (AidlInputGestureData data : mIm.getAppLaunchBookmarks()) {
+                result.add(new InputGestureData(data));
+            }
+            return result;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * A callback used to be notified about battery state changes for an input device. The
      * {@link #onBatteryStateChanged(int, long, BatteryState)} method will be called once after the
      * listener is successfully registered to provide the initial battery state of the device.
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index 4a9efe0..96f6ad1 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -20,16 +20,19 @@
 import static com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS;
 import static com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG;
 import static com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG;
+import static com.android.hardware.input.Flags.enableCustomizableInputGestures;
 import static com.android.hardware.input.Flags.keyboardA11yBounceKeysFlag;
+import static com.android.hardware.input.Flags.keyboardA11yMouseKeys;
 import static com.android.hardware.input.Flags.keyboardA11ySlowKeysFlag;
 import static com.android.hardware.input.Flags.keyboardA11yStickyKeysFlag;
-import static com.android.hardware.input.Flags.keyboardA11yMouseKeys;
 import static com.android.hardware.input.Flags.mouseReverseVerticalScrolling;
 import static com.android.hardware.input.Flags.mouseSwapPrimaryButton;
 import static com.android.hardware.input.Flags.touchpadTapDragging;
+import static com.android.hardware.input.Flags.touchpadThreeFingerTapShortcut;
 import static com.android.hardware.input.Flags.touchpadVisualizer;
-import static com.android.input.flags.Flags.enableInputFilterRustImpl;
+import static com.android.hardware.input.Flags.useKeyGestureEventHandler;
 import static com.android.input.flags.Flags.FLAG_KEYBOARD_REPEAT_KEYS;
+import static com.android.input.flags.Flags.enableInputFilterRustImpl;
 import static com.android.input.flags.Flags.keyboardRepeatKeys;
 
 import android.Manifest;
@@ -379,6 +382,15 @@
     }
 
     /**
+     * Returns true if the feature flag for the touchpad three-finger tap shortcut is enabled.
+     *
+     * @hide
+     */
+    public static boolean isTouchpadThreeFingerTapShortcutFeatureFlagEnabled() {
+        return isCustomizableInputGesturesFeatureFlagEnabled() && touchpadThreeFingerTapShortcut();
+    }
+
+    /**
      * Returns true if the feature flag for mouse reverse vertical scrolling is enabled.
      * @hide
      */
@@ -498,6 +510,22 @@
     }
 
     /**
+     * Returns true if three-finger taps on the touchpad should trigger a customizable shortcut
+     * rather than a middle click.
+     *
+     * The returned value only applies to gesture-compatible touchpads.
+     *
+     * @param context The application context.
+     * @return Whether three-finger taps should trigger the shortcut.
+     *
+     * @hide
+     */
+    public static boolean useTouchpadThreeFingerTapShortcut(@NonNull Context context) {
+        // TODO(b/365063048): determine whether to enable the shortcut based on the settings.
+        return isTouchpadThreeFingerTapShortcutFeatureFlagEnabled();
+    }
+
+    /**
      * Whether a pointer icon will be shown over the location of a stylus pointer.
      *
      * @hide
@@ -1105,4 +1133,18 @@
                 Settings.Secure.KEY_REPEAT_DELAY_MS, delayTimeMillis,
                 UserHandle.USER_CURRENT);
     }
+
+    /**
+     * Whether "Customizable key gestures" feature flag is enabled.
+     *
+     * <p>
+     * ‘Customizable key gestures’ is a feature which allows users to customize key based
+     * shortcuts on the physical keyboard.
+     * </p>
+     *
+     * @hide
+     */
+    public static boolean isCustomizableInputGesturesFeatureFlagEnabled() {
+        return enableCustomizableInputGestures() && useKeyGestureEventHandler();
+    }
 }
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index 71c91e9..f9cb94a 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -158,3 +158,10 @@
   description: "Allows privileged focused windows to capture power key events."
   bug: "357144512"
 }
+
+flag {
+  name: "touchpad_three_finger_tap_shortcut"
+  namespace: "input"
+  description: "Turns three-finger touchpad taps into a customizable shortcut."
+  bug: "365063048"
+}
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index c6fd0ee..85cf949 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -1750,8 +1750,8 @@
              *             internals, typically during enrollment.
              * @return the same Builder instance.
              */
-            public @NonNull Builder setData(@Nullable byte[] data) {
-                mData = data;
+            public @NonNull Builder setData(@NonNull byte[] data) {
+                mData = requireNonNull(data, "Data must not be null");
                 return this;
             }
 
diff --git a/core/java/android/hardware/usb/OWNERS b/core/java/android/hardware/usb/OWNERS
index a753f96..37604bc 100644
--- a/core/java/android/hardware/usb/OWNERS
+++ b/core/java/android/hardware/usb/OWNERS
@@ -1,7 +1,7 @@
 # Bug component: 175220
 
-aprasath@google.com
-kumarashishg@google.com
-sarup@google.com
 anothermark@google.com
+febinthattil@google.com
+aprasath@google.com
 badhri@google.com
+kumarashishg@google.com
\ No newline at end of file
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 8c3f0ef..ae83668 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -55,6 +55,7 @@
 import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECTIONLESS_HANDWRITING_ERROR_OTHER;
 import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED;
 import static android.view.inputmethod.Flags.FLAG_CONNECTIONLESS_HANDWRITING;
+import static android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP_API;
 import static android.view.inputmethod.Flags.ctrlShiftShortcut;
 import static android.view.inputmethod.Flags.predictiveBackIme;
 
@@ -4392,6 +4393,39 @@
     }
 
     /**
+     * Called when the requested visibility of a custom IME Switcher button changes.
+     *
+     * <p>When the system provides an IME navigation bar, it may decide to show an IME Switcher
+     * button inside this bar. However, the IME can request hiding the bar provided by the system
+     * with {@code getWindowInsetsController().hide(captionBar())} (the IME navigation bar provides
+     * {@link Type#captionBar() captionBar} insets to the IME window). If the request is successful,
+     * then it becomes the IME's responsibility to provide a custom IME Switcher button in its
+     * input view, with equivalent functionality.</p>
+     *
+     * <p>This custom button is only requested to be visible when the system provides the IME
+     * navigation bar, both the bar and the IME Switcher button inside it should be visible,
+     * but the IME successfully requested to hide the bar. This does not depend on the current
+     * visibility of the IME. It could be called with {@code true} while the IME is hidden, in
+     * which case the IME should prepare to show the button as soon as the IME itself is shown.</p>
+     *
+     * <p>This is only called when the requested visibility changes. The default value is
+     * {@code false} and as such, this will not be called initially if the resulting value is
+     * {@code false}.</p>
+     *
+     * <p>This can be called at any time after {@link #onCreate}, even if the IME is not currently
+     * visible. However, this is not guaranteed to be called before the IME is shown, as it depends
+     * on when the IME requested hiding the IME navigation bar. If the request is sent during
+     * the showing flow (e.g. during {@link #onStartInputView}), this will be called shortly after
+     * {@link #onWindowShown}, but before the first IME frame is drawn.</p>
+     *
+     * @param visible whether the button is requested visible or not.
+     */
+    @FlaggedApi(FLAG_IME_SWITCHER_REVAMP_API)
+    public void onCustomImeSwitcherButtonRequestedVisible(boolean visible) {
+        // Intentionally empty
+    }
+
+    /**
      * Called when the IME switch button was clicked from the client. Depending on the number of
      * enabled IME subtypes, this will either switch to the next IME/subtype, or show the input
      * method picker dialog.
diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java
index b08454d..38be8d9 100644
--- a/core/java/android/inputmethodservice/NavigationBarController.java
+++ b/core/java/android/inputmethodservice/NavigationBarController.java
@@ -41,6 +41,7 @@
 import android.view.WindowInsetsController.Appearance;
 import android.view.animation.Interpolator;
 import android.view.animation.PathInterpolator;
+import android.view.inputmethod.Flags;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.FrameLayout;
 
@@ -178,6 +179,9 @@
 
         private boolean mDrawLegacyNavigationBarBackground;
 
+        /** Whether a custom IME Switcher button should be visible. */
+        private boolean mCustomImeSwitcherVisible;
+
         private final Rect mTempRect = new Rect();
         private final int[] mTempPos = new int[2];
 
@@ -265,6 +269,7 @@
                     // IME navigation bar.
                     boolean visible = insets.isVisible(captionBar());
                     mNavigationBarFrame.setVisibility(visible ? View.VISIBLE : View.GONE);
+                    checkCustomImeSwitcherVisibility();
                 }
                 return view.onApplyWindowInsets(insets);
             });
@@ -491,6 +496,8 @@
                     mShouldShowImeSwitcherWhenImeIsShown;
             mShouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherWhenImeIsShown;
 
+            checkCustomImeSwitcherVisibility();
+
             mService.mWindow.getWindow().getDecorView().getWindowInsetsController()
                     .setImeCaptionBarInsetsHeight(getImeCaptionBarHeight(imeDrawsImeNavBar));
 
@@ -616,12 +623,33 @@
                     && mNavigationBarFrame.getVisibility() == View.VISIBLE;
         }
 
+        /**
+         * Checks if a custom IME Switcher button should be visible, and notifies the IME when this
+         * state changes. This can only be {@code true} if three conditions are met:
+         *
+         * <li>The IME should draw the IME navigation bar.</li>
+         * <li>The IME Switcher button should be visible when the IME is visible.</li>
+         * <li>The IME navigation bar should be visible, but was requested hidden by the IME.</li>
+         */
+        private void checkCustomImeSwitcherVisibility() {
+            if (!Flags.imeSwitcherRevampApi()) {
+                return;
+            }
+            final boolean visible = mImeDrawsImeNavBar && mShouldShowImeSwitcherWhenImeIsShown
+                    && mNavigationBarFrame != null && !isShown();
+            if (visible != mCustomImeSwitcherVisible) {
+                mCustomImeSwitcherVisible = visible;
+                mService.onCustomImeSwitcherButtonRequestedVisible(mCustomImeSwitcherVisible);
+            }
+        }
+
         @Override
         public String toDebugString() {
             return "{mImeDrawsImeNavBar=" + mImeDrawsImeNavBar
                     + " mNavigationBarFrame=" + mNavigationBarFrame
                     + " mShouldShowImeSwitcherWhenImeIsShown="
                     + mShouldShowImeSwitcherWhenImeIsShown
+                    + " mCustomImeSwitcherVisible="  + mCustomImeSwitcherVisible
                     + " mAppearance=0x" + Integer.toHexString(mAppearance)
                     + " mDarkIntensity=" + mDarkIntensity
                     + " mDrawLegacyNavigationBarBackground=" + mDrawLegacyNavigationBarBackground
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index 6c3c285..5ae425f 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -1315,7 +1315,7 @@
         }
 
         synchronized (BatteryUsageStats.class) {
-            if (!sInstances.isEmpty()) {
+            if (sInstances != null && !sInstances.isEmpty()) {
                 Exception callSite = sInstances.entrySet().iterator().next().getValue();
                 int count = sInstances.size();
                 sInstances.clear();
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 13d7e3c..b3aebad 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -40,8 +40,6 @@
 import android.util.Slog;
 import android.view.View;
 
-import com.android.internal.ravenwood.RavenwoodEnvironment;
-
 import dalvik.system.VMRuntime;
 
 import java.lang.annotation.Retention;
@@ -57,10 +55,6 @@
  */
 @RavenwoodKeepWholeClass
 public class Build {
-    static {
-        // Set up the default system properties.
-        RavenwoodEnvironment.ensureRavenwoodInitialized();
-    }
     private static final String TAG = "Build";
 
     /** Value used for when a build property is unknown. */
diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
index 66f4198..6cfbf4e 100644
--- a/core/java/android/os/CombinedMessageQueue/MessageQueue.java
+++ b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
@@ -93,9 +93,7 @@
      * system processes and provides a higher level of concurrency and higher enqueue throughput
      * than the legacy implementation.
      */
-    private static boolean sUseConcurrent;
-
-    private static boolean sUseConcurrentInitialized = false;
+    private boolean mUseConcurrent;
 
     @RavenwoodRedirect
     private native static long nativeInit();
@@ -112,10 +110,7 @@
     private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);
 
     MessageQueue(boolean quitAllowed) {
-        if (!sUseConcurrentInitialized) {
-            sUseConcurrent = UserHandle.isCore(Process.myUid());
-            sUseConcurrentInitialized = true;
-        }
+        mUseConcurrent = UserHandle.isCore(Process.myUid());
         mQuitAllowed = quitAllowed;
         mPtr = nativeInit();
     }
@@ -158,7 +153,7 @@
      * @return True if the looper is idle.
      */
     public boolean isIdle() {
-        if (sUseConcurrent) {
+        if (mUseConcurrent) {
             final long now = SystemClock.uptimeMillis();
 
             if (stackHasMessages(null, 0, null, null, now, mMatchDeliverableMessages, false)) {
@@ -208,7 +203,7 @@
         if (handler == null) {
             throw new NullPointerException("Can't add a null IdleHandler");
         }
-        if (sUseConcurrent) {
+        if (mUseConcurrent) {
             synchronized (mIdleHandlersLock) {
                 mIdleHandlers.add(handler);
             }
@@ -229,7 +224,7 @@
      * @param handler The IdleHandler to be removed.
      */
     public void removeIdleHandler(@NonNull IdleHandler handler) {
-        if (sUseConcurrent) {
+        if (mUseConcurrent) {
             synchronized (mIdleHandlersLock) {
                 mIdleHandlers.remove(handler);
             }
@@ -252,7 +247,7 @@
      * @hide
      */
     public boolean isPolling() {
-        if (sUseConcurrent) {
+        if (mUseConcurrent) {
             // If the loop is quitting then it must not be idling.
             // We can assume mPtr != 0 when sQuitting is false.
             return !((boolean) sQuitting.getVolatile(this)) && nativeIsPolling(mPtr);
@@ -303,7 +298,7 @@
             throw new IllegalArgumentException("listener must not be null");
         }
 
-        if (sUseConcurrent) {
+        if (mUseConcurrent) {
             synchronized (mFileDescriptorRecordsLock) {
                 updateOnFileDescriptorEventListenerLocked(fd, events, listener);
             }
@@ -331,7 +326,7 @@
         if (fd == null) {
             throw new IllegalArgumentException("fd must not be null");
         }
-        if (sUseConcurrent) {
+        if (mUseConcurrent) {
             synchronized (mFileDescriptorRecordsLock) {
                 updateOnFileDescriptorEventListenerLocked(fd, 0, null);
             }
@@ -388,7 +383,7 @@
         final int oldWatchedEvents;
         final OnFileDescriptorEventListener listener;
         final int seq;
-        if (sUseConcurrent) {
+        if (mUseConcurrent) {
             synchronized (mFileDescriptorRecordsLock) {
                 record = mFileDescriptorRecords.get(fd);
                 if (record == null) {
@@ -708,7 +703,7 @@
 
     @UnsupportedAppUsage
     Message next() {
-        if (sUseConcurrent) {
+        if (mUseConcurrent) {
             return nextConcurrent();
         }
 
@@ -834,7 +829,7 @@
             throw new IllegalStateException("Main thread not allowed to quit.");
         }
 
-        if (sUseConcurrent) {
+        if (mUseConcurrent) {
             synchronized (mIdleHandlersLock) {
                 if (sQuitting.compareAndSet(this, false, true)) {
                     if (safe) {
@@ -898,7 +893,7 @@
     private int postSyncBarrier(long when) {
         // Enqueue a new sync barrier token.
         // We don't need to wake the queue because the purpose of a barrier is to stall it.
-        if (sUseConcurrent) {
+        if (mUseConcurrent) {
             final int token = mNextBarrierTokenAtomic.getAndIncrement();
 
             // b/376573804: apps and tests may expect to be able to use reflection
@@ -991,7 +986,7 @@
     public void removeSyncBarrier(int token) {
         // Remove a sync barrier token from the queue.
         // If the queue is no longer stalled by a barrier then wake it.
-        if (sUseConcurrent) {
+        if (mUseConcurrent) {
             boolean removed;
             MessageNode first;
             final MatchBarrierToken matchBarrierToken = new MatchBarrierToken(token);
@@ -1058,7 +1053,7 @@
             throw new IllegalArgumentException("Message must have a target.");
         }
 
-        if (sUseConcurrent) {
+        if (mUseConcurrent) {
             if (msg.isInUse()) {
                 throw new IllegalStateException(msg + " This message is already in use.");
             }
@@ -1187,7 +1182,7 @@
         if (h == null) {
             return false;
         }
-        if (sUseConcurrent) {
+        if (mUseConcurrent) {
             return findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObject,
                     false);
         }
@@ -1219,7 +1214,7 @@
         if (h == null) {
             return false;
         }
-        if (sUseConcurrent) {
+        if (mUseConcurrent) {
             return findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObjectEquals,
                     false);
 
@@ -1253,7 +1248,7 @@
         if (h == null) {
             return false;
         }
-        if (sUseConcurrent) {
+        if (mUseConcurrent) {
             return findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObject,
                     false);
         }
@@ -1285,7 +1280,7 @@
         if (h == null) {
             return false;
         }
-        if (sUseConcurrent) {
+        if (mUseConcurrent) {
             return findOrRemoveMessages(h, -1, null, null, 0, mMatchHandler, false);
         }
         synchronized (this) {
@@ -1304,7 +1299,7 @@
         if (h == null) {
             return;
         }
-        if (sUseConcurrent) {
+        if (mUseConcurrent) {
             findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObject, true);
             return;
         }
@@ -1355,7 +1350,7 @@
             return;
         }
 
-        if (sUseConcurrent) {
+        if (mUseConcurrent) {
             findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObjectEquals, true);
             return;
         }
@@ -1407,7 +1402,7 @@
             return;
         }
 
-        if (sUseConcurrent) {
+        if (mUseConcurrent) {
             findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObject, true);
             return;
         }
@@ -1470,7 +1465,7 @@
             return;
         }
 
-        if (sUseConcurrent) {
+        if (mUseConcurrent) {
             findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObjectEquals, true);
             return;
         }
@@ -1532,7 +1527,7 @@
             return;
         }
 
-        if (sUseConcurrent) {
+        if (mUseConcurrent) {
             findOrRemoveMessages(h, -1, object, null, 0, mMatchHandlerAndObject, true);
             return;
         }
@@ -1594,7 +1589,7 @@
             return;
         }
 
-        if (sUseConcurrent) {
+        if (mUseConcurrent) {
             findOrRemoveMessages(h, -1, object, null, 0, mMatchHandlerAndObjectEquals, true);
             return;
         }
@@ -1742,7 +1737,7 @@
 
     @NeverCompile
     void dump(Printer pw, String prefix, Handler h) {
-        if (sUseConcurrent) {
+        if (mUseConcurrent) {
             long now = SystemClock.uptimeMillis();
             int n = 0;
 
@@ -1803,7 +1798,7 @@
 
     @NeverCompile
     void dumpDebug(ProtoOutputStream proto, long fieldId) {
-        if (sUseConcurrent) {
+        if (mUseConcurrent) {
             final long messageQueueToken = proto.start(fieldId);
 
             StackNode node = (StackNode) sState.getVolatile(this);
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index e80efd2..60eeb2b 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -41,7 +41,6 @@
 import android.net.Uri;
 import android.os.MessageQueue.OnFileDescriptorEventListener;
 import android.ravenwood.annotation.RavenwoodKeepWholeClass;
-import android.ravenwood.annotation.RavenwoodReplace;
 import android.ravenwood.annotation.RavenwoodThrow;
 import android.system.ErrnoException;
 import android.system.Os;
@@ -51,8 +50,6 @@
 import android.util.Log;
 import android.util.Slog;
 
-import com.android.internal.ravenwood.RavenwoodEnvironment;
-
 import dalvik.system.VMRuntime;
 
 import libcore.io.IoUtils;
@@ -1254,15 +1251,10 @@
         }
     }
 
-    @RavenwoodReplace
     private static boolean isAtLeastQ() {
         return (VMRuntime.getRuntime().getTargetSdkVersion() >= Build.VERSION_CODES.Q);
     }
 
-    private static boolean isAtLeastQ$ravenwood() {
-        return RavenwoodEnvironment.workaround().isTargetSdkAtLeastQ();
-    }
-
     private static int ifAtLeastQ(int value) {
         return isAtLeastQ() ? value : 0;
     }
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 71d29af..e728243 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -29,6 +29,11 @@
 import android.annotation.UptimeMillisLong;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build.VERSION_CODES;
+import android.ravenwood.annotation.RavenwoodKeep;
+import android.ravenwood.annotation.RavenwoodKeepPartialClass;
+import android.ravenwood.annotation.RavenwoodRedirect;
+import android.ravenwood.annotation.RavenwoodRedirectionClass;
+import android.ravenwood.annotation.RavenwoodReplace;
 import android.sysprop.MemoryProperties;
 import android.system.ErrnoException;
 import android.system.Os;
@@ -37,8 +42,6 @@
 import android.util.Pair;
 import android.webkit.WebViewZygote;
 
-import com.android.internal.os.SomeArgs;
-import com.android.internal.util.Preconditions;
 import com.android.sdksandbox.flags.Flags;
 
 import dalvik.system.VMDebug;
@@ -55,6 +58,8 @@
 /**
  * Tools for managing OS processes.
  */
+@RavenwoodKeepPartialClass
+@RavenwoodRedirectionClass("Process_ravenwood")
 public class Process {
     private static final String LOG_TAG = "Process";
 
@@ -671,7 +676,6 @@
      */
     public static final ZygoteProcess ZYGOTE_PROCESS = new ZygoteProcess();
 
-
     /**
      * The process name set via {@link #setArgV0(String)}.
      */
@@ -845,47 +849,20 @@
     /**
      * Returns true if the current process is a 64-bit runtime.
      */
-    @android.ravenwood.annotation.RavenwoodKeep
+    @RavenwoodKeep
     public static final boolean is64Bit() {
         return VMRuntime.getRuntime().is64Bit();
     }
 
-    private static volatile ThreadLocal<SomeArgs> sIdentity$ravenwood;
-
-    /** @hide */
-    @android.ravenwood.annotation.RavenwoodKeep
-    public static void init$ravenwood(final int uid, final int pid) {
-        sIdentity$ravenwood = ThreadLocal.withInitial(() -> {
-            final SomeArgs args = SomeArgs.obtain();
-            args.argi1 = uid;
-            args.argi2 = pid;
-            args.argi3 = Long.hashCode(Thread.currentThread().getId());
-            args.argi4 = THREAD_PRIORITY_DEFAULT;
-            args.arg1 = Boolean.TRUE; // backgroundOk
-            return args;
-        });
-    }
-
-    /** @hide */
-    @android.ravenwood.annotation.RavenwoodKeep
-    public static void reset$ravenwood() {
-        sIdentity$ravenwood = null;
-    }
-
     /**
      * Returns the identifier of this process, which can be used with
      * {@link #killProcess} and {@link #sendSignal}.
      */
-    @android.ravenwood.annotation.RavenwoodReplace
+    @RavenwoodKeep
     public static final int myPid() {
         return Os.getpid();
     }
 
-    /** @hide */
-    public static final int myPid$ravenwood() {
-        return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get().argi2;
-    }
-
     /**
      * Returns the identifier of this process' parent.
      * @hide
@@ -899,39 +876,29 @@
      * Returns the identifier of the calling thread, which be used with
      * {@link #setThreadPriority(int, int)}.
      */
-    @android.ravenwood.annotation.RavenwoodReplace
+    @RavenwoodKeep
     public static final int myTid() {
         return Os.gettid();
     }
 
-    /** @hide */
-    public static final int myTid$ravenwood() {
-        return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get().argi3;
-    }
-
     /**
      * Returns the identifier of this process's uid.  This is the kernel uid
      * that the process is running under, which is the identity of its
      * app-specific sandbox.  It is different from {@link #myUserHandle} in that
      * a uid identifies a specific app sandbox in a specific user.
      */
-    @android.ravenwood.annotation.RavenwoodReplace
+    @RavenwoodKeep
     public static final int myUid() {
         return Os.getuid();
     }
 
-    /** @hide */
-    public static final int myUid$ravenwood() {
-        return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get().argi1;
-    }
-
     /**
      * Returns this process's user handle.  This is the
      * user the process is running under.  It is distinct from
      * {@link #myUid()} in that a particular user will have multiple
      * distinct apps running under it each with their own uid.
      */
-    @android.ravenwood.annotation.RavenwoodKeep
+    @RavenwoodKeep
     public static UserHandle myUserHandle() {
         return UserHandle.of(UserHandle.getUserId(myUid()));
     }
@@ -940,7 +907,7 @@
      * Returns whether the given uid belongs to a system core component or not.
      * @hide
      */
-    @android.ravenwood.annotation.RavenwoodKeep
+    @RavenwoodKeep
     public static boolean isCoreUid(int uid) {
         return UserHandle.isCore(uid);
     }
@@ -951,7 +918,7 @@
      * @return Whether the uid corresponds to an application sandbox running in
      *     a specific user.
      */
-    @android.ravenwood.annotation.RavenwoodKeep
+    @RavenwoodKeep
     public static boolean isApplicationUid(int uid) {
         return UserHandle.isApp(uid);
     }
@@ -959,7 +926,7 @@
     /**
      * Returns whether the current process is in an isolated sandbox.
      */
-    @android.ravenwood.annotation.RavenwoodKeep
+    @RavenwoodKeep
     public static final boolean isIsolated() {
         return isIsolated(myUid());
     }
@@ -971,7 +938,7 @@
     @Deprecated
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
             publicAlternatives = "Use {@link #isIsolatedUid(int)} instead.")
-    @android.ravenwood.annotation.RavenwoodKeep
+    @RavenwoodKeep
     public static final boolean isIsolated(int uid) {
         return isIsolatedUid(uid);
     }
@@ -979,7 +946,7 @@
     /**
      * Returns whether the process with the given {@code uid} is an isolated sandbox.
      */
-    @android.ravenwood.annotation.RavenwoodKeep
+    @RavenwoodKeep
     public static final boolean isIsolatedUid(int uid) {
         uid = UserHandle.getAppId(uid);
         return (uid >= FIRST_ISOLATED_UID && uid <= LAST_ISOLATED_UID)
@@ -991,7 +958,7 @@
      * @see android.app.sdksandbox.SdkSandboxManager
      */
     @SuppressLint("UnflaggedApi") // promoting from @SystemApi.
-    @android.ravenwood.annotation.RavenwoodKeep
+    @RavenwoodKeep
     public static final boolean isSdkSandboxUid(int uid) {
         uid = UserHandle.getAppId(uid);
         return (uid >= FIRST_SDK_SANDBOX_UID && uid <= LAST_SDK_SANDBOX_UID);
@@ -1007,7 +974,7 @@
      * @throws IllegalArgumentException if input is not an sdk sandbox uid
      */
     @SuppressLint("UnflaggedApi") // promoting from @SystemApi.
-    @android.ravenwood.annotation.RavenwoodKeep
+    @RavenwoodKeep
     public static final int getAppUidForSdkSandboxUid(int uid) {
         if (!isSdkSandboxUid(uid)) {
             throw new IllegalArgumentException("Input UID is not an SDK sandbox UID");
@@ -1023,7 +990,7 @@
      */
     @SystemApi(client = MODULE_LIBRARIES)
     @TestApi
-    @android.ravenwood.annotation.RavenwoodKeep
+    @RavenwoodKeep
     // TODO(b/318651609): Deprecate once Process#getSdkSandboxUidForAppUid is rolled out to 100%
     public static final int toSdkSandboxUid(int uid) {
         return uid + (FIRST_SDK_SANDBOX_UID - FIRST_APPLICATION_UID);
@@ -1039,7 +1006,7 @@
      * @throws IllegalArgumentException if input is not an app uid
      */
     @FlaggedApi(Flags.FLAG_SDK_SANDBOX_UID_TO_APP_UID_API)
-    @android.ravenwood.annotation.RavenwoodKeep
+    @RavenwoodKeep
     public static final int getSdkSandboxUidForAppUid(int uid) {
         if (!isApplicationUid(uid)) {
             throw new IllegalArgumentException("Input UID is not an app UID");
@@ -1050,7 +1017,7 @@
     /**
      * Returns whether the current process is a sdk sandbox process.
      */
-    @android.ravenwood.annotation.RavenwoodKeep
+    @RavenwoodKeep
     public static final boolean isSdkSandbox() {
         return isSdkSandboxUid(myUid());
     }
@@ -1127,28 +1094,11 @@
      * not have permission to modify the given thread, or to use the given
      * priority.
      */
-    @android.ravenwood.annotation.RavenwoodReplace
+    @RavenwoodRedirect
     public static final native void setThreadPriority(int tid,
             @IntRange(from = -20, to = THREAD_PRIORITY_LOWEST) int priority)
             throws IllegalArgumentException, SecurityException;
 
-    /** @hide */
-    public static final void setThreadPriority$ravenwood(int tid, int priority) {
-        final SomeArgs args =
-                Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get();
-        if (args.argi3 == tid) {
-            boolean backgroundOk = (args.arg1 == Boolean.TRUE);
-            if (priority >= THREAD_PRIORITY_BACKGROUND && !backgroundOk) {
-                throw new IllegalArgumentException(
-                        "Priority " + priority + " blocked by setCanSelfBackground()");
-            }
-            args.argi4 = priority;
-        } else {
-            throw new UnsupportedOperationException(
-                    "Cross-thread priority management not yet available in Ravenwood");
-        }
-    }
-
     /**
      * Call with 'false' to cause future calls to {@link #setThreadPriority(int)} to
      * throw an exception if passed a background-level thread priority.  This is only
@@ -1156,16 +1106,9 @@
      *
      * @hide
      */
-    @android.ravenwood.annotation.RavenwoodReplace
+    @RavenwoodRedirect
     public static final native void setCanSelfBackground(boolean backgroundOk);
 
-    /** @hide */
-    public static final void setCanSelfBackground$ravenwood(boolean backgroundOk) {
-        final SomeArgs args =
-                Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get();
-        args.arg1 = Boolean.valueOf(backgroundOk);
-    }
-
     /**
      * Sets the scheduling group for a thread.
      * @hide
@@ -1294,13 +1237,12 @@
      *
      * @see #setThreadPriority(int, int)
      */
-    @android.ravenwood.annotation.RavenwoodReplace
+    @RavenwoodReplace
     public static final native void setThreadPriority(
             @IntRange(from = -20, to = THREAD_PRIORITY_LOWEST) int priority)
             throws IllegalArgumentException, SecurityException;
 
-    /** @hide */
-    public static final void setThreadPriority$ravenwood(int priority) {
+    private static void setThreadPriority$ravenwood(int priority) {
         setThreadPriority(myTid(), priority);
     }
 
@@ -1317,23 +1259,11 @@
      * @throws IllegalArgumentException Throws IllegalArgumentException if
      * <var>tid</var> does not exist.
      */
-    @android.ravenwood.annotation.RavenwoodReplace
+    @RavenwoodRedirect
     @IntRange(from = -20, to = THREAD_PRIORITY_LOWEST)
     public static final native int getThreadPriority(int tid)
             throws IllegalArgumentException;
 
-    /** @hide */
-    public static final int getThreadPriority$ravenwood(int tid) {
-        final SomeArgs args =
-                Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get();
-        if (args.argi3 == tid) {
-            return args.argi4;
-        } else {
-            throw new UnsupportedOperationException(
-                    "Cross-thread priority management not yet available in Ravenwood");
-        }
-    }
-
     /**
      * Return the current scheduling policy of a thread, based on Linux.
      *
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 90993e1..edeb75b 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -365,7 +365,7 @@
     public static final int NETWORK_POLICY_REJECT = 2;
 
     /**
-     * Detect explicit calls to {@link Runtime#gc()}.
+     * Detects explicit calls to {@link Runtime#gc()}.
      */
     @ChangeId
     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@@ -501,7 +501,7 @@
             private Executor mExecutor;
 
             /**
-             * Create a Builder that detects nothing and has no violations. (but note that {@link
+             * Creates a Builder that detects nothing and has no violations. (but note that {@link
              * #build} will default to enabling {@link #penaltyLog} if no other penalties are
              * specified)
              */
@@ -509,7 +509,7 @@
                 mMask = 0;
             }
 
-            /** Initialize a Builder from an existing ThreadPolicy. */
+            /** Initializes a Builder from an existing ThreadPolicy. */
             public Builder(ThreadPolicy policy) {
                 mMask = policy.mask;
                 mListener = policy.mListener;
@@ -517,7 +517,7 @@
             }
 
             /**
-             * Detect everything that's potentially suspect.
+             * Detects everything that's potentially suspect.
              *
              * <p>As of the Gingerbread release this includes network and disk operations but will
              * likely expand in future releases.
@@ -544,52 +544,52 @@
                 return this;
             }
 
-            /** Disable the detection of everything. */
+            /** Disables the detection of everything. */
             public @NonNull Builder permitAll() {
                 return disable(DETECT_THREAD_ALL);
             }
 
-            /** Enable detection of network operations. */
+            /** Enables detection of network operations. */
             public @NonNull Builder detectNetwork() {
                 return enable(DETECT_THREAD_NETWORK);
             }
 
-            /** Disable detection of network operations. */
+            /** Disables detection of network operations. */
             public @NonNull Builder permitNetwork() {
                 return disable(DETECT_THREAD_NETWORK);
             }
 
-            /** Enable detection of disk reads. */
+            /** Enables detection of disk reads. */
             public @NonNull Builder detectDiskReads() {
                 return enable(DETECT_THREAD_DISK_READ);
             }
 
-            /** Disable detection of disk reads. */
+            /** Disables detection of disk reads. */
             public @NonNull Builder permitDiskReads() {
                 return disable(DETECT_THREAD_DISK_READ);
             }
 
-            /** Enable detection of slow calls. */
+            /** Enables detection of slow calls. */
             public @NonNull Builder detectCustomSlowCalls() {
                 return enable(DETECT_THREAD_CUSTOM);
             }
 
-            /** Disable detection of slow calls. */
+            /** Disables detection of slow calls. */
             public @NonNull Builder permitCustomSlowCalls() {
                 return disable(DETECT_THREAD_CUSTOM);
             }
 
-            /** Disable detection of mismatches between defined resource types and getter calls. */
+            /** Disables detection of mismatches between defined resource types and getter calls. */
             public @NonNull Builder permitResourceMismatches() {
                 return disable(DETECT_THREAD_RESOURCE_MISMATCH);
             }
 
-            /** Detect unbuffered input/output operations. */
+            /** Detects unbuffered input/output operations. */
             public @NonNull Builder detectUnbufferedIo() {
                 return enable(DETECT_THREAD_UNBUFFERED_IO);
             }
 
-            /** Disable detection of unbuffered input/output operations. */
+            /** Disables detection of unbuffered input/output operations. */
             public @NonNull Builder permitUnbufferedIo() {
                 return disable(DETECT_THREAD_UNBUFFERED_IO);
             }
@@ -610,32 +610,32 @@
                 return enable(DETECT_THREAD_RESOURCE_MISMATCH);
             }
 
-            /** Enable detection of disk writes. */
+            /** Enables detection of disk writes. */
             public @NonNull Builder detectDiskWrites() {
                 return enable(DETECT_THREAD_DISK_WRITE);
             }
 
-            /** Disable detection of disk writes. */
+            /** Disables detection of disk writes. */
             public @NonNull Builder permitDiskWrites() {
                 return disable(DETECT_THREAD_DISK_WRITE);
             }
 
             /**
-             * Detect calls to {@link Runtime#gc()}.
+             * Detects calls to {@link Runtime#gc()}.
              */
             public @NonNull Builder detectExplicitGc() {
                 return enable(DETECT_THREAD_EXPLICIT_GC);
             }
 
             /**
-             * Disable detection of calls to {@link Runtime#gc()}.
+             * Disables detection of calls to {@link Runtime#gc()}.
              */
             public @NonNull Builder permitExplicitGc() {
                 return disable(DETECT_THREAD_EXPLICIT_GC);
             }
 
             /**
-             * Show an annoying dialog to the developer on detected violations, rate-limited to be
+             * Shows an annoying dialog to the developer on detected violations, rate-limited to be
              * only a little annoying.
              */
             public @NonNull Builder penaltyDialog() {
@@ -643,7 +643,7 @@
             }
 
             /**
-             * Crash the whole process on violation. This penalty runs at the end of all enabled
+             * Crashes the whole process on violation. This penalty runs at the end of all enabled
              * penalties so you'll still get see logging or other violations before the process
              * dies.
              *
@@ -655,7 +655,7 @@
             }
 
             /**
-             * Crash the whole process on any network usage. Unlike {@link #penaltyDeath}, this
+             * Crashes the whole process on any network usage. Unlike {@link #penaltyDeath}, this
              * penalty runs <em>before</em> anything else. You must still have called {@link
              * #detectNetwork} to enable this.
              *
@@ -665,18 +665,18 @@
                 return enable(PENALTY_DEATH_ON_NETWORK);
             }
 
-            /** Flash the screen during a violation. */
+            /** Flashes the screen during a violation. */
             public @NonNull Builder penaltyFlashScreen() {
                 return enable(PENALTY_FLASH);
             }
 
-            /** Log detected violations to the system log. */
+            /** Logs detected violations to the system log. */
             public @NonNull Builder penaltyLog() {
                 return enable(PENALTY_LOG);
             }
 
             /**
-             * Enable detected violations log a stacktrace and timing data to the {@link
+             * Enables detected violations log a stacktrace and timing data to the {@link
              * android.os.DropBoxManager DropBox} on policy violation. Intended mostly for platform
              * integrators doing beta user field data collection.
              */
@@ -685,7 +685,7 @@
             }
 
             /**
-             * Call #{@link OnThreadViolationListener#onThreadViolation(Violation)} on specified
+             * Calls #{@link OnThreadViolationListener#onThreadViolation(Violation)} on specified
              * executor every violation.
              */
             public @NonNull Builder penaltyListener(
@@ -715,7 +715,7 @@
             }
 
             /**
-             * Construct the ThreadPolicy instance.
+             * Constructs the ThreadPolicy instance.
              *
              * <p>Note: if no penalties are enabled before calling <code>build</code>, {@link
              * #penaltyLog} is implicitly set.
@@ -805,7 +805,7 @@
                 mMask = 0;
             }
 
-            /** Build upon an existing VmPolicy. */
+            /** Builds upon an existing VmPolicy. */
             public Builder(VmPolicy base) {
                 mMask = base.mask;
                 mClassInstanceLimitNeedCow = true;
@@ -815,7 +815,7 @@
             }
 
             /**
-             * Set an upper bound on how many instances of a class can be in memory at once. Helps
+             * Sets an upper bound on how many instances of a class can be in memory at once. Helps
              * to prevent object leaks.
              */
             public @NonNull Builder setClassInstanceLimit(Class klass, int instanceLimit) {
@@ -838,7 +838,7 @@
                 return this;
             }
 
-            /** Detect leaks of {@link android.app.Activity} subclasses. */
+            /** Detects leaks of {@link android.app.Activity} subclasses. */
             public @NonNull Builder detectActivityLeaks() {
                 return enable(DETECT_VM_ACTIVITY_LEAKS);
             }
@@ -852,7 +852,7 @@
             }
 
             /**
-             * Detect reflective usage of APIs that are not part of the public Android SDK.
+             * Detects reflective usage of APIs that are not part of the public Android SDK.
              *
              * <p>Note that any non-SDK APIs that this processes accesses before this detection is
              * enabled may not be detected. To ensure that all such API accesses are detected,
@@ -863,7 +863,7 @@
             }
 
             /**
-             * Permit reflective usage of APIs that are not part of the public Android SDK. Note
+             * Permits reflective usage of APIs that are not part of the public Android SDK. Note
              * that this <b>only</b> affects {@code StrictMode}, the underlying runtime may
              * continue to restrict or warn on access to methods that are not part of the
              * public SDK.
@@ -873,7 +873,7 @@
             }
 
             /**
-             * Detect everything that's potentially suspect.
+             * Detects everything that's potentially suspect.
              *
              * <p>In the Honeycomb release this includes leaks of SQLite cursors, Activities, and
              * other closable objects but will likely expand in future releases.
@@ -924,8 +924,8 @@
             }
 
             /**
-             * Detect when an {@link android.database.sqlite.SQLiteCursor} or other SQLite object is
-             * finalized without having been closed.
+             * Detects when an {@link android.database.sqlite.SQLiteCursor} or other SQLite
+             * object is finalized without having been closed.
              *
              * <p>You always want to explicitly close your SQLite cursors to avoid unnecessary
              * database contention and temporary memory leaks.
@@ -935,8 +935,8 @@
             }
 
             /**
-             * Detect when an {@link java.io.Closeable} or other object with an explicit termination
-             * method is finalized without having been closed.
+             * Detects when an {@link java.io.Closeable} or other object with an explicit
+             * termination method is finalized without having been closed.
              *
              * <p>You always want to explicitly close such objects to avoid unnecessary resources
              * leaks.
@@ -946,16 +946,16 @@
             }
 
             /**
-             * Detect when a {@link BroadcastReceiver} or {@link ServiceConnection} is leaked during
-             * {@link Context} teardown.
+             * Detects when a {@link BroadcastReceiver} or {@link ServiceConnection} is leaked
+             * during {@link Context} teardown.
              */
             public @NonNull Builder detectLeakedRegistrationObjects() {
                 return enable(DETECT_VM_REGISTRATION_LEAKS);
             }
 
             /**
-             * Detect when the calling application exposes a {@code file://} {@link android.net.Uri}
-             * to another app.
+             * Detects when the calling application exposes a {@code file://}
+             * {@link android.net.Uri} to another app.
              *
              * <p>This exposure is discouraged since the receiving app may not have access to the
              * shared path. For example, the receiving app may not have requested the {@link
@@ -973,9 +973,9 @@
             }
 
             /**
-             * Detect any network traffic from the calling app which is not wrapped in SSL/TLS. This
-             * can help you detect places that your app is inadvertently sending cleartext data
-             * across the network.
+             * Detects any network traffic from the calling app which is not wrapped in SSL/TLS.
+             * This can help you detect places that your app is inadvertently sending cleartext
+             * data across the network.
              *
              * <p>Using {@link #penaltyDeath()} or {@link #penaltyDeathOnCleartextNetwork()} will
              * block further traffic on that socket to prevent accidental data leakage, in addition
@@ -992,7 +992,7 @@
             }
 
             /**
-             * Detect when the calling application sends a {@code content://} {@link
+             * Detects when the calling application sends a {@code content://} {@link
              * android.net.Uri} to another app without setting {@link
              * Intent#FLAG_GRANT_READ_URI_PERMISSION} or {@link
              * Intent#FLAG_GRANT_WRITE_URI_PERMISSION}.
@@ -1008,7 +1008,7 @@
             }
 
             /**
-             * Detect any sockets in the calling app which have not been tagged using {@link
+             * Detects any sockets in the calling app which have not been tagged using {@link
              * TrafficStats}. Tagging sockets can help you investigate network usage inside your
              * app, such as a narrowing down heavy usage to a specific library or component.
              *
@@ -1028,7 +1028,7 @@
             }
 
             /**
-             * Detect any implicit reliance on Direct Boot automatic filtering
+             * Detects any implicit reliance on Direct Boot automatic filtering
              * of {@link PackageManager} values. Violations are only triggered
              * when implicit calls are made while the user is locked.
              * <p>
@@ -1051,7 +1051,7 @@
             }
 
             /**
-             * Detect access to filesystem paths stored in credential protected
+             * Detects access to filesystem paths stored in credential protected
              * storage areas while the user is locked.
              * <p>
              * When a user is locked, credential protected storage is
@@ -1072,7 +1072,7 @@
             }
 
             /**
-             * Detect attempts to invoke a method on a {@link Context} that is not suited for such
+             * Detects attempts to invoke a method on a {@link Context} that is not suited for such
              * operation.
              * <p>An example of this is trying to obtain an instance of UI service (e.g.
              * {@link android.view.WindowManager}) from a non-visual {@link Context}. This is not
@@ -1086,7 +1086,7 @@
             }
 
             /**
-             * Disable detection of incorrect context use.
+             * Disables detection of incorrect context use.
              *
              * @see #detectIncorrectContextUse()
              *
@@ -1098,7 +1098,7 @@
             }
 
             /**
-             * Detect when your app sends an unsafe {@link Intent}.
+             * Detects when your app sends an unsafe {@link Intent}.
              * <p>
              * Violations may indicate security vulnerabilities in the design of
              * your app, where a malicious app could trick you into granting
@@ -1139,7 +1139,7 @@
             }
 
             /**
-             * Permit your app to launch any {@link Intent} which originated
+             * Permits your app to launch any {@link Intent} which originated
              * from outside your app.
              * <p>
              * Disabling this check is <em>strongly discouraged</em>, as
@@ -1214,13 +1214,13 @@
                 return enable(PENALTY_DEATH_ON_FILE_URI_EXPOSURE);
             }
 
-            /** Log detected violations to the system log. */
+            /** Logs detected violations to the system log. */
             public @NonNull Builder penaltyLog() {
                 return enable(PENALTY_LOG);
             }
 
             /**
-             * Enable detected violations log a stacktrace and timing data to the {@link
+             * Enables detected violations log a stacktrace and timing data to the {@link
              * android.os.DropBoxManager DropBox} on policy violation. Intended mostly for platform
              * integrators doing beta user field data collection.
              */
@@ -1229,7 +1229,7 @@
             }
 
             /**
-             * Call #{@link OnVmViolationListener#onVmViolation(Violation)} on every violation.
+             * Calls #{@link OnVmViolationListener#onVmViolation(Violation)} on every violation.
              */
             public @NonNull Builder penaltyListener(
                     @NonNull Executor executor, @NonNull OnVmViolationListener listener) {
@@ -1258,7 +1258,7 @@
             }
 
             /**
-             * Construct the VmPolicy instance.
+             * Constructs the VmPolicy instance.
              *
              * <p>Note: if no penalties are enabled before calling <code>build</code>, {@link
              * #penaltyLog} is implicitly set.
@@ -1474,7 +1474,7 @@
     }
 
     /**
-     * Determine if the given app is "bundled" as part of the system image. These bundled apps are
+     * Determines if the given app is "bundled" as part of the system image. These bundled apps are
      * developed in lock-step with the OS, and they aren't updated outside of an OTA, so we want to
      * chase any {@link StrictMode} regressions by enabling detection when running on {@link
      * Build#IS_USERDEBUG} or {@link Build#IS_ENG} builds.
@@ -1512,7 +1512,7 @@
     }
 
     /**
-     * Initialize default {@link ThreadPolicy} for the current thread.
+     * Initializes default {@link ThreadPolicy} for the current thread.
      *
      * @hide
      */
@@ -1547,7 +1547,7 @@
     }
 
     /**
-     * Initialize default {@link VmPolicy} for the current VM.
+     * Initializes default {@link VmPolicy} for the current VM.
      *
      * @hide
      */
@@ -2244,7 +2244,7 @@
     }
 
     /**
-     * Enable the recommended StrictMode defaults, with violations just being logged.
+     * Enables the recommended StrictMode defaults, with violations just being logged.
      *
      * <p>This catches disk and network access on the main thread, as well as leaked SQLite cursors
      * and unclosed resources. This is simply a wrapper around {@link #setVmPolicy} and {@link
@@ -2545,7 +2545,7 @@
     private static final SparseLongArray sRealLastVmViolationTime = new SparseLongArray();
 
     /**
-     * Clamp the given map by removing elements with timestamp older than the given retainSince.
+     * Clamps the given map by removing elements with timestamp older than the given retainSince.
      */
     private static void clampViolationTimeMap(final @NonNull SparseLongArray violationTime,
             final long retainSince) {
@@ -2812,7 +2812,7 @@
             };
 
     /**
-     * Enter a named critical span (e.g. an animation)
+     * Enters a named critical span (e.g. an animation)
      *
      * <p>The name is an arbitary label (or tag) that will be applied to any strictmode violation
      * that happens while this span is active. You must call finish() on the span when done.
@@ -3056,7 +3056,7 @@
         /** If this is a instance count violation, the number of instances in memory, else -1. */
         public long numInstances = -1;
 
-        /** Create an instance of ViolationInfo initialized from an exception. */
+        /** Creates an instance of ViolationInfo initialized from an exception. */
         ViolationInfo(Violation tr, int penaltyMask) {
             this.mViolation = tr;
             this.mPenaltyMask = penaltyMask;
@@ -3131,8 +3131,8 @@
         }
 
         /**
-         * Add a {@link Throwable} from the current process that caused the underlying violation. We
-         * only preserve the stack trace elements.
+         * Adds a {@link Throwable} from the current process that caused the underlying violation.
+         * We only preserve the stack trace elements.
          *
          * @hide
          */
@@ -3160,14 +3160,14 @@
             return result;
         }
 
-        /** Create an instance of ViolationInfo initialized from a Parcel. */
+        /** Creates an instance of ViolationInfo initialized from a Parcel. */
         @UnsupportedAppUsage
         public ViolationInfo(Parcel in) {
             this(in, false);
         }
 
         /**
-         * Create an instance of ViolationInfo initialized from a Parcel.
+         * Creates an instance of ViolationInfo initialized from a Parcel.
          *
          * @param unsetGatheringBit if true, the caller is the root caller and the gathering penalty
          *     should be removed.
@@ -3203,7 +3203,7 @@
             tags = in.readStringArray();
         }
 
-        /** Save a ViolationInfo instance to a parcel. */
+        /** Saves a ViolationInfo instance to a parcel. */
         @Override
         public void writeToParcel(Parcel dest, int flags) {
             dest.writeSerializable(mViolation);
@@ -3248,7 +3248,7 @@
             }
         }
 
-        /** Dump a ViolationInfo instance to a Printer. */
+        /** Dumps a ViolationInfo instance to a Printer. */
         public void dump(Printer pw, String prefix) {
             pw.println(prefix + "stackTrace: " + getStackTrace());
             pw.println(prefix + "penalty: " + mPenaltyMask);
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 6a49322..9e0d0e1 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -53,6 +53,24 @@
 }
 
 flag {
+    name: "enhanced_confirmation_in_call_apis_enabled"
+    is_exported: true
+    is_fixed_read_only: true
+    namespace: "permissions"
+    description: "enable enhanced confirmation incall apis"
+    bug: "310220212"
+}
+
+flag {
+    name: "unknown_call_package_install_blocking_enabled"
+    is_exported: true
+    is_fixed_read_only: true
+    namespace: "permissions"
+    description: "enable the blocking of certain app installs during an unknown call"
+    bug: "310220212"
+}
+
+flag {
     name: "op_enable_mobile_data_by_user"
     is_exported: true
     namespace: "permissions"
@@ -332,3 +350,38 @@
     description: "Enables ExtServices to leverage TextClassifier for OTP detection"
     bug: "351976749"
 }
+
+flag {
+    name: "health_connect_backup_restore_permission_enabled"
+    is_fixed_read_only: true
+    namespace: "health_connect"
+    description: "This flag protects the permission that is required to call Health Connect backup and restore apis"
+    bug: "376014879" # android_fr bug
+}
+
+flag {
+    name: "enable_aiai_proxied_text_classifiers"
+    is_fixed_read_only: true
+    is_exported: true
+    namespace: "permissions"
+    description: "Enables the AiAi to utilize the default OTP text classifier that is also used by ExtServices"
+    bug: "377229653"
+}
+
+flag {
+    name: "enable_sqlite_appops_accesses"
+    is_fixed_read_only: true
+    is_exported: true
+    namespace: "permissions"
+    description: "Enables SQlite for recording discrete and historical AppOp accesses"
+    bug: "377584611"
+}
+
+flag {
+    name: "ranging_permission_enabled"
+    is_fixed_read_only: true
+    is_exported: true
+    namespace: "uwb"
+    description: "This fixed read-only flag is used to enable new ranging permission for all ranging use cases."
+    bug: "370977414"
+}
diff --git a/core/java/android/print/OWNERS b/core/java/android/print/OWNERS
index 0809de2..ce79f5d 100644
--- a/core/java/android/print/OWNERS
+++ b/core/java/android/print/OWNERS
@@ -2,3 +2,4 @@
 
 anothermark@google.com
 kumarashishg@google.com
+bmgordon@google.com
diff --git a/core/java/android/printservice/OWNERS b/core/java/android/printservice/OWNERS
index 0809de2..ce79f5d 100644
--- a/core/java/android/printservice/OWNERS
+++ b/core/java/android/printservice/OWNERS
@@ -2,3 +2,4 @@
 
 anothermark@google.com
 kumarashishg@google.com
+bmgordon@google.com
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 2e660fc..7d79fd3 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -1164,7 +1164,7 @@
     public boolean startRecognition(@RecognitionFlags int recognitionFlags) {
         if (DBG) Slog.d(TAG, "startRecognition(" + recognitionFlags + ")");
         synchronized (mLock) {
-            return startRecognitionLocked(recognitionFlags, null /* data */) == STATUS_OK;
+            return startRecognitionLocked(recognitionFlags, /* data= */new byte[0]) == STATUS_OK;
         }
     }
 
@@ -1496,8 +1496,8 @@
     }
 
     @GuardedBy("mLock")
-    private int startRecognitionLocked(int recognitionFlags,
-            @Nullable byte[] data) {
+    @SuppressWarnings("FlaggedApi") // RecognitionConfig.Builder is available internally.
+    private int startRecognitionLocked(int recognitionFlags, @NonNull byte[] data) {
         if (DBG) {
             Slog.d(TAG, "startRecognition("
                     + recognitionFlags
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index e830d89..02923ed 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -202,3 +202,10 @@
   description: "Deprecate the Paint#elegantTextHeight API and stick it to true"
   bug: "349519475"
 }
+
+flag {
+  name: "vertical_text_layout"
+  namespace: "text"
+  description: "Make Paint class work for vertical layout text."
+  bug: "355296926"
+}
diff --git a/core/java/android/util/Log.java b/core/java/android/util/Log.java
index 8358b9a..1dd9d46 100644
--- a/core/java/android/util/Log.java
+++ b/core/java/android/util/Log.java
@@ -75,8 +75,7 @@
 @android.ravenwood.annotation.RavenwoodClassLoadHook(
         "com.android.platform.test.ravenwood.runtimehelper.ClassLoadHook.onClassLoaded")
 // Uncomment the following annotation to switch to the Java substitution version.
-//@android.ravenwood.annotation.RavenwoodNativeSubstitutionClass(
-//        "com.android.platform.test.ravenwood.nativesubstitution.Log_host")
+@android.ravenwood.annotation.RavenwoodRedirectionClass("Log_host")
 public final class Log {
     /** @hide */
     @IntDef({ASSERT, ERROR, WARN, INFO, DEBUG, VERBOSE})
@@ -250,6 +249,7 @@
      *         tag limit of concern after this API level.
      */
     @FastNative
+    @android.ravenwood.annotation.RavenwoodRedirect
     public static native boolean isLoggable(@Nullable String tag, @Level int level);
 
     /**
@@ -425,6 +425,7 @@
      * @hide
      */
     @UnsupportedAppUsage
+    @android.ravenwood.annotation.RavenwoodRedirect
     public static native int println_native(int bufID, int priority, String tag, String msg);
 
     /**
@@ -452,6 +453,7 @@
      * Return the maximum payload the log daemon accepts without truncation.
      * @return LOGGER_ENTRY_MAX_PAYLOAD.
      */
+    @android.ravenwood.annotation.RavenwoodRedirect
     private static native int logger_entry_max_payload_native();
 
     /**
diff --git a/core/java/android/view/HapticFeedbackConstants.java b/core/java/android/view/HapticFeedbackConstants.java
index 1fe06d4..66d64d7 100644
--- a/core/java/android/view/HapticFeedbackConstants.java
+++ b/core/java/android/view/HapticFeedbackConstants.java
@@ -176,7 +176,7 @@
 
     /**
      * The user is executing a swipe/drag-style gesture, such as pull-to-refresh, where the
-     * gesture action is “eligible” at a certain threshold of movement, and can be cancelled by
+     * gesture action is "eligible" at a certain threshold of movement, and can be cancelled by
      * moving back past the threshold. This constant indicates that the user's motion has just
      * passed the threshold for the action to be activated on release.
      *
@@ -186,7 +186,7 @@
 
     /**
      * The user is executing a swipe/drag-style gesture, such as pull-to-refresh, where the
-     * gesture action is “eligible” at a certain threshold of movement, and can be cancelled by
+     * gesture action is "eligible" at a certain threshold of movement, and can be cancelled by
      * moving back past the threshold. This constant indicates that the user's motion has just
      * re-crossed back "under" the threshold for the action to be activated, meaning the gesture is
      * currently in a cancelled state.
diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java
index 7877352..acbd95bf 100644
--- a/core/java/android/view/InsetsSourceControl.java
+++ b/core/java/android/view/InsetsSourceControl.java
@@ -212,8 +212,7 @@
                 && mInitiallyVisible == that.mInitiallyVisible
                 && mSurfacePosition.equals(that.mSurfacePosition)
                 && mInsetsHint.equals(that.mInsetsHint)
-                && mSkipAnimationOnce == that.mSkipAnimationOnce
-                && Objects.equals(mImeStatsToken, that.mImeStatsToken);
+                && mSkipAnimationOnce == that.mSkipAnimationOnce;
     }
 
     @Override
diff --git a/core/java/android/view/RoundScrollbarRenderer.java b/core/java/android/view/RoundScrollbarRenderer.java
index 59c2598..5e1eada 100644
--- a/core/java/android/view/RoundScrollbarRenderer.java
+++ b/core/java/android/view/RoundScrollbarRenderer.java
@@ -35,7 +35,9 @@
  * @hide
  */
 public class RoundScrollbarRenderer {
-    private static final String BLUECHIP_ENABLED_SYSPROP = "persist.cw_build.bluechip.enabled";
+    /** @hide */
+    public static final String BLUECHIP_ENABLED_SYSPROP = "persist.cw_build.bluechip.enabled";
+
     // The range of the scrollbar position represented as an angle in degrees.
     private static final float SCROLLBAR_ANGLE_RANGE = 28.8f;
     private static final float MAX_SCROLLBAR_ANGLE_SWIPE = 26.3f; // 90%
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index b8b22e2..df54d31 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -312,6 +312,7 @@
     private static native void nativeNotifyShutdown();
     private static native void nativeSetLuts(long transactionObj, long nativeObject,
             float[] buffers, int[] slots, int[] dimensions, int[] sizes, int[] samplingKeys);
+    private static native void nativeEnableDebugLogCallPoints(long transactionObj);
 
     /**
      * Transforms that can be applied to buffers as they are displayed to a window.
@@ -4605,7 +4606,6 @@
         }
 
         /**
-         * TODO(b/366484871): To be removed once we have some logging in native
          * This is called when BlastBufferQueue.mergeWithNextTransaction() is called from java, and
          * for the purposes of logging that path.
          */
@@ -4616,6 +4616,7 @@
                 if (mCalls != null) {
                     mCalls.clear();
                 }
+                nativeEnableDebugLogCallPoints(mNativeObject);
             }
         }
 
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index e49eec6..5ee229f 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -30,6 +30,7 @@
 import static android.view.Surface.FRAME_RATE_COMPATIBILITY_GTE;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED;
+import static android.view.accessibility.Flags.FLAG_DEPRECATE_ACCESSIBILITY_ANNOUNCEMENT_APIS;
 import static android.view.accessibility.Flags.FLAG_SUPPLEMENTAL_DESCRIPTION;
 import static android.view.accessibility.Flags.removeChildHoverCheckForTouchExploration;
 import static android.view.accessibility.Flags.supplementalDescription;
@@ -42,6 +43,7 @@
 import static android.view.flags.Flags.FLAG_SENSITIVE_CONTENT_APP_PROTECTION_API;
 import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY;
 import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
+import static android.view.flags.Flags.calculateBoundsInParentFromBoundsInScreen;
 import static android.view.flags.Flags.enableUseMeasureCacheDuringForceLayout;
 import static android.view.flags.Flags.sensitiveContentAppProtection;
 import static android.view.flags.Flags.toolkitFrameRateAnimationBugfix25q1;
@@ -970,6 +972,13 @@
     private static boolean sAlwaysRemeasureExactly = false;
 
     /**
+     * When true calculates the bounds in parent from bounds in screen relative to its parents.
+     * This addresses the deprecated API (setBoundsInParent) in Compose, which causes empty
+     * getBoundsInParent call for Compose apps.
+     */
+    private static boolean sCalculateBoundsInParentFromBoundsInScreenFlagValue = false;
+
+    /**
      * When true makes it possible to use onMeasure caches also when the force layout flag is
      * enabled. This helps avoiding multiple measures in the same frame with the same dimensions.
      */
@@ -2561,6 +2570,8 @@
 
         sToolkitSetFrameRateReadOnlyFlagValue = toolkitSetFrameRateReadOnly();
         sToolkitMetricsForFrameRateDecisionFlagValue = toolkitMetricsForFrameRateDecision();
+        sCalculateBoundsInParentFromBoundsInScreenFlagValue =
+                calculateBoundsInParentFromBoundsInScreen();
         sUseMeasureCacheDuringForceLayoutFlagValue = enableUseMeasureCacheDuringForceLayout();
     }
 
@@ -8941,44 +8952,45 @@
     }
 
     /**
-     * Convenience method for sending a {@link AccessibilityEvent#TYPE_ANNOUNCEMENT}
-     * {@link AccessibilityEvent} to suggest that an accessibility service announce the
-     * specified text to its users.
-     * <p>
-     * Note: The event generated with this API carries no semantic meaning, and is appropriate only
-     * in exceptional situations. Apps can generally achieve correct behavior for accessibility by
-     * accurately supplying the semantics of their UI.
-     * They should not need to specify what exactly is announced to users.
+     * Convenience method for sending a {@link AccessibilityEvent#TYPE_ANNOUNCEMENT} {@link
+     * AccessibilityEvent} to suggest that an accessibility service announce the specified text to
+     * its users.
      *
-     * <p>
-     * In general, only announce transitions and don't generate a confirmation message for simple
-     * actions like a button press. Label your controls concisely and precisely instead, and for
-     * significant UI changes like window changes, use
-     * {@link android.app.Activity#setTitle(CharSequence)} and
-     * {@link #setAccessibilityPaneTitle(CharSequence)}.
+     * <p>Note: The event generated with this API carries no semantic meaning, and accessibility
+     * services may choose to ignore it. Apps that accurately supply accessibility with the
+     * semantics of their UI should not need to specify what exactly is announced.
      *
-     * <p>
-     * Use {@link #setAccessibilityLiveRegion(int)} to inform the user of changes to critical
+     * <p>In general, do not attempt to generate announcements as confirmation message for simple
+     * actions like a button press. Label your controls concisely and precisely instead.
+     *
+     * <p>To convey significant UI changes like window changes, use {@link
+     * android.app.Activity#setTitle(CharSequence)} and {@link
+     * #setAccessibilityPaneTitle(CharSequence)}.
+     *
+     * <p>Use {@link #setAccessibilityLiveRegion(int)} to inform the user of changes to critical
      * views within the user interface. These should still be used sparingly as they may generate
      * announcements every time a View is updated.
      *
-     * <p>
-     * For notifying users about errors, such as in a login screen with text that displays an
-     * "incorrect password" notification, that view should send an AccessibilityEvent of type
-     * {@link AccessibilityEvent#CONTENT_CHANGE_TYPE_ERROR} and set
-     * {@link AccessibilityNodeInfo#setError(CharSequence)} instead. Custom widgets should expose
-     * error-setting methods that support accessibility automatically. For example, instead of
-     * explicitly sending this event when using a TextView, use
-     * {@link android.widget.TextView#setError(CharSequence)}.
-     *
-     * <p>
-     * Use {@link #setStateDescription(CharSequence)} to convey state changes to views within the
+     * <p>Use {@link #setStateDescription(CharSequence)} to convey state changes to views within the
      * user interface. While a live region may send different types of events generated by the view,
      * state description will send {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} events of
      * type {@link AccessibilityEvent#CONTENT_CHANGE_TYPE_STATE_DESCRIPTION}.
      *
+     * <p>For notifying users about errors, such as in a login screen with text that displays an
+     * "incorrect password" notification, set {@link AccessibilityNodeInfo#setError(CharSequence)}
+     * and dispatch an {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} event with a change
+     * type of {@link AccessibilityEvent#CONTENT_CHANGE_TYPE_ERROR}, instead. Some widgets may
+     * expose methods that convey error states to accessibility automatically, such as {@link
+     * android.widget.TextView#setError(CharSequence)}, which manages these accessibility semantics
+     * and event dispatch for callers.
+     *
+     * @deprecated Use one of the methods described in the documentation above to semantically
+     *     describe UI instead of using an announcement, as accessibility services may choose to
+     *     ignore events dispatched with this method.
      * @param text The announcement text.
      */
+    @FlaggedApi(FLAG_DEPRECATE_ACCESSIBILITY_ANNOUNCEMENT_APIS)
+    @Deprecated
     public void announceForAccessibility(CharSequence text) {
         if (AccessibilityManager.getInstance(mContext).isEnabled() && mParent != null) {
             AccessibilityEvent event = AccessibilityEvent.obtain(
@@ -9806,7 +9818,7 @@
             structure.setChildCount(1);
             final ViewStructure root = structure.newChild(0);
             if (info != null) {
-                populateVirtualStructure(root, provider, info, forAutofill);
+                populateVirtualStructure(root, provider, info, null, forAutofill);
                 info.recycle();
             } else {
                 Log.w(AUTOFILL_LOG_TAG, "AccessibilityNodeInfo is null.");
@@ -11105,11 +11117,19 @@
 
     private void populateVirtualStructure(ViewStructure structure,
             AccessibilityNodeProvider provider, AccessibilityNodeInfo info,
-            boolean forAutofill) {
+            @Nullable AccessibilityNodeInfo parentInfo, boolean forAutofill) {
         structure.setId(AccessibilityNodeInfo.getVirtualDescendantId(info.getSourceNodeId()),
                 null, null, info.getViewIdResourceName());
         Rect rect = structure.getTempRect();
-        info.getBoundsInParent(rect);
+        // The bounds in parent for Jetpack Compose views aren't set as setBoundsInParent is
+        // deprecated, and only setBoundsInScreen is called.
+        // The bounds in parent can be calculated by diff'ing the child view's bounds in screen with
+        // the parent's.
+        if (sCalculateBoundsInParentFromBoundsInScreenFlagValue) {
+            getBoundsInParent(info, parentInfo, rect);
+        } else {
+            info.getBoundsInParent(rect);
+        }
         structure.setDimens(rect.left, rect.top, 0, 0, rect.width(), rect.height());
         structure.setVisibility(VISIBLE);
         structure.setEnabled(info.isEnabled());
@@ -11193,13 +11213,32 @@
                         AccessibilityNodeInfo.getVirtualDescendantId(info.getChildId(i)));
                 if (cinfo != null) {
                     ViewStructure child = structure.newChild(i);
-                    populateVirtualStructure(child, provider, cinfo, forAutofill);
+                    populateVirtualStructure(child, provider, cinfo, info, forAutofill);
                     cinfo.recycle();
                 }
             }
         }
     }
 
+    private void getBoundsInParent(@NonNull AccessibilityNodeInfo info,
+            @Nullable AccessibilityNodeInfo parentInfo, @NonNull Rect rect) {
+        info.getBoundsInParent(rect);
+        // Fallback to calculate bounds in parent by diffing the bounds in
+        // screen if it's all 0.
+        if ((rect.left | rect.top | rect.right | rect.bottom) == 0) {
+            if (parentInfo != null) {
+                Rect parentBoundsInScreen = parentInfo.getBoundsInScreen();
+                Rect boundsInScreen = info.getBoundsInScreen();
+                rect.set(boundsInScreen.left - parentBoundsInScreen.left,
+                        boundsInScreen.top - parentBoundsInScreen.top,
+                        boundsInScreen.right - parentBoundsInScreen.left,
+                        boundsInScreen.bottom - parentBoundsInScreen.top);
+            } else {
+                info.getBoundsInScreen(rect);
+            }
+        }
+    }
+
     /**
      * Dispatch creation of {@link ViewStructure} down the hierarchy.  The default
      * implementation calls {@link #onProvideStructure} and
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 5b39f62..b4b0687 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1426,6 +1426,31 @@
             "android.window.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE";
 
     /**
+     * Activity-level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+     * that specifies whether this activity can declare or request
+     * {@link android.R.attr#screenOrientation fixed orientation},
+     * {@link android.R.attr#minAspectRatio max aspect ratio},
+     * {@link android.R.attr#maxAspectRatio min aspect ratio}
+     * {@link android.R.attr#resizeableActivity unresizable} on large screen devices with the
+     * ignore orientation request display setting enabled since Android 16 (API level 36) or higher.
+     *
+     * <p>The default value is {@code false}.
+     *
+     * <p><b>Syntax:</b>
+     * <pre>
+     * &lt;activity&gt;
+     *   &lt;property
+     *     android:name="android.window.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY"
+     *     android:value="true"/&gt;
+     * &lt;/activity&gt;
+     * </pre>
+     * @hide
+     */
+    // TODO(b/357141415): Make this public API.
+    String PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY =
+            "android.window.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY";
+
+    /**
      * @hide
      */
     public static final String PARCEL_KEY_SHORTCUTS_ARRAY = "shortcuts_array";
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index c690787..0dfaf41 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -563,10 +563,13 @@
 
     /**
      * Represents the event of an application making an announcement.
-     * <p>
-     * In general, follow the practices described in
-     * {@link View#announceForAccessibility(CharSequence)}.
+     *
+     * @deprecated Use one of the semantic alternative methods described in the documentation of
+     *     {@link View#announceForAccessibility(CharSequence)} instead of using this event, as
+     *     accessibility services may choose to ignore dispatch of this event type.
      */
+    @FlaggedApi(Flags.FLAG_DEPRECATE_ACCESSIBILITY_ANNOUNCEMENT_APIS)
+    @Deprecated
     public static final int TYPE_ANNOUNCEMENT = 1 << 14;
 
     /**
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 5e5f33e..59a82be 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -857,8 +857,10 @@
      * <p>
      * The data can be retrieved from the {@code Bundle} returned by {@link #getExtras()} using this
      * string as a key for {@link Bundle#getParcelableArray(String, Class)}. The
-     * {@link android.graphics.RectF} will be null for characters that either do not exist or are
-     * off the screen.
+     * {@link android.graphics.RectF} will be {@code null} for characters that either do not exist
+     * or are off the screen.
+     * <p>
+     * Note that character locations returned are modified by changes in display magnification.
      *
      * {@see #refreshWithExtraData(String, Bundle)}
      */
@@ -866,6 +868,36 @@
             "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_KEY";
 
     /**
+     * Key used to request and locate extra data for text character location in
+     * window coordinates. This key requests that an array of
+     * {@link android.graphics.RectF}s be added to the extras. This request is made
+     * with {@link #refreshWithExtraData(String, Bundle)}. The arguments taken by
+     * this request are two integers:
+     * {@link #EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX} and
+     * {@link #EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH}. The starting index
+     * must be valid inside the CharSequence returned by {@link #getText()}, and
+     * the length must be positive.
+     * <p>
+     * Providers may advertise that they support text characters in window coordinates using
+     * {@link #setAvailableExtraData(List)}. Services may check if an implementation supports text
+     * characters in window coordinates with {@link #getAvailableExtraData()}.
+     * <p>
+     * The data can be retrieved from the {@code Bundle} returned by
+     * {@link #getExtras()} using this string as a key for
+     * {@link Bundle#getParcelableArray(String, Class)}. The
+     * {@link android.graphics.RectF} will be {@code null} for characters that either do
+     * not exist or are outside of the window bounds.
+     * <p>
+     * Note that character locations in window bounds are not modified by
+     * changes in display magnification.
+     *
+     * {@see #refreshWithExtraData(String, Bundle)}
+     */
+    @FlaggedApi(Flags.FLAG_A11Y_CHARACTER_IN_WINDOW_API)
+    public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_IN_WINDOW_KEY =
+            "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_IN_WINDOW_KEY";
+
+    /**
      * Integer argument specifying the start index of the requested text location data. Must be
      * valid inside the CharSequence returned by {@link #getText()}.
      *
@@ -5573,20 +5605,13 @@
         builder.append("; maxTextLength: ").append(mMaxTextLength);
         builder.append("; stateDescription: ").append(mStateDescription);
         builder.append("; contentDescription: ").append(mContentDescription);
-        if (Flags.supplementalDescription()) {
-            builder.append("; supplementalDescription: ").append(mSupplementalDescription);
-        }
         builder.append("; tooltipText: ").append(mTooltipText);
         builder.append("; containerTitle: ").append(mContainerTitle);
         builder.append("; viewIdResName: ").append(mViewIdResourceName);
         builder.append("; uniqueId: ").append(mUniqueId);
         builder.append("; expandedState: ").append(getExpandedStateSymbolicName(mExpandedState));
-
         builder.append("; checkable: ").append(isCheckable());
         builder.append("; checked: ").append(isChecked());
-        if (Flags.a11yIsRequiredApi()) {
-            builder.append("; required: ").append(isFieldRequired());
-        }
         builder.append("; focusable: ").append(isFocusable());
         builder.append("; focused: ").append(isFocused());
         builder.append("; selected: ").append(isSelected());
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index c07da41..7177ef3 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -85,6 +85,13 @@
 
 flag {
     namespace: "accessibility"
+    name: "deprecate_accessibility_announcement_apis"
+    description: "Controls the deprecation of platform APIs related to disruptive accessibility announcements"
+    bug: "376727542"
+}
+
+flag {
+    namespace: "accessibility"
     name: "fix_merged_content_change_event_v2"
     description: "Fixes event type and source of content change event merged in ViewRootImpl"
     bug: "277305460"
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index 2ca62a0..dd32d57 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -221,6 +221,7 @@
             PHASE_WM_INVOKING_IME_REQUESTED_LISTENER,
             PHASE_CLIENT_ALREADY_HIDDEN,
             PHASE_CLIENT_VIEW_HANDLER_AVAILABLE,
+            PHASE_SERVER_UPDATE_CLIENT_VISIBILITY,
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface Phase {}
@@ -430,6 +431,11 @@
      * continue without.
      */
     int PHASE_CLIENT_VIEW_HANDLER_AVAILABLE = ImeProtoEnums.PHASE_CLIENT_VIEW_HANDLER_AVAILABLE;
+    /**
+     * ImeInsetsSourceProvider sets the reported visibility of the caller/client window (either the
+     * app or the RemoteInsetsControlTarget).
+     */
+    int PHASE_SERVER_UPDATE_CLIENT_VISIBILITY = ImeProtoEnums.PHASE_SERVER_UPDATE_CLIENT_VISIBILITY;
 
     /**
      * Called when an IME request is started.
diff --git a/core/java/android/view/inputmethod/InputMethodSubtype.java b/core/java/android/view/inputmethod/InputMethodSubtype.java
index be91cfb..a67ae7c 100644
--- a/core/java/android/view/inputmethod/InputMethodSubtype.java
+++ b/core/java/android/view/inputmethod/InputMethodSubtype.java
@@ -17,8 +17,10 @@
 package android.view.inputmethod;
 
 import android.annotation.AnyThread;
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.StringRes;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.res.Configuration;
@@ -87,8 +89,17 @@
     private final boolean mIsAsciiCapable;
     private final int mSubtypeHashCode;
     private final int mSubtypeIconResId;
+    /** The subtype name resource identifier. */
     private final int mSubtypeNameResId;
+    /** The untranslatable name of the subtype. */
+    @NonNull
     private final CharSequence mSubtypeNameOverride;
+    /** The layout label string resource identifier. */
+    @StringRes
+    private final int mLayoutLabelResId;
+    /** The non-localized layout label. */
+    @NonNull
+    private final CharSequence mLayoutLabelNonLocalized;
     private final String mPkLanguageTag;
     private final String mPkLayoutType;
     private final int mSubtypeId;
@@ -176,6 +187,7 @@
             mSubtypeNameResId = subtypeNameResId;
             return this;
         }
+        /** The subtype name resource identifier. */
         private int mSubtypeNameResId = 0;
 
         /**
@@ -191,9 +203,56 @@
             mSubtypeNameOverride = nameOverride;
             return this;
         }
+        /** The untranslatable name of the subtype. */
+        @NonNull
         private CharSequence mSubtypeNameOverride = "";
 
         /**
+         * Sets the layout label string resource identifier.
+         *
+         * @param layoutLabelResId the layout label string resource identifier.
+         *
+         * @see #getLayoutDisplayName
+         */
+        @FlaggedApi(Flags.FLAG_IME_SWITCHER_REVAMP_API)
+        @NonNull
+        public InputMethodSubtypeBuilder setLayoutLabelResource(
+                @StringRes int layoutLabelResId) {
+            if (!Flags.imeSwitcherRevampApi()) {
+                return this;
+            }
+            mLayoutLabelResId = layoutLabelResId;
+            return this;
+        }
+        /** The layout label string resource identifier. */
+        @StringRes
+        private int mLayoutLabelResId = 0;
+
+        /**
+         * Sets the non-localized layout label. This is used as the layout display name if the
+         * {@link #getLayoutLabelResource layoutLabelResource} is not set ({@code 0}).
+         *
+         * @param layoutLabelNonLocalized the non-localized layout label.
+         *
+         * @see #getLayoutDisplayName
+         */
+        @FlaggedApi(Flags.FLAG_IME_SWITCHER_REVAMP_API)
+        @NonNull
+        public InputMethodSubtypeBuilder setLayoutLabelNonLocalized(
+                @NonNull CharSequence layoutLabelNonLocalized) {
+            if (!Flags.imeSwitcherRevampApi()) {
+                return this;
+            }
+            Objects.requireNonNull(layoutLabelNonLocalized,
+                    "layoutLabelNonLocalized cannot be null");
+            mLayoutLabelNonLocalized = layoutLabelNonLocalized;
+            return this;
+        }
+        /** The non-localized layout label. */
+        @NonNull
+        private CharSequence mLayoutLabelNonLocalized = "";
+
+        /**
          * Sets the physical keyboard hint information, such as language and layout.
          *
          * The system can use the hint information to automatically configure the physical keyboard
@@ -350,6 +409,8 @@
     private InputMethodSubtype(InputMethodSubtypeBuilder builder) {
         mSubtypeNameResId = builder.mSubtypeNameResId;
         mSubtypeNameOverride = builder.mSubtypeNameOverride;
+        mLayoutLabelResId = builder.mLayoutLabelResId;
+        mLayoutLabelNonLocalized = builder.mLayoutLabelNonLocalized;
         mPkLanguageTag = builder.mPkLanguageTag;
         mPkLayoutType = builder.mPkLayoutType;
         mSubtypeIconResId = builder.mSubtypeIconResId;
@@ -376,6 +437,9 @@
         mSubtypeNameResId = source.readInt();
         CharSequence cs = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
         mSubtypeNameOverride = cs != null ? cs : "";
+        mLayoutLabelResId = source.readInt();
+        cs = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+        mLayoutLabelNonLocalized = cs != null ? cs : "";
         s = source.readString8();
         mPkLanguageTag = s != null ? s : "";
         s = source.readString8();
@@ -412,6 +476,24 @@
     }
 
     /**
+     * Returns the layout label string resource identifier.
+     */
+    @FlaggedApi(Flags.FLAG_IME_SWITCHER_REVAMP_API)
+    @StringRes
+    public int getLayoutLabelResource() {
+        return mLayoutLabelResId;
+    }
+
+    /**
+     * Returns the non-localized layout label.
+     */
+    @FlaggedApi(Flags.FLAG_IME_SWITCHER_REVAMP_API)
+    @NonNull
+    public CharSequence getLayoutLabelNonLocalized() {
+        return mLayoutLabelNonLocalized;
+    }
+
+    /**
      * Returns the physical keyboard BCP-47 language tag.
      *
      * @attr ref android.R.styleable#InputMethod_Subtype_physicalKeyboardHintLanguageTag
@@ -643,11 +725,49 @@
         try {
             return String.format(subtypeNameString, replacementString);
         } catch (IllegalFormatException e) {
-            Slog.w(TAG, "Found illegal format in subtype name("+ subtypeName + "): " + e);
+            Slog.w(TAG, "Found illegal format in subtype name(" + subtypeName + "): " + e);
             return "";
         }
     }
 
+    /**
+     * Returns the layout display name.
+     *
+     * <p>If {@code layoutLabelResource} is non-zero (specified through
+     * {@link InputMethodSubtypeBuilder#setLayoutLabelResource setLayoutLabelResource}), the
+     * text generated from that resource will be returned. The localized string resource of the
+     * label should be capitalized for inclusion in UI lists.
+     *
+     * <p>If {@code layoutLabelResource} is zero, the framework returns the non-localized
+     * layout label, if specified through
+     * {@link InputMethodSubtypeBuilder#setLayoutLabelNonLocalized setLayoutLabelNonLocalized}.
+     *
+     * @param context The context used for getting the
+     * {@link android.content.pm.PackageManager PackageManager}.
+     * @param imeAppInfo The {@link ApplicationInfo} of the input method.
+     * @return the layout display name.
+     */
+    @NonNull
+    @FlaggedApi(Flags.FLAG_IME_SWITCHER_REVAMP_API)
+    public CharSequence getLayoutDisplayName(@NonNull Context context,
+            @NonNull ApplicationInfo imeAppInfo) {
+        if (!Flags.imeSwitcherRevampApi()) {
+            return "";
+        }
+        Objects.requireNonNull(context, "context cannot be null");
+        Objects.requireNonNull(imeAppInfo, "imeAppInfo cannot be null");
+        if (mLayoutLabelResId == 0) {
+            return mLayoutLabelNonLocalized;
+        }
+
+        final CharSequence subtypeLayoutName = context.getPackageManager().getText(
+                imeAppInfo.packageName, mLayoutLabelResId, imeAppInfo);
+        if (TextUtils.isEmpty(subtypeLayoutName)) {
+            return "";
+        }
+        return subtypeLayoutName;
+    }
+
     @Nullable
     private static Locale getLocaleFromContext(@Nullable final Context context) {
         if (context == null) {
@@ -778,6 +898,8 @@
     public void writeToParcel(Parcel dest, int parcelableFlags) {
         dest.writeInt(mSubtypeNameResId);
         TextUtils.writeToParcel(mSubtypeNameOverride, dest, parcelableFlags);
+        dest.writeInt(mLayoutLabelResId);
+        TextUtils.writeToParcel(mLayoutLabelNonLocalized, dest, parcelableFlags);
         dest.writeString8(mPkLanguageTag);
         dest.writeString8(mPkLayoutType);
         dest.writeInt(mSubtypeIconResId);
@@ -794,6 +916,7 @@
 
     void dump(@NonNull Printer pw, @NonNull String prefix) {
         pw.println(prefix + "mSubtypeNameOverride=" + mSubtypeNameOverride
+                + " mLayoutLabelNonLocalized=" + mLayoutLabelNonLocalized
                 + " mPkLanguageTag=" + mPkLanguageTag
                 + " mPkLayoutType=" + mPkLayoutType
                 + " mSubtypeId=" + mSubtypeId
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index ef941da..d7750bd 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -26,6 +26,9 @@
 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH;
 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX;
 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
+import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_IN_WINDOW_KEY;
+import static android.view.accessibility.Flags.FLAG_A11Y_CHARACTER_IN_WINDOW_API;
+import static android.view.accessibility.Flags.a11yCharacterInWindowApi;
 import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
 import static android.view.inputmethod.EditorInfo.STYLUS_HANDWRITING_ENABLED_ANDROIDX_EXTRAS_KEY;
 import static android.view.inputmethod.Flags.initiationWithoutInputConnection;
@@ -492,6 +495,20 @@
     /** Accessibility action start id for "smart" actions. @hide */
     static final int ACCESSIBILITY_ACTION_SMART_START_ID = 0x10001000;
 
+    // Stable extra data keys supported by TextView.
+    private static final List<String> ACCESSIBILITY_EXTRA_DATA_KEYS = List.of(
+            EXTRA_DATA_RENDERING_INFO_KEY,
+            EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY
+    );
+
+    // Flagged and stable extra data keys supported by TextView.
+    @FlaggedApi(FLAG_A11Y_CHARACTER_IN_WINDOW_API)
+    private static final List<String> ACCESSIBILITY_EXTRA_DATA_KEYS_FLAGGED = List.of(
+            EXTRA_DATA_RENDERING_INFO_KEY,
+            EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY,
+            EXTRA_DATA_TEXT_CHARACTER_LOCATION_IN_WINDOW_KEY
+    );
+
     /**
      * @hide
      */
@@ -14207,10 +14224,11 @@
                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
             info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
-            info.setAvailableExtraData(Arrays.asList(
-                    EXTRA_DATA_RENDERING_INFO_KEY,
-                    EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY
-            ));
+            if (a11yCharacterInWindowApi()) {
+                info.setAvailableExtraData(ACCESSIBILITY_EXTRA_DATA_KEYS_FLAGGED);
+            } else {
+                info.setAvailableExtraData(ACCESSIBILITY_EXTRA_DATA_KEYS);
+            }
             info.setTextSelectable(isTextSelectable() || isTextEditable());
         } else {
             info.setAvailableExtraData(Arrays.asList(
@@ -14275,7 +14293,11 @@
     @Override
     public void addExtraDataToAccessibilityNodeInfo(
             AccessibilityNodeInfo info, String extraDataKey, Bundle arguments) {
-        if (arguments != null && extraDataKey.equals(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)) {
+        boolean isCharacterLocationKey = extraDataKey.equals(
+                EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY);
+        boolean isCharacterLocationInWindowKey = (a11yCharacterInWindowApi() && extraDataKey.equals(
+                EXTRA_DATA_TEXT_CHARACTER_LOCATION_IN_WINDOW_KEY));
+        if (arguments != null && (isCharacterLocationKey || isCharacterLocationInWindowKey)) {
             int positionInfoStartIndex = arguments.getInt(
                     EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, -1);
             int positionInfoLength = arguments.getInt(
@@ -14297,7 +14319,11 @@
                     RectF bounds = cursorAnchorInfo
                             .getCharacterBounds(positionInfoStartIndex + i);
                     if (bounds != null) {
-                        mapRectFromViewToScreenCoords(bounds, true);
+                        if (isCharacterLocationKey) {
+                            mapRectFromViewToScreenCoords(bounds, true);
+                        } else if (isCharacterLocationInWindowKey) {
+                            mapRectFromViewToWindowCoords(bounds, true);
+                        }
                         boundingRects[i] = bounds;
                     }
                 }
diff --git a/core/java/android/window/TransitionFilter.java b/core/java/android/window/TransitionFilter.java
index 8bb4c52..61fc622 100644
--- a/core/java/android/window/TransitionFilter.java
+++ b/core/java/android/window/TransitionFilter.java
@@ -17,6 +17,7 @@
 package android.window;
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.WindowManager.TransitionType;
 
 import android.annotation.IntDef;
@@ -189,6 +190,8 @@
         public Boolean mCustomAnimation = null;
         public IBinder mTaskFragmentToken = null;
 
+        public int mWindowingMode = WINDOWING_MODE_UNDEFINED;
+
         public Requirement() {
         }
 
@@ -206,6 +209,7 @@
             final int customAnimRaw = in.readInt();
             mCustomAnimation = customAnimRaw == 0 ? null : Boolean.valueOf(customAnimRaw == 2);
             mTaskFragmentToken = in.readStrongBinder();
+            mWindowingMode = in.readInt();
         }
 
         /** Go through changes and find if at-least one change matches this filter */
@@ -270,6 +274,12 @@
                         continue;
                     }
                 }
+                if (mWindowingMode != WINDOWING_MODE_UNDEFINED) {
+                    if (change.getTaskInfo() == null
+                            || change.getTaskInfo().getWindowingMode() != mWindowingMode) {
+                        continue;
+                    }
+                }
                 return true;
             }
             return false;
@@ -322,6 +332,7 @@
             int customAnimRaw = mCustomAnimation == null ? 0 : (mCustomAnimation ? 2 : 1);
             dest.writeInt(customAnimRaw);
             dest.writeStrongBinder(mTaskFragmentToken);
+            dest.writeInt(mWindowingMode);
         }
 
         @NonNull
@@ -369,6 +380,8 @@
             if (mTaskFragmentToken != null) {
                 out.append(" taskFragmentToken=").append(mTaskFragmentToken);
             }
+            out.append(" windowingMode="
+                    + WindowConfiguration.windowingModeToString(mWindowingMode));
             out.append("}");
             return out.toString();
         }
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 14505f5..0f2dd10 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -169,8 +169,11 @@
     /** This change represents its start configuration for the duration of the animation. */
     public static final int FLAG_CONFIG_AT_END = 1 << 22;
 
+    /** This change represents one of a Task Display Area. */
+    public static final int FLAG_IS_TASK_DISPLAY_AREA = 1 << 23;
+
     /** The first unused bit. This can be used by remotes to attach custom flags to this change. */
-    public static final int FLAG_FIRST_CUSTOM = 1 << 23;
+    public static final int FLAG_FIRST_CUSTOM = 1 << 24;
 
     /** The change belongs to a window that won't contain activities. */
     public static final int FLAGS_IS_NON_APP_WINDOW =
@@ -205,6 +208,7 @@
             FLAG_MOVED_TO_TOP,
             FLAG_SYNC,
             FLAG_CONFIG_AT_END,
+            FLAG_IS_TASK_DISPLAY_AREA,
             FLAG_FIRST_CUSTOM
     }, flag = true)
     public @interface ChangeFlags {}
@@ -553,6 +557,9 @@
         if ((flags & FLAG_MOVED_TO_TOP) != 0) {
             sb.append(sb.length() == 0 ? "" : "|").append("MOVE_TO_TOP");
         }
+        if ((flags & FLAG_IS_TASK_DISPLAY_AREA) != 0) {
+            sb.append(sb.length() == 0 ? "" : "|").append("FLAG_IS_TASK_DISPLAY_AREA");
+        }
         return sb.toString();
     }
 
diff --git a/core/java/android/window/flags/responsible_apis.aconfig b/core/java/android/window/flags/responsible_apis.aconfig
index fd5de91..b2f125d 100644
--- a/core/java/android/window/flags/responsible_apis.aconfig
+++ b/core/java/android/window/flags/responsible_apis.aconfig
@@ -16,13 +16,6 @@
 }
 
 flag {
-    name: "bal_show_toasts"
-    namespace: "responsible_apis"
-    description: "Enable toasts to indicate (potential) BAL blocking."
-    bug: "308059069"
-}
-
-flag {
     name: "bal_show_toasts_blocked"
     namespace: "responsible_apis"
     description: "Enable toasts to indicate actual BAL blocking."
@@ -64,14 +57,6 @@
     bug: "339720406"
 }
 
-# replaced by bal_strict_mode_ro
-flag {
-    name: "bal_strict_mode"
-    namespace: "responsible_apis"
-    description: "Strict mode flag"
-    bug: "324089586"
-}
-
 flag {
     name: "bal_strict_mode_ro"
     namespace: "responsible_apis"
diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig
index 460df31..392c307 100644
--- a/core/java/android/window/flags/window_surfaces.aconfig
+++ b/core/java/android/window/flags/window_surfaces.aconfig
@@ -13,14 +13,6 @@
 
 flag {
     namespace: "window_surfaces"
-    name: "explicit_refresh_rate_hints"
-    description: "Performance related hints during transitions"
-    is_fixed_read_only: true
-    bug: "300019131"
-}
-
-flag {
-    namespace: "window_surfaces"
     name: "delete_capture_display"
     description: "Delete uses of ScreenCapture#captureDisplay"
     is_fixed_read_only: true
diff --git a/core/java/com/android/internal/os/KernelSingleUidTimeReader.java b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
index de3edeb..15736ed 100644
--- a/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
+++ b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
@@ -18,14 +18,13 @@
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.util.Slog;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
-import dalvik.annotation.optimization.CriticalNative;
-
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
@@ -255,8 +254,8 @@
      * the delta in the supplied array container.
      */
     public void addDelta(int uid, LongArrayMultiStateCounter counter, long timestampMs,
-            LongArrayMultiStateCounter.LongArrayContainer deltaContainer) {
-        mInjector.addDelta(uid, counter, timestampMs, deltaContainer);
+            long[] delta) {
+        mInjector.addDelta(uid, counter, timestampMs, delta);
     }
 
     @VisibleForTesting
@@ -274,15 +273,13 @@
          * The delta is also returned via the optional deltaOut parameter.
          */
         public boolean addDelta(int uid, LongArrayMultiStateCounter counter, long timestampMs,
-                LongArrayMultiStateCounter.LongArrayContainer deltaOut) {
-            return addDeltaFromBpf(uid, counter.mNativeObject, timestampMs,
-                    deltaOut != null ? deltaOut.mNativeObject : 0);
+                long[] deltaOut) {
+            return addDeltaFromBpf(uid, counter.mNativeObject, timestampMs, deltaOut);
         }
 
-        @CriticalNative
         private static native boolean addDeltaFromBpf(int uid,
                 long longArrayMultiStateCounterNativePointer, long timestampMs,
-                long longArrayContainerNativePointer);
+                @Nullable long[] deltaOut);
 
         /**
          * Used for testing.
@@ -291,14 +288,14 @@
          */
         public boolean addDeltaForTest(int uid, LongArrayMultiStateCounter counter,
                 long timestampMs, long[][] timeInFreqDataNanos,
-                LongArrayMultiStateCounter.LongArrayContainer deltaOut) {
+                long[] deltaOut) {
             return addDeltaForTest(uid, counter.mNativeObject, timestampMs, timeInFreqDataNanos,
-                    deltaOut != null ? deltaOut.mNativeObject : 0);
+                    deltaOut);
         }
 
         private static native boolean addDeltaForTest(int uid,
                 long longArrayMultiStateCounterNativePointer, long timestampMs,
-                long[][] timeInFreqDataNanos, long longArrayContainerNativePointer);
+                long[][] timeInFreqDataNanos, long[] deltaOut);
     }
 
     @VisibleForTesting
diff --git a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
index 489721f..b3480ab 100644
--- a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
+++ b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
@@ -30,9 +30,6 @@
 
 import libcore.util.NativeAllocationRegistry;
 
-import java.util.Arrays;
-import java.util.concurrent.atomic.AtomicReference;
-
 /**
  * Performs per-state counting of multi-element values over time. The class' behavior is illustrated
  * by this example:
@@ -44,15 +41,14 @@
  *   counter.setState(1, 1000);
  *
  *   // At 3000 ms, the tracked values are updated to {30, 300}
- *   arrayContainer.setValues(new long[]{{30, 300}};
- *   counter.updateValues(arrayContainer, 3000);
+ *   counter.updateValues(arrayContainer, new long[]{{30, 300}, 3000);
  *
  *   // The values are distributed between states 0 and 1 according to the time
  *   // spent in those respective states. In this specific case, 1000 and 2000 ms.
- *   counter.getValues(arrayContainer, 0);
- *   // arrayContainer now has values {10, 100}
- *   counter.getValues(arrayContainer, 1);
- *   // arrayContainer now has values {20, 200}
+ *   counter.getCounts(array, 0);
+ *   // array now has values {10, 100}
+ *   counter.getCounts(array, 1);
+ *   // array now has values {20, 200}
  * </pre>
  *
  * The tracked values are expected to increase monotonically.
@@ -62,110 +58,7 @@
 @RavenwoodKeepWholeClass
 @RavenwoodRedirectionClass("LongArrayMultiStateCounter_host")
 public final class LongArrayMultiStateCounter implements Parcelable {
-
-    /**
-     * Container for a native equivalent of a long[].
-     */
-    @RavenwoodKeepWholeClass
-    @RavenwoodRedirectionClass("LongArrayContainer_host")
-    public static class LongArrayContainer {
-        private static NativeAllocationRegistry sRegistry;
-
-        // Visible to other objects in this package so that it can be passed to @CriticalNative
-        // methods.
-        final long mNativeObject;
-        private final int mLength;
-
-        public LongArrayContainer(int length) {
-            mLength = length;
-            mNativeObject = native_init(length);
-            registerNativeAllocation();
-        }
-
-        @RavenwoodReplace
-        private void registerNativeAllocation() {
-            if (sRegistry == null) {
-                synchronized (LongArrayMultiStateCounter.class) {
-                    if (sRegistry == null) {
-                        sRegistry = NativeAllocationRegistry.createMalloced(
-                                LongArrayContainer.class.getClassLoader(), native_getReleaseFunc());
-                    }
-                }
-            }
-            sRegistry.registerNativeAllocation(this, mNativeObject);
-        }
-
-        private void registerNativeAllocation$ravenwood() {
-            // No-op under ravenwood
-        }
-
-        /**
-         * Copies the supplied values into the underlying native array.
-         */
-        public void setValues(long[] array) {
-            if (array.length != mLength) {
-                throw new IllegalArgumentException(
-                        "Invalid array length: " + array.length + ", expected: " + mLength);
-            }
-            native_setValues(mNativeObject, array);
-        }
-
-        /**
-         * Copies the underlying native array values to the supplied array.
-         */
-        public void getValues(long[] array) {
-            if (array.length != mLength) {
-                throw new IllegalArgumentException(
-                        "Invalid array length: " + array.length + ", expected: " + mLength);
-            }
-            native_getValues(mNativeObject, array);
-        }
-
-        /**
-         * Combines contained values into a smaller array by aggregating them
-         * according to an index map.
-         */
-        public boolean combineValues(long[] array, int[] indexMap) {
-            if (indexMap.length != mLength) {
-                throw new IllegalArgumentException(
-                        "Wrong index map size " + indexMap.length + ", expected " + mLength);
-            }
-            return native_combineValues(mNativeObject, array, indexMap);
-        }
-
-        @Override
-        public String toString() {
-            final long[] array = new long[mLength];
-            getValues(array);
-            return Arrays.toString(array);
-        }
-
-        @CriticalNative
-        @RavenwoodRedirect
-        private static native long native_init(int length);
-
-        @CriticalNative
-        @RavenwoodRedirect
-        private static native long native_getReleaseFunc();
-
-        @FastNative
-        @RavenwoodRedirect
-        private static native void native_setValues(long nativeObject, long[] array);
-
-        @FastNative
-        @RavenwoodRedirect
-        private static native void native_getValues(long nativeObject, long[] array);
-
-        @FastNative
-        @RavenwoodRedirect
-        private static native boolean native_combineValues(long nativeObject, long[] array,
-                int[] indexMap);
-    }
-
     private static volatile NativeAllocationRegistry sRegistry;
-    private static final AtomicReference<LongArrayContainer> sTmpArrayContainer =
-            new AtomicReference<>();
-
     private final int mStateCount;
     private final int mLength;
 
@@ -257,13 +150,14 @@
             throw new IllegalArgumentException(
                     "Invalid array length: " + values.length + ", expected: " + mLength);
         }
-        LongArrayContainer container = sTmpArrayContainer.getAndSet(null);
-        if (container == null || container.mLength != values.length) {
-            container = new LongArrayContainer(values.length);
-        }
-        container.setValues(values);
-        native_setValues(mNativeObject, state, container.mNativeObject);
-        sTmpArrayContainer.set(container);
+        native_setValues(mNativeObject, state, values);
+    }
+
+    /**
+     * Adds the supplied values to the current accumulated values in the counter.
+     */
+    public void incrementValues(long[] values, long timestampMs) {
+        native_incrementValues(mNativeObject, values, timestampMs);
     }
 
     /**
@@ -272,51 +166,22 @@
      * since the previous call to updateValues.
      */
     public void updateValues(long[] values, long timestampMs) {
-        LongArrayContainer container = sTmpArrayContainer.getAndSet(null);
-        if (container == null || container.mLength != values.length) {
-            container = new LongArrayContainer(values.length);
+        if (values.length != mLength) {
+            throw new IllegalArgumentException(
+                    "Invalid array length: " + values.length + ", expected: " + mLength);
         }
-        container.setValues(values);
-        updateValues(container, timestampMs);
-        sTmpArrayContainer.set(container);
+        native_updateValues(mNativeObject, values, timestampMs);
     }
 
     /**
      * Adds the supplied values to the current accumulated values in the counter.
      */
-    public void incrementValues(long[] values, long timestampMs) {
-        LongArrayContainer container = sTmpArrayContainer.getAndSet(null);
-        if (container == null || container.mLength != values.length) {
-            container = new LongArrayContainer(values.length);
-        }
-        container.setValues(values);
-        native_incrementValues(mNativeObject, container.mNativeObject, timestampMs);
-        sTmpArrayContainer.set(container);
-    }
-
-    /**
-     * Sets the new values.  The delta between the previously set values and these values
-     * is distributed among the state according to the time the object spent in those states
-     * since the previous call to updateValues.
-     */
-    public void updateValues(LongArrayContainer longArrayContainer, long timestampMs) {
-        if (longArrayContainer.mLength != mLength) {
+    public void addCounts(long[] counts) {
+        if (counts.length != mLength) {
             throw new IllegalArgumentException(
-                    "Invalid array length: " + longArrayContainer.mLength + ", expected: "
-                            + mLength);
+                    "Invalid array length: " + counts.length + ", expected: " + mLength);
         }
-        native_updateValues(mNativeObject, longArrayContainer.mNativeObject, timestampMs);
-    }
-
-    /**
-     * Adds the supplied values to the current accumulated values in the counter.
-     */
-    public void addCounts(LongArrayContainer counts) {
-        if (counts.mLength != mLength) {
-            throw new IllegalArgumentException(
-                    "Invalid array length: " + counts.mLength + ", expected: " + mLength);
-        }
-        native_addCounts(mNativeObject, counts.mNativeObject);
+        native_addCounts(mNativeObject, counts);
     }
 
     /**
@@ -330,29 +195,15 @@
      * Populates the array with the accumulated counts for the specified state.
      */
     public void getCounts(long[] counts, int state) {
-        LongArrayContainer container = sTmpArrayContainer.getAndSet(null);
-        if (container == null || container.mLength != counts.length) {
-            container = new LongArrayContainer(counts.length);
-        }
-        getCounts(container, state);
-        container.getValues(counts);
-        sTmpArrayContainer.set(container);
-    }
-
-    /**
-     * Populates longArrayContainer with the accumulated counts for the specified state.
-     */
-    public void getCounts(LongArrayContainer longArrayContainer, int state) {
         if (state < 0 || state >= mStateCount) {
             throw new IllegalArgumentException(
                     "State: " + state + ", outside the range: [0-" + mStateCount + "]");
         }
-        if (longArrayContainer.mLength != mLength) {
+        if (counts.length != mLength) {
             throw new IllegalArgumentException(
-                    "Invalid array length: " + longArrayContainer.mLength
-                            + ", expected: " + mLength);
+                    "Invalid array length: " + counts.length + ", expected: " + mLength);
         }
-        native_getCounts(mNativeObject, longArrayContainer.mNativeObject, state);
+        native_getCounts(mNativeObject, counts, state);
     }
 
     @Override
@@ -370,18 +221,17 @@
         return 0;
     }
 
-    public static final Creator<LongArrayMultiStateCounter> CREATOR =
-            new Creator<LongArrayMultiStateCounter>() {
-                @Override
-                public LongArrayMultiStateCounter createFromParcel(Parcel in) {
-                    return new LongArrayMultiStateCounter(in);
-                }
+    public static final Creator<LongArrayMultiStateCounter> CREATOR = new Creator<>() {
+        @Override
+        public LongArrayMultiStateCounter createFromParcel(Parcel in) {
+            return new LongArrayMultiStateCounter(in);
+        }
 
-                @Override
-                public LongArrayMultiStateCounter[] newArray(int size) {
-                    return new LongArrayMultiStateCounter[size];
-                }
-            };
+        @Override
+        public LongArrayMultiStateCounter[] newArray(int size) {
+            return new LongArrayMultiStateCounter[size];
+        }
+    };
 
 
     @CriticalNative
@@ -406,34 +256,31 @@
     private static native void native_copyStatesFrom(long nativeObjectTarget,
             long nativeObjectSource);
 
-    @CriticalNative
+    @FastNative
     @RavenwoodRedirect
-    private static native void native_setValues(long nativeObject, int state,
-            long longArrayContainerNativeObject);
+    private static native void native_setValues(long nativeObject, int state, long[] values);
 
-    @CriticalNative
+    @FastNative
     @RavenwoodRedirect
-    private static native void native_updateValues(long nativeObject,
-            long longArrayContainerNativeObject, long timestampMs);
+    private static native void native_updateValues(long nativeObject, long[] values,
+            long timestampMs);
 
-    @CriticalNative
+    @FastNative
     @RavenwoodRedirect
-    private static native void native_incrementValues(long nativeObject,
-            long longArrayContainerNativeObject, long timestampMs);
+    private static native void native_incrementValues(long nativeObject, long[] values,
+            long timestampMs);
 
-    @CriticalNative
+    @FastNative
     @RavenwoodRedirect
-    private static native void native_addCounts(long nativeObject,
-            long longArrayContainerNativeObject);
+    private static native void native_addCounts(long nativeObject, long[] counts);
 
     @CriticalNative
     @RavenwoodRedirect
     private static native void native_reset(long nativeObject);
 
-    @CriticalNative
+    @FastNative
     @RavenwoodRedirect
-    private static native void native_getCounts(long nativeObject,
-            long longArrayContainerNativeObject, int state);
+    private static native void native_getCounts(long nativeObject, long[] counts, int state);
 
     @FastNative
     @RavenwoodRedirect
diff --git a/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java
index 30b160a..a69d2e4 100644
--- a/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java
+++ b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java
@@ -28,19 +28,9 @@
 public final class RavenwoodEnvironment {
     public static final String TAG = "RavenwoodEnvironment";
 
-    private static final RavenwoodEnvironment sInstance;
-    private static final Workaround sWorkaround;
+    private static RavenwoodEnvironment sInstance = new RavenwoodEnvironment();
 
-    private RavenwoodEnvironment() {
-    }
-
-    static {
-        sInstance = new RavenwoodEnvironment();
-        sWorkaround = new Workaround();
-        ensureRavenwoodInitialized();
-    }
-
-    public static RuntimeException notSupportedOnDevice() {
+    private static RuntimeException notSupportedOnDevice() {
         return new UnsupportedOperationException("This method can only be used on Ravenwood");
     }
 
@@ -52,15 +42,6 @@
     }
 
     /**
-     * Initialize the ravenwood environment if it hasn't happened already, if running on Ravenwood.
-     *
-     * No-op if called on the device side.
-     */
-    @RavenwoodRedirect
-    public static void ensureRavenwoodInitialized() {
-    }
-
-    /**
      * USE IT SPARINGLY! Returns true if it's running on Ravenwood, hostside test environment.
      *
      * <p>Using this allows code to behave differently on a real device and on Ravenwood, but
@@ -91,38 +72,10 @@
     }
 
     /**
-     * See {@link Workaround}. It's only usable on Ravenwood.
-     */
-    @RavenwoodReplace
-    public static Workaround workaround() {
-        throw notSupportedOnDevice();
-    }
-
-    private static Workaround workaround$ravenwood() {
-        return sWorkaround;
-    }
-
-    /**
      * @return the "ravenwood-runtime" directory.
      */
     @RavenwoodRedirect
     public String getRavenwoodRuntimePath() {
         throw notSupportedOnDevice();
     }
-
-    /**
-     * A set of APIs used to work around missing features on Ravenwood. Ideally, this class should
-     * be empty, and all its APIs should be able to be implemented properly.
-     */
-    public static class Workaround {
-        Workaround() {
-        }
-
-        /**
-         * @return whether the app's target SDK level is at least Q.
-         */
-        public boolean isTargetSdkAtLeastQ() {
-            return true;
-        }
-    }
 }
diff --git a/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java b/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java
index b6383d9..38685b6 100644
--- a/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java
+++ b/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java
@@ -530,8 +530,26 @@
         int rootViewTopOnWindow = mTmpCoords[1];
         int windowLeftOnScreen = rootViewLeftOnScreen - rootViewLeftOnWindow;
         int windowTopOnScreen = rootViewTopOnScreen - rootViewTopOnWindow;
-        mCoordsOnWindow.set(
-                Math.max(0, x - windowLeftOnScreen), Math.max(0, y - windowTopOnScreen));
+        // In some cases, app can have specific Window for Android UI components such as EditText.
+        // In this case, Window bounds != App bounds. Hence, instead of ensuring non-negative
+        // PopupWindow coords, app bounds should be used to limit the coords. For instance,
+        //  ____  <- |
+        // |   |     |W1 & App bounds
+        // |___|    |
+        // |W2 |    | W2 has smaller bounds and contain EditText where PopupWindow will be opened.
+        // ----  <-|
+        // Here, we'll open PopupWindow upwards, but as PopupWindow is anchored based on W2, it
+        // will have negative Y coords. This negative Y is safe to use because it's still within app
+        // bounds. However, if it gets out of app bounds, we should clamp it to 0.
+        Rect appBounds = mContext
+                .getResources().getConfiguration().windowConfiguration.getAppBounds();
+        mCoordsOnWindow.set(x - windowLeftOnScreen, y - windowTopOnScreen);
+        if (rootViewLeftOnScreen + mCoordsOnWindow.x < appBounds.left) {
+            mCoordsOnWindow.x = 0;
+        }
+        if (rootViewTopOnScreen + mCoordsOnWindow.y < appBounds.top) {
+            mCoordsOnWindow.y = 0;
+        }
     }
 
     /**
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 2bb6e71..2541258 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -76,7 +76,6 @@
         "android_content_res_ApkAssets.cpp",
         "android_os_SystemClock.cpp",
         "android_os_SystemProperties.cpp",
-        "android_os_Trace.cpp",
         "android_text_AndroidCharacter.cpp",
         "android_util_AssetManager.cpp",
         "android_util_EventLog.cpp",
@@ -104,10 +103,6 @@
         "system/media/private/camera/include",
     ],
 
-    shared_libs: [
-        "libtracing_perfetto",
-    ],
-
     static_libs: [
         "libziparchive_for_incfs",
         "libguiflags",
@@ -190,6 +185,7 @@
                 "android_os_ServiceManagerNative.cpp",
                 "android_os_SharedMemory.cpp",
                 "android_os_storage_StorageManager.cpp",
+                "android_os_Trace.cpp",
                 "android_os_UEventObserver.cpp",
                 "android_os_incremental_IncrementalManager.cpp",
                 "android_net_LocalSocketImpl.cpp",
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index df87a69..56292c3 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -2403,6 +2403,11 @@
     }
 }
 
+static void nativeEnableDebugLogCallPoints(JNIEnv* env, jclass clazz, jlong transactionObj) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+    transaction->enableDebugLogCallPoints();
+}
+
 static const JNINativeMethod sSurfaceControlMethods[] = {
         // clang-format off
     {"nativeCreate", "(Landroid/view/SurfaceSession;Ljava/lang/String;IIIIJLandroid/os/Parcel;)J",
@@ -2649,6 +2654,7 @@
     {"nativeNotifyShutdown", "()V",
             (void*)nativeNotifyShutdown },
     {"nativeSetLuts", "(JJ[F[I[I[I[I)V", (void*)nativeSetLuts },
+    {"nativeEnableDebugLogCallPoints", "(J)V", (void*)nativeEnableDebugLogCallPoints },
         // clang-format on
 };
 
diff --git a/core/jni/com_android_internal_os_KernelSingleUidTimeReader.cpp b/core/jni/com_android_internal_os_KernelSingleUidTimeReader.cpp
index bea5ffe..a5b5057 100644
--- a/core/jni/com_android_internal_os_KernelSingleUidTimeReader.cpp
+++ b/core/jni/com_android_internal_os_KernelSingleUidTimeReader.cpp
@@ -49,27 +49,26 @@
  * to the supplied multi-state counter in accordance with the counter's state.
  */
 static jboolean addCpuTimeInFreqDelta(
-        jint uid, jlong counterNativePtr, jlong timestampMs,
+        JNIEnv *env, jint uid, jlong counterNativePtr, jlong timestampMs,
         std::optional<std::vector<std::vector<uint64_t>>> timeInFreqDataNanos,
-        jlong deltaOutContainerNativePtr) {
+        jlongArray deltaOut) {
     if (!timeInFreqDataNanos) {
         return false;
     }
 
-    battery::LongArrayMultiStateCounter *counter =
-            reinterpret_cast<battery::LongArrayMultiStateCounter *>(counterNativePtr);
+    auto counter = reinterpret_cast<battery::LongArrayMultiStateCounter *>(counterNativePtr);
     size_t s = 0;
     for (const auto &cluster : *timeInFreqDataNanos) s += cluster.size();
 
-    std::vector<uint64_t> flattened;
-    flattened.reserve(s);
-    auto offset = flattened.begin();
+    battery::Uint64ArrayRW flattened(s);
+    uint64_t *out = flattened.dataRW();
+    auto offset = out;
     for (const auto &cluster : *timeInFreqDataNanos) {
-        flattened.insert(offset, cluster.begin(), cluster.end());
+        memcpy(offset, cluster.data(), cluster.size() * sizeof(uint64_t));
         offset += cluster.size();
     }
     for (size_t i = 0; i < s; ++i) {
-        flattened[i] /= NSEC_PER_MSEC;
+        out[i] /= NSEC_PER_MSEC;
     }
     if (s != counter->getCount(0).size()) { // Counter has at least one state
         ALOGE("Mismatch between eBPF data size (%d) and the counter size (%d)", (int)s,
@@ -77,29 +76,32 @@
         return false;
     }
 
-    const std::vector<uint64_t> &delta = counter->updateValue(flattened, timestampMs);
-    if (deltaOutContainerNativePtr) {
-        std::vector<uint64_t> *vector =
-                reinterpret_cast<std::vector<uint64_t> *>(deltaOutContainerNativePtr);
-        *vector = delta;
+    const battery::Uint64Array &delta = counter->updateValue(flattened, timestampMs);
+    if (deltaOut) {
+        ScopedLongArrayRW scopedArray(env, deltaOut);
+        uint64_t *array = reinterpret_cast<uint64_t *>(scopedArray.get());
+        if (delta.data() != nullptr) {
+            memcpy(array, delta.data(), s * sizeof(uint64_t));
+        } else {
+            memset(array, 0, s * sizeof(uint64_t));
+        }
     }
 
     return true;
 }
 
-static jboolean addDeltaFromBpf(jint uid, jlong counterNativePtr, jlong timestampMs,
-                                jlong deltaOutContainerNativePtr) {
-    return addCpuTimeInFreqDelta(uid, counterNativePtr, timestampMs,
-                                 android::bpf::getUidCpuFreqTimes(uid), deltaOutContainerNativePtr);
+static jboolean addDeltaFromBpf(JNIEnv *env, jlong self, jint uid, jlong counterNativePtr,
+                                jlong timestampMs, jlongArray deltaOut) {
+    return addCpuTimeInFreqDelta(env, uid, counterNativePtr, timestampMs,
+                                 android::bpf::getUidCpuFreqTimes(uid), deltaOut);
 }
 
 static jboolean addDeltaForTest(JNIEnv *env, jclass, jint uid, jlong counterNativePtr,
                                 jlong timestampMs, jobjectArray timeInFreqDataNanos,
-                                jlong deltaOutContainerNativePtr) {
+                                jlongArray deltaOut) {
     if (!timeInFreqDataNanos) {
-        return addCpuTimeInFreqDelta(uid, counterNativePtr, timestampMs,
-                                     std::optional<std::vector<std::vector<uint64_t>>>(),
-                                     deltaOutContainerNativePtr);
+        return addCpuTimeInFreqDelta(env, uid, counterNativePtr, timestampMs,
+                                     std::optional<std::vector<std::vector<uint64_t>>>(), deltaOut);
     }
 
     std::vector<std::vector<uint64_t>> timeInFreqData;
@@ -113,18 +115,16 @@
         }
         timeInFreqData.push_back(cluster);
     }
-    return addCpuTimeInFreqDelta(uid, counterNativePtr, timestampMs, std::optional(timeInFreqData),
-                                 deltaOutContainerNativePtr);
+    return addCpuTimeInFreqDelta(env, uid, counterNativePtr, timestampMs,
+                                 std::optional(timeInFreqData), deltaOut);
 }
 
 static const JNINativeMethod g_single_methods[] = {
         {"readBpfData", "(I)[J", (void *)getUidCpuFreqTimeMs},
-
-        // @CriticalNative
-        {"addDeltaFromBpf", "(IJJJ)Z", (void *)addDeltaFromBpf},
+        {"addDeltaFromBpf", "(IJJ[J)Z", (void *)addDeltaFromBpf},
 
         // Used for testing
-        {"addDeltaForTest", "(IJJ[[JJ)Z", (void *)addDeltaForTest},
+        {"addDeltaForTest", "(IJJ[[J[J)Z", (void *)addDeltaForTest},
 };
 
 int register_com_android_internal_os_KernelSingleUidTimeReader(JNIEnv *env) {
diff --git a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
index b3c41df..7ffe0ed 100644
--- a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
+++ b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
@@ -26,16 +26,40 @@
 #include "core_jni_helpers.h"
 
 namespace android {
+namespace battery {
+
+/**
+ * Implementation of Uint64Array that wraps a Java long[]. Since it uses the "critical"
+ * version of JNI array access (halting GC), any usage of this class must be extra quick.
+ */
+class JavaUint64Array : public Uint64Array {
+    JNIEnv *mEnv;
+    jlongArray mJavaArray;
+    uint64_t *mData;
+
+public:
+    JavaUint64Array(JNIEnv *env, jlongArray values) : Uint64Array(env->GetArrayLength(values)) {
+        mEnv = env;
+        mJavaArray = values;
+        mData = reinterpret_cast<uint64_t *>(mEnv->GetPrimitiveArrayCritical(mJavaArray, nullptr));
+    }
+
+    ~JavaUint64Array() override {
+        mEnv->ReleasePrimitiveArrayCritical(mJavaArray, mData, 0);
+    }
+
+    const uint64_t *data() const override {
+        return mData;
+    }
+};
 
 static jlong native_init(jint stateCount, jint arrayLength) {
-    battery::LongArrayMultiStateCounter *counter =
-            new battery::LongArrayMultiStateCounter(stateCount, std::vector<uint64_t>(arrayLength));
+    auto *counter = new LongArrayMultiStateCounter(stateCount, Uint64Array(arrayLength));
     return reinterpret_cast<jlong>(counter);
 }
 
 static void native_dispose(void *nativePtr) {
-    battery::LongArrayMultiStateCounter *counter =
-            reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
+    auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
     delete counter;
 }
 
@@ -44,80 +68,63 @@
 }
 
 static void native_setEnabled(jlong nativePtr, jboolean enabled, jlong timestamp) {
-    battery::LongArrayMultiStateCounter *counter =
-            reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
+    auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
     counter->setEnabled(enabled, timestamp);
 }
 
 static void native_setState(jlong nativePtr, jint state, jlong timestamp) {
-    battery::LongArrayMultiStateCounter *counter =
-            reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
+    auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
     counter->setState(state, timestamp);
 }
 
 static void native_copyStatesFrom(jlong nativePtrTarget, jlong nativePtrSource) {
-    battery::LongArrayMultiStateCounter *counterTarget =
-            reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtrTarget);
-    battery::LongArrayMultiStateCounter *counterSource =
-            reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtrSource);
+    auto *counterTarget = reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtrTarget);
+    auto *counterSource = reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtrSource);
     counterTarget->copyStatesFrom(*counterSource);
 }
 
-static void native_setValues(jlong nativePtr, jint state, jlong longArrayContainerNativePtr) {
-    battery::LongArrayMultiStateCounter *counter =
-            reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
-    std::vector<uint64_t> *vector =
-            reinterpret_cast<std::vector<uint64_t> *>(longArrayContainerNativePtr);
-
-    counter->setValue(state, *vector);
+static void native_setValues(JNIEnv *env, jclass, jlong nativePtr, jint state, jlongArray values) {
+    auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
+    counter->setValue(state, JavaUint64Array(env, values));
 }
 
-static void native_updateValues(jlong nativePtr, jlong longArrayContainerNativePtr,
+static void native_updateValues(JNIEnv *env, jclass, jlong nativePtr, jlongArray values,
                                 jlong timestamp) {
-    battery::LongArrayMultiStateCounter *counter =
-            reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
-    std::vector<uint64_t> *vector =
-            reinterpret_cast<std::vector<uint64_t> *>(longArrayContainerNativePtr);
-
-    counter->updateValue(*vector, timestamp);
+    auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
+    counter->updateValue(JavaUint64Array(env, values), timestamp);
 }
 
-static void native_incrementValues(jlong nativePtr, jlong longArrayContainerNativePtr,
+static void native_incrementValues(JNIEnv *env, jclass, jlong nativePtr, jlongArray values,
                                    jlong timestamp) {
-    battery::LongArrayMultiStateCounter *counter =
-            reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
-    std::vector<uint64_t> *vector =
-            reinterpret_cast<std::vector<uint64_t> *>(longArrayContainerNativePtr);
-
-    counter->incrementValue(*vector, timestamp);
+    auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
+    counter->incrementValue(JavaUint64Array(env, values), timestamp);
 }
 
-static void native_addCounts(jlong nativePtr, jlong longArrayContainerNativePtr) {
-    battery::LongArrayMultiStateCounter *counter =
-            reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
-    std::vector<uint64_t> *vector =
-            reinterpret_cast<std::vector<uint64_t> *>(longArrayContainerNativePtr);
-    counter->addValue(*vector);
+static void native_addCounts(JNIEnv *env, jclass, jlong nativePtr, jlongArray values) {
+    auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
+    counter->addValue(JavaUint64Array(env, values));
 }
 
 static void native_reset(jlong nativePtr) {
-    battery::LongArrayMultiStateCounter *counter =
-            reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
+    auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
     counter->reset();
 }
 
-static void native_getCounts(jlong nativePtr, jlong longArrayContainerNativePtr, jint state) {
-    battery::LongArrayMultiStateCounter *counter =
-            reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
-    std::vector<uint64_t> *vector =
-            reinterpret_cast<std::vector<uint64_t> *>(longArrayContainerNativePtr);
-
-    *vector = counter->getCount(state);
+static void native_getCounts(JNIEnv *env, jclass, jlong nativePtr, jlongArray values, jint state) {
+    auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
+    ScopedLongArrayRW scopedArray(env, values);
+    auto *data = counter->getCount(state).data();
+    auto size = env->GetArrayLength(values);
+    auto *outData = scopedArray.get();
+    if (data == nullptr) {
+        memset(outData, 0, size * sizeof(uint64_t));
+    } else {
+        memcpy(outData, data, size * sizeof(uint64_t));
+    }
 }
 
 static jobject native_toString(JNIEnv *env, jclass, jlong nativePtr) {
-    battery::LongArrayMultiStateCounter *counter =
-            reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
+    auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
     return env->NewStringUTF(counter->toString().c_str());
 }
 
@@ -137,20 +144,26 @@
 
 static void native_writeToParcel(JNIEnv *env, jclass, jlong nativePtr, jobject jParcel,
                                  jint flags) {
-    battery::LongArrayMultiStateCounter *counter =
-            reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
+    auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
     ndk::ScopedAParcel parcel(AParcel_fromJavaParcel(env, jParcel));
 
     uint16_t stateCount = counter->getStateCount();
     THROW_AND_RETURN_ON_WRITE_ERROR(AParcel_writeInt32(parcel.get(), stateCount));
 
     // LongArrayMultiStateCounter has at least state 0
-    const std::vector<uint64_t> &anyState = counter->getCount(0);
+    const Uint64Array &anyState = counter->getCount(0);
     THROW_AND_RETURN_ON_WRITE_ERROR(AParcel_writeInt32(parcel.get(), anyState.size()));
 
     for (battery::state_t state = 0; state < stateCount; state++) {
-        THROW_AND_RETURN_ON_WRITE_ERROR(
-                ndk::AParcel_writeVector(parcel.get(), counter->getCount(state)));
+        const Uint64Array &value = counter->getCount(state);
+        if (value.data() == nullptr) {
+            THROW_AND_RETURN_ON_WRITE_ERROR(AParcel_writeBool(parcel.get(), false));
+        } else {
+            THROW_AND_RETURN_ON_WRITE_ERROR(AParcel_writeBool(parcel.get(), true));
+            for (size_t i = 0; i < anyState.size(); i++) {
+                THROW_AND_RETURN_ON_WRITE_ERROR(AParcel_writeUint64(parcel.get(), value.data()[i]));
+            }
+        }
     }
 }
 
@@ -183,40 +196,37 @@
     int32_t arrayLength;
     THROW_AND_RETURN_ON_READ_ERROR(AParcel_readInt32(parcel.get(), &arrayLength));
 
-    auto counter = std::make_unique<battery::LongArrayMultiStateCounter>(stateCount,
-                                                                         std::vector<uint64_t>(
-                                                                                 arrayLength));
-
-    std::vector<uint64_t> value;
-    value.reserve(arrayLength);
-
+    auto counter =
+            std::make_unique<LongArrayMultiStateCounter>(stateCount, Uint64Array(arrayLength));
+    Uint64ArrayRW array(arrayLength);
     for (battery::state_t state = 0; state < stateCount; state++) {
-        THROW_AND_RETURN_ON_READ_ERROR(ndk::AParcel_readVector(parcel.get(), &value));
-        counter->setValue(state, value);
+        bool hasValues;
+        THROW_AND_RETURN_ON_READ_ERROR(AParcel_readBool(parcel.get(), &hasValues));
+        if (hasValues) {
+            for (int i = 0; i < arrayLength; i++) {
+                THROW_AND_RETURN_ON_READ_ERROR(
+                        AParcel_readUint64(parcel.get(), &(array.dataRW()[i])));
+            }
+            counter->setValue(state, array);
+        }
     }
 
     return reinterpret_cast<jlong>(counter.release());
 }
 
 static jint native_getStateCount(jlong nativePtr) {
-    battery::LongArrayMultiStateCounter *counter =
-            reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
+    auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
     return counter->getStateCount();
 }
 
 static jint native_getArrayLength(jlong nativePtr) {
-    battery::LongArrayMultiStateCounter *counter =
-            reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
+    auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
 
     // LongArrayMultiStateCounter has at least state 0
-    const std::vector<uint64_t> &anyState = counter->getCount(0);
+    const Uint64Array &anyState = counter->getCount(0);
     return anyState.size();
 }
 
-static jlong native_init_LongArrayContainer(jint length) {
-    return reinterpret_cast<jlong>(new std::vector<uint64_t>(length));
-}
-
 static const JNINativeMethod g_LongArrayMultiStateCounter_methods[] = {
         // @CriticalNative
         {"native_init", "(II)J", (void *)native_init},
@@ -228,18 +238,18 @@
         {"native_setState", "(JIJ)V", (void *)native_setState},
         // @CriticalNative
         {"native_copyStatesFrom", "(JJ)V", (void *)native_copyStatesFrom},
-        // @CriticalNative
-        {"native_setValues", "(JIJ)V", (void *)native_setValues},
-        // @CriticalNative
-        {"native_updateValues", "(JJJ)V", (void *)native_updateValues},
-        // @CriticalNative
-        {"native_incrementValues", "(JJJ)V", (void *)native_incrementValues},
-        // @CriticalNative
-        {"native_addCounts", "(JJ)V", (void *)native_addCounts},
+        // @FastNative
+        {"native_setValues", "(JI[J)V", (void *)native_setValues},
+        // @FastNative
+        {"native_updateValues", "(J[JJ)V", (void *)native_updateValues},
+        // @FastNative
+        {"native_incrementValues", "(J[JJ)V", (void *)native_incrementValues},
+        // @FastNative
+        {"native_addCounts", "(J[J)V", (void *)native_addCounts},
         // @CriticalNative
         {"native_reset", "(J)V", (void *)native_reset},
-        // @CriticalNative
-        {"native_getCounts", "(JJI)V", (void *)native_getCounts},
+        // @FastNative
+        {"native_getCounts", "(J[JI)V", (void *)native_getCounts},
         // @FastNative
         {"native_toString", "(J)Ljava/lang/String;", (void *)native_toString},
         // @FastNative
@@ -252,91 +262,12 @@
         {"native_getArrayLength", "(J)I", (void *)native_getArrayLength},
 };
 
-/////////////////////// LongArrayMultiStateCounter.LongArrayContainer ////////////////////////
-
-static void native_dispose_LongArrayContainer(jlong nativePtr) {
-    std::vector<uint64_t> *vector = reinterpret_cast<std::vector<uint64_t> *>(nativePtr);
-    delete vector;
-}
-
-static jlong native_getReleaseFunc_LongArrayContainer() {
-    return reinterpret_cast<jlong>(native_dispose_LongArrayContainer);
-}
-
-static void native_setValues_LongArrayContainer(JNIEnv *env, jclass, jlong nativePtr,
-                                                jlongArray jarray) {
-    std::vector<uint64_t> *vector = reinterpret_cast<std::vector<uint64_t> *>(nativePtr);
-    ScopedLongArrayRO scopedArray(env, jarray);
-    const uint64_t *array = reinterpret_cast<const uint64_t *>(scopedArray.get());
-    uint8_t size = scopedArray.size();
-
-    // Boundary checks are performed in the Java layer
-    std::copy(array, array + size, vector->data());
-}
-
-static void native_getValues_LongArrayContainer(JNIEnv *env, jclass, jlong nativePtr,
-                                                jlongArray jarray) {
-    std::vector<uint64_t> *vector = reinterpret_cast<std::vector<uint64_t> *>(nativePtr);
-    ScopedLongArrayRW scopedArray(env, jarray);
-
-    // Boundary checks are performed in the Java layer
-    std::copy(vector->data(), vector->data() + vector->size(), scopedArray.get());
-}
-
-static jboolean native_combineValues_LongArrayContainer(JNIEnv *env, jclass, jlong nativePtr,
-                                                        jlongArray jarray, jintArray jindexMap) {
-    std::vector<uint64_t> *vector = reinterpret_cast<std::vector<uint64_t> *>(nativePtr);
-    ScopedLongArrayRW scopedArray(env, jarray);
-    ScopedIntArrayRO scopedIndexMap(env, jindexMap);
-
-    const uint64_t *data = vector->data();
-    uint64_t *array = reinterpret_cast<uint64_t *>(scopedArray.get());
-    const uint8_t size = scopedArray.size();
-
-    for (int i = 0; i < size; i++) {
-        array[i] = 0;
-    }
-
-    bool nonZero = false;
-    for (size_t i = 0; i < vector->size(); i++) {
-        jint index = scopedIndexMap[i];
-        if (index < 0 || index >= size) {
-            jniThrowExceptionFmt(env, "java/lang/IndexOutOfBoundsException",
-                                 "Index %d is out of bounds: [0, %d]", index, size - 1);
-            return false;
-        }
-
-        if (data[i] != 0L) {
-            array[index] += data[i];
-            nonZero = true;
-        }
-    }
-
-    return nonZero;
-}
-
-static const JNINativeMethod g_LongArrayContainer_methods[] = {
-        // @CriticalNative
-        {"native_init", "(I)J", (void *)native_init_LongArrayContainer},
-        // @CriticalNative
-        {"native_getReleaseFunc", "()J", (void *)native_getReleaseFunc_LongArrayContainer},
-        // @FastNative
-        {"native_setValues", "(J[J)V", (void *)native_setValues_LongArrayContainer},
-        // @FastNative
-        {"native_getValues", "(J[J)V", (void *)native_getValues_LongArrayContainer},
-        // @FastNative
-        {"native_combineValues", "(J[J[I)Z", (void *)native_combineValues_LongArrayContainer},
-};
+} // namespace battery
 
 int register_com_android_internal_os_LongArrayMultiStateCounter(JNIEnv *env) {
     // 0 represents success, thus "|" and not "&"
     return RegisterMethodsOrDie(env, "com/android/internal/os/LongArrayMultiStateCounter",
-                                g_LongArrayMultiStateCounter_methods,
-                                NELEM(g_LongArrayMultiStateCounter_methods)) |
-            RegisterMethodsOrDie(env,
-                                 "com/android/internal/os/LongArrayMultiStateCounter"
-                                 "$LongArrayContainer",
-                                 g_LongArrayContainer_methods, NELEM(g_LongArrayContainer_methods));
+                                battery::g_LongArrayMultiStateCounter_methods,
+                                NELEM(battery::g_LongArrayMultiStateCounter_methods));
 }
-
 } // namespace android
diff --git a/core/jni/com_android_internal_os_LongMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongMultiStateCounter.cpp
index 56d3fbb..b3bfd0bc 100644
--- a/core/jni/com_android_internal_os_LongMultiStateCounter.cpp
+++ b/core/jni/com_android_internal_os_LongMultiStateCounter.cpp
@@ -28,7 +28,7 @@
 
 namespace battery {
 
-typedef battery::MultiStateCounter<int64_t> LongMultiStateCounter;
+typedef battery::MultiStateCounter<int64_t, int64_t> LongMultiStateCounter;
 
 template <>
 bool LongMultiStateCounter::delta(const int64_t &previousValue, const int64_t &newValue,
@@ -47,12 +47,6 @@
         *value1 += value2;
     }
 }
-
-template <>
-std::string LongMultiStateCounter::valueToString(const int64_t &v) const {
-    return std::to_string(v);
-}
-
 } // namespace battery
 
 static inline battery::LongMultiStateCounter *asLongMultiStateCounter(const jlong nativePtr) {
diff --git a/core/jni/platform/host/HostRuntime.cpp b/core/jni/platform/host/HostRuntime.cpp
index 3747299..7fca117 100644
--- a/core/jni/platform/host/HostRuntime.cpp
+++ b/core/jni/platform/host/HostRuntime.cpp
@@ -87,7 +87,6 @@
 extern int register_android_os_Parcel(JNIEnv* env);
 extern int register_android_os_SystemClock(JNIEnv* env);
 extern int register_android_os_SystemProperties(JNIEnv* env);
-extern int register_android_os_Trace(JNIEnv* env);
 extern int register_android_text_AndroidCharacter(JNIEnv* env);
 extern int register_android_util_EventLog(JNIEnv* env);
 extern int register_android_util_Log(JNIEnv* env);
@@ -133,7 +132,6 @@
 #endif
         {"android.os.SystemClock", REG_JNI(register_android_os_SystemClock)},
         {"android.os.SystemProperties", REG_JNI(register_android_os_SystemProperties)},
-        {"android.os.Trace", REG_JNI(register_android_os_Trace)},
         {"android.text.AndroidCharacter", REG_JNI(register_android_text_AndroidCharacter)},
         {"android.util.EventLog", REG_JNI(register_android_util_EventLog)},
         {"android.util.Log", REG_JNI(register_android_util_Log)},
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index 654d83c..407790c 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -465,6 +465,7 @@
     repeated .android.graphics.RectProto unrestricted_keep_clear_areas = 46;
     repeated .android.view.InsetsSourceProto mergedLocalInsetsSources = 47;
     optional int32 requested_visible_types = 48;
+    optional .android.graphics.RectProto dim_bounds = 49;
 }
 
 message IdentifierProto {
diff --git a/core/proto/android/service/appwidget.proto b/core/proto/android/service/appwidget.proto
index 97350ef..fb90719 100644
--- a/core/proto/android/service/appwidget.proto
+++ b/core/proto/android/service/appwidget.proto
@@ -20,6 +20,8 @@
 option java_multiple_files = true;
 option java_outer_classname = "AppWidgetServiceProto";
 
+import "frameworks/base/core/proto/android/widget/remoteviews.proto";
+
 // represents the object holding the dump info of the app widget service
 message AppWidgetServiceDumpProto {
     repeated WidgetProto widgets = 1; // the array of bound widgets
@@ -38,3 +40,14 @@
     optional int32 maxHeight = 9;
     optional bool restoreCompleted = 10;
 }
+
+// represents a set of widget previews for a particular provider
+message GeneratedPreviewsProto {
+    repeated Preview previews = 1;
+
+    // represents a particular RemoteViews preview, which may be set for multiple categories
+    message Preview {
+        repeated int32 widget_categories = 1;
+        optional android.widget.RemoteViewsProto views = 2;
+    }
+}
\ No newline at end of file
diff --git a/core/res/Android.bp b/core/res/Android.bp
index f6ca821..66c2e12 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -171,6 +171,7 @@
         "android.security.flags-aconfig",
         "com.android.hardware.input.input-aconfig",
         "aconfig_trade_in_mode_flags",
+        "ranging_aconfig_flags",
     ],
 }
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 0479318..5913992 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2412,6 +2412,16 @@
                 android:label="@string/permlab_nearby_wifi_devices"
                 android:protectionLevel="dangerous" />
 
+    <!-- Required to be able to range to devices using generic ranging module.
+         @FlaggedApi("android.permission.flags.ranging_permission_enabled")
+         <p>Protection level: dangerous -->
+    <permission android:name="android.permission.RANGING"
+        android:permissionGroup="android.permission-group.UNDEFINED"
+        android:description="@string/permdesc_ranging"
+        android:label="@string/permlab_ranging"
+        android:protectionLevel="dangerous"
+        android:featureFlag="android.permission.flags.ranging_permission_enabled"/>
+
     <!-- @SystemApi @TestApi Allows an application to suspend other apps, which will prevent the
          user from using them until they are unsuspended.
          @hide
diff --git a/core/res/res/drawable-watch/ic_lock_bugreport.xml b/core/res/res/drawable-watch/ic_lock_bugreport.xml
index b664fe4f..35834be 100644
--- a/core/res/res/drawable-watch/ic_lock_bugreport.xml
+++ b/core/res/res/drawable-watch/ic_lock_bugreport.xml
@@ -13,19 +13,6 @@
     See the License for the specific language governing permissions and
     limitations under the License.
 -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24.0dp"
-        android:height="24.0dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"
-        android:tint="@android:color/white">
-    <path
-        android:fillColor="@android:color/white"
-        android:pathData="M20,10V8h-2.81c-0.45,-0.78 -1.07,-1.46 -1.82,-1.96L17,4.41L15.59,3l-2.17,2.17c-0.03,-0.01 -0.05,-0.01 -0.08,-0.01c-0.16,-0.04 -0.32,-0.06 -0.49,-0.09c-0.06,-0.01 -0.11,-0.02 -0.17,-0.03C12.46,5.02 12.23,5 12,5h0c-0.49,0 -0.97,0.07 -1.42,0.18l0.02,-0.01L8.41,3L7,4.41l1.62,1.63l0.01,0C7.88,6.54 7.26,7.22 6.81,8H4v2h2.09C6.03,10.33 6,10.66 6,11v1H4v2h2v1c0,0.34 0.04,0.67 0.09,1H4v2h2.81c1.04,1.79 2.97,3 5.19,3h0c2.22,0 4.15,-1.21 5.19,-3H20v-2h-2.09l0,0c0.05,-0.33 0.09,-0.66 0.09,-1v-1h2v-2h-2v-1c0,-0.34 -0.04,-0.67 -0.09,-1l0,0H20zM16,15c0,2.21 -1.79,4 -4,4c-2.21,0 -4,-1.79 -4,-4v-4c0,-2.21 1.79,-4 4,-4h0c2.21,0 4,1.79 4,4V15z"/>
-    <path
-        android:fillColor="@android:color/white"
-        android:pathData="M10,14h4v2h-4z"/>
-    <path
-        android:fillColor="@android:color/white"
-        android:pathData="M10,10h4v2h-4z"/>
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="960" android:viewportHeight="960" android:tint="?attr/colorControlNormal">
+<path android:fillColor="@android:color/white" android:pathData="M480.01,848.13Q412.37,848.13 354.6,814.34Q296.83,780.54 263.87,721.91L193.78,721.91Q175.97,721.91 163.92,709.86Q151.87,697.81 151.87,680Q151.87,662.19 163.92,650.14Q175.97,638.09 193.78,638.09L235.63,638.09Q232.81,619.12 232.34,600.16Q231.87,581.2 231.87,561.91L193.78,561.91Q175.97,561.91 163.92,549.86Q151.87,537.81 151.87,520Q151.87,502.19 163.92,490.14Q175.97,478.09 193.78,478.09L231.87,478.09Q231.87,458.8 232.34,439.84Q232.81,420.88 235.63,401.91L193.78,401.91Q175.97,401.91 163.92,389.86Q151.87,377.81 151.87,360Q151.87,342.19 163.92,330.14Q175.97,318.09 193.78,318.09L265.07,318.09Q278.11,294.13 295.97,273.77Q313.83,253.41 337.07,237.93L301.26,201.37Q289.54,189.65 289.66,172.32Q289.78,154.98 302.26,142.5Q313.98,130.78 331.7,130.78Q349.41,130.78 361.13,142.5L417.7,199.07Q447.13,188.63 477.92,188.39Q508.72,188.15 538.15,198.35L597.2,140.3Q608.79,128.59 626.19,128.59Q643.59,128.59 656.07,141.07Q667.78,152.78 667.78,170.5Q667.78,188.22 656.07,199.93L619.98,236.02Q644.41,251.98 663.51,273.03Q682.61,294.09 696.38,320L767.17,320Q784.41,320 796.27,331.86Q808.13,343.72 808.13,360.96Q808.13,378.2 796.27,390.05Q784.41,401.91 767.17,401.91L724.37,401.91Q727.19,420.88 727.66,439.84Q728.13,458.8 728.13,478.09L766.22,478.09Q784.03,478.09 796.08,490.14Q808.13,502.19 808.13,520Q808.13,537.81 796.08,549.86Q784.03,561.91 766.22,561.91L728.13,561.91Q728.13,581.2 727.51,600.12Q726.89,619.04 724.13,638.09L766.22,638.09Q784.03,638.09 796.08,650.14Q808.13,662.19 808.13,680Q808.13,697.81 796.08,709.86Q784.03,721.91 766.22,721.91L696.13,721.91Q663.17,780.54 605.42,814.34Q547.66,848.13 480.01,848.13ZM441.91,641.91L518.09,641.91Q535.9,641.91 547.95,629.86Q560,617.81 560,600Q560,582.19 547.95,570.14Q535.9,558.09 518.09,558.09L441.91,558.09Q424.1,558.09 412.05,570.14Q400,582.19 400,600Q400,617.81 412.05,629.86Q424.1,641.91 441.91,641.91ZM441.91,481.91L518.09,481.91Q535.9,481.91 547.95,469.86Q560,457.81 560,440Q560,422.19 547.95,410.14Q535.9,398.09 518.09,398.09L441.91,398.09Q424.1,398.09 412.05,410.14Q400,422.19 400,440Q400,457.81 412.05,469.86Q424.1,481.91 441.91,481.91Z"/>
 </vector>
diff --git a/core/res/res/drawable-watch/ic_lock_power_off.xml b/core/res/res/drawable-watch/ic_lock_power_off.xml
index b437a4b..c42d7d2 100644
--- a/core/res/res/drawable-watch/ic_lock_power_off.xml
+++ b/core/res/res/drawable-watch/ic_lock_power_off.xml
@@ -14,13 +14,6 @@
   ~ limitations under the License.
   -->
 
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24"
-        android:viewportHeight="24"
-        android:tint="@android:color/white">
-    <path
-        android:fillColor="@android:color/white"
-        android:pathData="M11,2h2v10h-2zM18.37,5.64l-1.41,1.41c2.73,2.73 2.72,7.16 -0.01,9.89 -2.73,2.73 -7.17,2.73 -9.89,0.01 -2.73,-2.73 -2.74,-7.18 -0.01,-9.91l-1.41,-1.4c-3.51,3.51 -3.51,9.21 0.01,12.73 3.51,3.51 9.21,3.51 12.72,-0.01 3.51,-3.51 3.51,-9.2 0,-12.72z"/>
-</vector>
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="960" android:viewportHeight="960" android:tint="?attr/colorControlNormal">
+<path android:fillColor="@android:color/white" android:pathData="M480,888.13Q395.09,888.13 320.65,856.03Q246.22,823.93 191.14,768.86Q136.07,713.78 103.97,639.35Q71.87,564.91 71.87,480Q71.87,406.52 96.01,340.78Q120.15,275.04 162.91,222.33Q175.11,206.65 192.52,207.41Q209.93,208.17 222.61,219.37Q235.28,230.57 239.26,248.84Q243.24,267.11 227.8,287.22Q197.48,327.26 180.17,376.09Q162.87,424.91 162.87,480Q162.87,613.04 254.91,705.09Q346.96,797.13 480,797.13Q613.04,797.13 705.09,705.09Q797.13,613.04 797.13,480Q797.13,424.91 779.83,376.09Q762.52,327.26 732.2,287.22Q716.76,267.11 720.74,248.84Q724.72,230.57 737.39,219.37Q750.07,208.17 767.48,207.41Q784.89,206.65 797.09,222.33Q839.85,275.04 863.99,340.78Q888.13,406.52 888.13,480Q888.13,564.91 856.03,639.35Q823.93,713.78 768.86,768.86Q713.78,823.93 639.35,856.03Q564.91,888.13 480,888.13ZM480,525.5Q460.85,525.5 447.67,512.33Q434.5,499.15 434.5,480L434.5,117.37Q434.5,98.22 447.67,85.04Q460.85,71.87 480,71.87Q499.15,71.87 512.33,85.04Q525.5,98.22 525.5,117.37L525.5,480Q525.5,499.15 512.33,512.33Q499.15,525.5 480,525.5Z"/>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable-watch/ic_restart.xml b/core/res/res/drawable-watch/ic_restart.xml
index 52933aa..ddcfd25 100644
--- a/core/res/res/drawable-watch/ic_restart.xml
+++ b/core/res/res/drawable-watch/ic_restart.xml
@@ -14,13 +14,6 @@
   ~ limitations under the License.
   -->
 
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24"
-        android:viewportHeight="24"
-        android:tint="@android:color/white">
-    <path
-        android:fillColor="@android:color/white"
-        android:pathData="M6,13c0,-1.65 0.67,-3.15 1.76,-4.24L6.34,7.34C4.9,8.79 4,10.79 4,13c0,4.08 3.05,7.44 7,7.93v-2.02c-2.83,-0.48 -5,-2.94 -5,-5.91zM20,13c0,-4.42 -3.58,-8 -8,-8 -0.06,0 -0.12,0.01 -0.18,0.01l1.09,-1.09L11.5,2.5 8,6l3.5,3.5 1.41,-1.41 -1.08,-1.08c0.06,0 0.12,-0.01 0.17,-0.01 3.31,0 6,2.69 6,6 0,2.97 -2.17,5.43 -5,5.91v2.02c3.95,-0.49 7,-3.85 7,-7.93z"/>
-</vector>
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="960" android:viewportHeight="960" android:tint="?attr/colorControlNormal">
+<path android:fillColor="@android:color/white" android:pathData="M385.83,834.46Q282.35,803.54 217.11,717.61Q151.87,631.67 151.87,520.48Q151.87,464.91 169.91,414.25Q187.96,363.59 220.8,320.83Q232.76,305.72 252.11,304.98Q271.46,304.24 286.85,319.63Q298.57,331.35 299.3,348.66Q300.04,365.98 288.8,381.41Q266.72,411.22 254.79,446.54Q242.87,481.87 242.87,520.48Q242.87,599.33 288.46,661.27Q334.04,723.22 406.41,746.7Q421.09,751.65 430.54,764.09Q440,776.52 440,790.96Q440,814.3 423.73,827.6Q407.46,840.89 385.83,834.46ZM574.17,834.46Q552.54,840.89 536.27,827.22Q520,813.54 520,790.2Q520,776.76 529.46,764.21Q538.91,751.65 553.59,746.7Q625.72,722.22 671.42,660.65Q717.13,599.09 717.13,520.48Q717.13,423.11 649.64,354.3Q582.15,285.5 485.02,283.59L481.07,283.59L497.3,299.83Q509.02,311.54 509.02,329.14Q509.02,346.74 497.3,358.46Q485.59,370.17 467.99,370.17Q450.39,370.17 438.67,358.46L348.46,268.24Q341.74,261.52 338.76,253.45Q335.78,245.37 335.78,236.41Q335.78,227.46 338.76,219.38Q341.74,211.3 348.46,204.59L438.67,114.13Q450.39,102.41 467.99,102.41Q485.59,102.41 497.3,114.13Q509.02,125.85 509.02,143.45Q509.02,161.04 497.3,172.76L477.72,192.35L481.91,192.35Q618.54,192.35 713.34,287.98Q808.13,383.61 808.13,520.48Q808.13,630.91 742.89,717.11Q677.65,803.3 574.17,834.46Z"/>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable-watch/ic_settings.xml b/core/res/res/drawable-watch/ic_settings.xml
new file mode 100644
index 0000000..cef10e9
--- /dev/null
+++ b/core/res/res/drawable-watch/ic_settings.xml
@@ -0,0 +1,19 @@
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="960" android:viewportHeight="960" android:tint="?attr/colorControlNormal">
+<path android:fillColor="@android:color/white" android:pathData="M428.46,888.13Q400.26,888.13 379.92,869.53Q359.59,850.93 355.59,823.74L346.59,757.5Q335.5,753.22 325.55,747.17Q315.61,741.13 306.04,734.33L244.28,760.33Q218.33,771.57 192.13,762.45Q165.93,753.33 151.46,729.13L99.91,638.76Q85.43,614.8 91.55,587.73Q97.67,560.65 119.63,543.17L172.39,503.17Q171.63,497.13 171.63,491.59Q171.63,486.04 171.63,480Q171.63,473.96 171.63,468.41Q171.63,462.87 172.39,456.83L119.63,417.07Q97.43,399.59 91.43,372.51Q85.43,345.43 99.91,321.24L151.46,231.11Q165.93,207.15 192.01,197.91Q218.09,188.67 244.04,199.91L306.52,225.91Q316.09,219.11 326.17,213.18Q336.26,207.26 346.59,202.98L355.59,136.5Q359.59,109.07 379.92,90.47Q400.26,71.87 428.46,71.87L531.54,71.87Q559.74,71.87 580.08,90.47Q600.41,109.07 604.41,136.5L613.41,202.98Q624.5,207.26 634.45,213.18Q644.39,219.11 653.96,225.91L715.72,199.91Q741.67,188.67 767.87,197.91Q794.07,207.15 808.54,231.11L860.09,321.24Q874.57,345.43 868.57,372.51Q862.57,399.59 840.37,417.07L787.37,456.83Q788.13,462.87 788.13,468.41Q788.13,473.96 788.13,480Q788.13,486.04 788.01,491.59Q787.89,497.13 786.37,503.17L839.37,542.93Q861.57,560.41 867.57,587.49Q873.57,614.57 859.09,638.76L806.54,729.13Q792.07,753.09 765.99,762.33Q739.91,771.57 713.96,760.33L653.48,734.33Q643.91,741.13 633.83,747.17Q623.74,753.22 613.41,757.5L604.41,823.74Q600.41,850.93 580.08,869.53Q559.74,888.13 531.54,888.13L428.46,888.13ZM481.28,620Q539.28,620 580.28,579Q621.28,538 621.28,480Q621.28,422 580.28,381Q539.28,340 481.28,340Q422.52,340 381.9,381Q341.28,422 341.28,480Q341.28,538 381.9,579Q422.52,620 481.28,620Z"/>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/layout/input_method_switch_item_new.xml b/core/res/res/layout/input_method_switch_item_new.xml
index f8710cc..7b241af 100644
--- a/core/res/res/layout/input_method_switch_item_new.xml
+++ b/core/res/res/layout/input_method_switch_item_new.xml
@@ -18,18 +18,20 @@
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/list_item"
     android:layout_width="match_parent"
-    android:layout_height="72dp"
+    android:layout_height="wrap_content"
+    android:minHeight="72dp"
     android:background="@drawable/input_method_switch_item_background"
     android:gravity="center_vertical"
     android:orientation="horizontal"
     android:layout_marginHorizontal="16dp"
     android:layout_marginBottom="8dp"
     android:paddingStart="20dp"
-    android:paddingEnd="24dp">
+    android:paddingEnd="24dp"
+    android:paddingVertical="8dp">
 
     <LinearLayout
         android:layout_width="0dp"
-        android:layout_height="match_parent"
+        android:layout_height="wrap_content"
         android:layout_weight="1"
         android:gravity="start|center_vertical"
         android:orientation="vertical">
@@ -39,11 +41,26 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:ellipsize="marquee"
+            android:marqueeRepeatLimit="1"
             android:singleLine="true"
             android:fontFamily="google-sans-text"
             android:textColor="@color/input_method_switch_on_item"
             android:textAppearance="?attr/textAppearanceListItem"/>
 
+        <TextView
+            android:id="@+id/text2"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="4dp"
+            android:ellipsize="marquee"
+            android:marqueeRepeatLimit="1"
+            android:singleLine="true"
+            android:fontFamily="google-sans-text"
+            android:textColor="?attr/materialColorOnSurfaceVariant"
+            android:textAppearance="?attr/textAppearanceListItemSecondary"
+            android:textAllCaps="true"
+            android:visibility="gone"/>
+
     </LinearLayout>
 
     <ImageView
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 092d2a72..e6dedce 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4418,6 +4418,10 @@
     <declare-styleable name="InputMethod_Subtype">
         <!-- The name of the subtype. -->
         <attr name="label" />
+        <!-- The layout label of the subtype.
+             {@link android.view.inputmethod.InputMethodSubtype#getLayoutDisplayName} returns the
+             value specified in this attribute. -->
+        <attr name="layoutLabel" format="reference" />
         <!-- The icon of the subtype. -->
         <attr name="icon" />
         <!-- The locale of the subtype. This string should be a locale (for example en_US and fr_FR)
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index ac9bb93..7402a2f 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3087,6 +3087,12 @@
     <!-- Whether UI for multi user should be shown -->
     <bool name="config_enableMultiUserUI">false</bool>
 
+    <!-- Indicates the boot strategy in Headless System User Mode (HSUM)
+         This config has no effect if the device is not in HSUM.
+         0 (Default) : boot to the previous foreground user if there is one, otherwise the first switchable user.
+         1 : boot to the first switchable full user for initial boot (unprovisioned device), else to the headless system user, i.e. user 0. -->
+    <integer name="config_hsumBootStrategy">0</integer>
+
     <!-- Whether to boot system with the headless system user, i.e. user 0. If set to true,
          system will be booted with the headless system user, or user 0. It has no effect if device
          is not in Headless System User Mode (HSUM). -->
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 779422a..31e9913 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -472,10 +472,13 @@
     <integer name="config_mt_sms_polling_throttle_millis">300000</integer>
     <java-symbol type="integer" name="config_mt_sms_polling_throttle_millis" />
 
-
     <!-- The receiver class of the intent that hidden menu sends to start satellite non-emergency mode -->
     <string name="config_satellite_carrier_roaming_non_emergency_session_class" translatable="false"></string>
     <java-symbol type="string" name="config_satellite_carrier_roaming_non_emergency_session_class" />
 
+    <!-- Whether to show the system notification to users whenever there is a change
+     in the satellite availability state at the current location. -->
+    <bool name="config_satellite_should_notify_availability">false</bool>
+    <java-symbol type="bool" name="config_satellite_should_notify_availability" />
 
 </resources>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 0c28ea4..70cc5f1 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -125,6 +125,8 @@
     <public name="supplementalDescription"/>
     <!-- @FlaggedApi("android.security.enable_intent_matching_flags") -->
     <public name="intentMatchingFlags"/>
+    <!-- @FlaggedApi(android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP_API) -->
+    <public name="layoutLabel"/>
   </staging-public-group>
 
   <staging-public-group type="id" first-id="0x01b60000">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d3ef07ca..c13fdb1 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1766,6 +1766,11 @@
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=140]-->
     <string name="permdesc_nearby_wifi_devices">Allows the app to advertise, connect, and determine the relative position of nearby Wi\u2011Fi devices</string>
 
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=50]-->
+    <string name="permlab_ranging">determine relative position between nearby devices</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=120]-->
+    <string name="permdesc_ranging">Allow the app to determine relative position between nearby devices</string>
+
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_preferredPaymentInfo">Preferred NFC Payment Service Information</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
@@ -6522,6 +6527,54 @@
     <string name="satellite_manual_selection_state_popup_cancel">Go back</string>
     <!-- Initial/System provided label shown for an app which gets unarchived. [CHAR LIMIT=64]. -->
     <string name="unarchival_session_app_label">Pending...</string>
+    <!-- Notification title when satellite service is available. -->
+    <string name="satellite_sos_available_notification_title">Satellite SOS is now available</string>
+    <!-- Notification summary when satellite service is available. [CHAR LIMIT=NONE] -->
+    <string name="satellite_sos_available_notification_summary">You can message emergency services if there\'s no mobile or Wi-Fi network. Google Messages must be your default messaging app.</string>
+    <!-- Notification title when satellite service is not supported by device. -->
+    <string name="satellite_sos_not_supported_notification_title">Satellite SOS isn\'t supported</string>
+    <!-- Notification summary when satellite service is not supported by device. [CHAR LIMIT=NONE] -->
+    <string name="satellite_sos_not_supported_notification_summary">Satellite SOS isn\'t supported on this device</string>
+    <!-- Notification title when satellite service is not provisioned. [CHAR LIMIT=NONE] -->
+    <string name="satellite_sos_not_provisioned_notification_title">Satellite SOS isn\'t set up</string>
+    <!-- Notification summary when satellite service is not provisioned. [CHAR LIMIT=NONE] -->
+    <string name="satellite_sos_not_provisioned_notification_summary">Make sure you\'re connected to the internet and try setup again</string>
+    <!-- Notification title when satellite service is not allowed at current location. -->
+    <string name="satellite_sos_not_in_allowed_region_notification_title">Satellite SOS isn\'t available</string>
+    <!-- Notification summary when satellite service is not allowed at current location. [CHAR LIMIT=NONE] -->
+    <string name="satellite_sos_not_in_allowed_region_notification_summary">Satellite SOS isn\'t available in this country or region</string>
+    <!-- Notification title when default messaging app does not support satellite. [CHAR LIMIT=NONE] -->
+    <string name="satellite_sos_unsupported_default_sms_app_notification_title">Satellite SOS not set up</string>
+    <!-- Notification summary when default messaging app does not support satellite. [CHAR LIMIT=NONE] -->
+    <string name="satellite_sos_unsupported_default_sms_app_notification_summary">To message by satellite, set Google Messages as your default messaging app</string>
+    <!-- Notification title when location settings is disabled. -->
+    <string name="satellite_sos_location_disabled_notification_title">Satellite SOS isn\'t available</string>
+    <!-- Notification summary when location settings is disabled. [CHAR LIMIT=NONE] -->
+    <string name="satellite_sos_location_disabled_notification_summary">To check if satellite SOS is available in this country or region, turn on location settings</string>
+    <!-- Notification title when satellite service is available. -->
+    <string name="satellite_messaging_available_notification_title">Satellite messaging available</string>
+    <!-- Notification summary when satellite service is available. [CHAR LIMIT=NONE] -->
+    <string name="satellite_messaging_available_notification_summary">You can message by satellite if there\'s no mobile or Wi-Fi network. Google Messages must be your default messaging app.</string>
+    <!-- Notification title when satellite service is not supported by device. -->
+    <string name="satellite_messaging_not_supported_notification_title">Satellite messaging not supported</string>
+    <!-- Notification summary when satellite service is not supported by device. [CHAR LIMIT=NONE] -->
+    <string name="satellite_messaging_not_supported_notification_summary">Satellite messaging isn\'t supported on this device</string>
+    <!-- Notification title when satellite service is not provisioned. [CHAR LIMIT=NONE] -->
+    <string name="satellite_messaging_not_provisioned_notification_title">Satellite messaging not set up</string>
+    <!-- Notification summary when satellite service is not provisioned. [CHAR LIMIT=NONE] -->
+    <string name="satellite_messaging_not_provisioned_notification_summary">Make sure you\'re connected to the internet and try setup again</string>
+    <!-- Notification title when satellite service is not allowed at current location. -->
+    <string name="satellite_messaging_not_in_allowed_region_notification_title">Satellite messaging not available</string>
+    <!-- Notification summary when satellite service is not allowed at current location. [CHAR LIMIT=NONE] -->
+    <string name="satellite_messaging_not_in_allowed_region_notification_summary">Satellite messaging isn\'t available in this country or region</string>
+    <!-- Notification title when default messaging app does not support satellite. [CHAR LIMIT=NONE] -->
+    <string name="satellite_messaging_unsupported_default_sms_app_notification_title">Satellite messaging not set up</string>
+    <!-- Notification summary when default messaging app does not support satellite. [CHAR LIMIT=NONE] -->
+    <string name="satellite_messaging_unsupported_default_sms_app_notification_summary">To message by satellite, set Google Messages as your default messaging app</string>
+    <!-- Notification title when location settings is disabled. -->
+    <string name="satellite_messaging_location_disabled_notification_title">Satellite messaging not available</string>
+    <!-- Notification summary when location settings is disabled. [CHAR LIMIT=NONE] -->
+    <string name="satellite_messaging_location_disabled_notification_summary">To check if satellite messaging is available in this country or region, turn on location settings</string>
 
     <!-- Fingerprint dangling notification title -->
     <string name="fingerprint_dangling_notification_title">Set up Fingerprint Unlock again</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 515ebd5..fec8bbb 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -312,6 +312,7 @@
   <java-symbol type="bool" name="config_disableLockscreenByDefault" />
   <java-symbol type="bool" name="config_enableBurnInProtection" />
   <java-symbol type="bool" name="config_hotswapCapable" />
+  <java-symbol type="integer" name="config_hsumBootStrategy" />
   <java-symbol type="bool" name="config_mms_content_disposition_support" />
   <java-symbol type="bool" name="config_networkSamplingWakesDevice" />
   <java-symbol type="bool" name="config_showMenuShortcutsWhenKeyboardPresent" />
@@ -5551,6 +5552,30 @@
   <java-symbol type="string" name="satellite_manual_selection_state_popup_cancel" />
   <java-symbol type="drawable" name="ic_satellite_alt_24px" />
   <java-symbol type="drawable" name="ic_android_satellite_24px" />
+  <java-symbol type="string" name="satellite_sos_available_notification_title" />
+  <java-symbol type="string" name="satellite_sos_available_notification_summary" />
+  <java-symbol type="string" name="satellite_sos_not_in_allowed_region_notification_title" />
+  <java-symbol type="string" name="satellite_sos_not_in_allowed_region_notification_summary" />
+  <java-symbol type="string" name="satellite_sos_not_supported_notification_title" />
+  <java-symbol type="string" name="satellite_sos_not_supported_notification_summary" />
+  <java-symbol type="string" name="satellite_sos_not_provisioned_notification_title" />
+  <java-symbol type="string" name="satellite_sos_not_provisioned_notification_summary" />
+  <java-symbol type="string" name="satellite_sos_unsupported_default_sms_app_notification_title" />
+  <java-symbol type="string" name="satellite_sos_unsupported_default_sms_app_notification_summary" />
+  <java-symbol type="string" name="satellite_sos_location_disabled_notification_title" />
+  <java-symbol type="string" name="satellite_sos_location_disabled_notification_summary" />
+  <java-symbol type="string" name="satellite_messaging_available_notification_title" />
+  <java-symbol type="string" name="satellite_messaging_available_notification_summary" />
+  <java-symbol type="string" name="satellite_messaging_not_in_allowed_region_notification_title" />
+  <java-symbol type="string" name="satellite_messaging_not_in_allowed_region_notification_summary" />
+  <java-symbol type="string" name="satellite_messaging_not_supported_notification_title" />
+  <java-symbol type="string" name="satellite_messaging_not_supported_notification_summary" />
+  <java-symbol type="string" name="satellite_messaging_not_provisioned_notification_title" />
+  <java-symbol type="string" name="satellite_messaging_not_provisioned_notification_summary" />
+  <java-symbol type="string" name="satellite_messaging_unsupported_default_sms_app_notification_title" />
+  <java-symbol type="string" name="satellite_messaging_unsupported_default_sms_app_notification_summary" />
+  <java-symbol type="string" name="satellite_messaging_location_disabled_notification_title" />
+  <java-symbol type="string" name="satellite_messaging_location_disabled_notification_summary" />
 
   <!-- DisplayManager configs. -->
   <java-symbol type="bool" name="config_evenDimmerEnabled" />
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index 24f6cea..8d045f8 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -26,6 +26,7 @@
 import static android.view.Display.INVALID_DISPLAY;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -59,6 +60,7 @@
 import android.app.servertransaction.StopActivityItem;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ApplicationInfo;
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -67,7 +69,11 @@
 import android.hardware.display.VirtualDisplay;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.Looper;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.DisplayMetrics;
 import android.util.Log;
@@ -129,6 +135,9 @@
     @Rule(order = 1)
     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     private ActivityWindowInfoListener mActivityWindowInfoListener;
     private WindowTokenClientController mOriginalWindowTokenClientController;
     private Configuration mOriginalAppConfig;
@@ -912,6 +921,32 @@
     }
 
     /**
+     * Verifies that {@link ActivityThread#handleApplicationInfoChanged} does send updates to the
+     * system context, when given the system application info.
+     */
+    @RequiresFlagsEnabled(android.content.res.Flags.FLAG_SYSTEM_CONTEXT_HANDLE_APP_INFO_CHANGED)
+    @Test
+    public void testHandleApplicationInfoChanged_systemContext() {
+        Looper.prepare();
+        final var systemThread = ActivityThread.createSystemActivityThreadForTesting();
+
+        final Context systemContext = systemThread.getSystemContext();
+        final var appInfo = systemContext.getApplicationInfo();
+        // sourceDir must not be null, and contain at least a '/', for handleApplicationInfoChanged.
+        appInfo.sourceDir = "/";
+
+        // Create a copy of the application info.
+        final var newAppInfo = new ApplicationInfo(appInfo);
+        newAppInfo.sourceDir = "/";
+        assertWithMessage("New application info is a separate instance")
+                .that(systemContext.getApplicationInfo()).isNotSameInstanceAs(newAppInfo);
+
+        systemThread.handleApplicationInfoChanged(newAppInfo);
+        assertWithMessage("Application info was updated successfully")
+                .that(systemContext.getApplicationInfo()).isSameInstanceAs(newAppInfo);
+    }
+
+    /**
      * Calls {@link ActivityThread#handleActivityConfigurationChanged(ActivityClientRecord,
      * Configuration, int, ActivityWindowInfo)} to try to push activity configuration to the
      * activity for the given sequence number.
diff --git a/core/tests/coretests/src/android/view/RoundScrollbarRendererTest.java b/core/tests/coretests/src/android/view/RoundScrollbarRendererTest.java
index 262bd5c..0f17f9c 100644
--- a/core/tests/coretests/src/android/view/RoundScrollbarRendererTest.java
+++ b/core/tests/coretests/src/android/view/RoundScrollbarRendererTest.java
@@ -16,7 +16,11 @@
 
 package android.view;
 
+import static android.view.RoundScrollbarRenderer.BLUECHIP_ENABLED_SYSPROP;
+
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -30,11 +34,8 @@
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.Rect;
+import android.os.SystemProperties;
 import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.view.flags.Flags;
 
 import androidx.test.core.app.ApplicationProvider;
@@ -42,7 +43,6 @@
 import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -66,9 +66,6 @@
     private static final float DEFAULT_ALPHA = 0.5f;
     private static final Rect BOUNDS = new Rect(0, 0, 200, 200);
 
-    @Rule
-    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
     @Mock private Canvas mCanvas;
     @Captor private ArgumentCaptor<Paint> mPaintCaptor;
     private RoundScrollbarRenderer mScrollbar;
@@ -88,8 +85,8 @@
     }
 
     @Test
-    @RequiresFlagsDisabled(Flags.FLAG_USE_REFACTORED_ROUND_SCROLLBAR)
     public void testScrollbarDrawn_legacy() {
+        assumeFalse(usingRefactoredScrollbar());
         mScrollbar.drawRoundScrollbars(mCanvas, DEFAULT_ALPHA, BOUNDS, /* drawToLeft= */ false);
 
         // The arc will be drawn twice, i.e. once for track and once for thumb
@@ -105,8 +102,8 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_USE_REFACTORED_ROUND_SCROLLBAR)
     public void testScrollbarDrawn() {
+        assumeTrue(usingRefactoredScrollbar());
         mScrollbar.drawRoundScrollbars(mCanvas, DEFAULT_ALPHA, BOUNDS, /* drawToLeft= */ false);
 
         // The arc will be drawn thrice, i.e. twice for track and once for thumb
@@ -143,4 +140,9 @@
             return super.computeVerticalScrollExtent();
         }
     }
+
+    private static boolean usingRefactoredScrollbar() {
+        return Flags.useRefactoredRoundScrollbar()
+                && SystemProperties.getBoolean(BLUECHIP_ENABLED_SYSPROP, false);
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java
index 120a4de..3239598 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java
@@ -301,18 +301,14 @@
                         {1_000_000, 2_000_000, 3_000_000},
                         {4_000_000, 5_000_000}});
 
-        LongArrayMultiStateCounter.LongArrayContainer array =
-                new LongArrayMultiStateCounter.LongArrayContainer(5);
+
         long[] out = new long[5];
 
-        success = mInjector.addDelta(TEST_UID, counter, 2000, array);
+        success = mInjector.addDelta(TEST_UID, counter, 2000, out);
         assertThat(success).isTrue();
-
-        array.getValues(out);
         assertThat(out).isEqualTo(new long[]{1, 2, 3, 4, 5});
 
-        counter.getCounts(array, 0);
-        array.getValues(out);
+        counter.getCounts(out, 0);
         assertThat(out).isEqualTo(new long[]{1, 2, 3, 4, 5});
 
         counter.setState(1, 3000);
@@ -322,18 +318,14 @@
                         {11_000_000, 22_000_000, 33_000_000},
                         {44_000_000, 55_000_000}});
 
-        success = mInjector.addDelta(TEST_UID, counter, 4000, array);
+        success = mInjector.addDelta(TEST_UID, counter, 4000, out);
         assertThat(success).isTrue();
-
-        array.getValues(out);
         assertThat(out).isEqualTo(new long[]{10, 20, 30, 40, 50});
 
-        counter.getCounts(array, 0);
-        array.getValues(out);
+        counter.getCounts(out, 0);
         assertThat(out).isEqualTo(new long[]{1 + 5, 2 + 10, 3 + 15, 4 + 20, 5 + 25});
 
-        counter.getCounts(array, 1);
-        array.getValues(out);
+        counter.getCounts(out, 1);
         assertThat(out).isEqualTo(new long[]{5, 10, 15, 20, 25});
     }
 
@@ -385,7 +377,7 @@
 
         @Override
         public boolean addDelta(int uid, LongArrayMultiStateCounter counter, long timestampMs,
-                LongArrayMultiStateCounter.LongArrayContainer deltaOut) {
+                long[] deltaOut) {
             return addDeltaForTest(uid, counter, timestampMs, mCpuTimeInStatePerClusterNs,
                     deltaOut);
         }
diff --git a/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java b/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java
index b86dc58..7e5d0a4 100644
--- a/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java
@@ -24,14 +24,11 @@
 import android.os.Parcel;
 import android.platform.test.ravenwood.RavenwoodRule;
 
-import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import org.junit.Rule;
 import org.junit.Test;
-import org.junit.runner.RunWith;
 
-@RunWith(AndroidJUnit4.class)
 @SmallTest
 public class LongArrayMultiStateCounterTest {
     @Rule
@@ -41,11 +38,11 @@
     public void setStateAndUpdateValue() {
         LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 4);
 
-        updateValue(counter, new long[]{0, 0, 0, 0}, 1000);
+        counter.updateValues(new long[]{0, 0, 0, 0}, 1000);
         counter.setState(0, 1000);
         counter.setState(1, 2000);
         counter.setState(0, 4000);
-        updateValue(counter, new long[]{100, 200, 300, 400}, 9000);
+        counter.updateValues(new long[]{100, 200, 300, 400}, 9000);
 
         assertCounts(counter, 0, new long[]{75, 150, 225, 300});
         assertCounts(counter, 1, new long[]{25, 50, 75, 100});
@@ -55,15 +52,28 @@
     }
 
     @Test
+    public void increment() {
+        LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 4);
+
+        counter.updateValues(new long[]{0, 0, 0, 0}, 1000);
+        counter.setState(0, 1000);
+        counter.incrementValues(new long[]{1, 2, 3, 4}, 2000);
+        counter.incrementValues(new long[]{100, 200, 300, 400}, 3000);
+
+        assertCounts(counter, 0, new long[]{101, 202, 303, 404});
+        assertCounts(counter, 1, new long[]{0, 0, 0, 0});
+    }
+
+    @Test
     public void copyStatesFrom() {
         LongArrayMultiStateCounter source = new LongArrayMultiStateCounter(2, 1);
-        updateValue(source, new long[]{0}, 1000);
+        source.updateValues(new long[]{0}, 1000);
         source.setState(0, 1000);
         source.setState(1, 2000);
 
         LongArrayMultiStateCounter target = new LongArrayMultiStateCounter(2, 1);
         target.copyStatesFrom(source);
-        updateValue(target, new long[]{1000}, 5000);
+        target.updateValues(new long[]{1000}, 5000);
 
         assertCounts(target, 0, new long[]{250});
         assertCounts(target, 1, new long[]{750});
@@ -83,25 +93,25 @@
     public void setEnabled() {
         LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 4);
         counter.setState(0, 1000);
-        updateValue(counter, new long[]{0, 0, 0, 0}, 1000);
-        updateValue(counter, new long[]{100, 200, 300, 400}, 2000);
+        counter.updateValues(new long[]{0, 0, 0, 0}, 1000);
+        counter.updateValues(new long[]{100, 200, 300, 400}, 2000);
 
         assertCounts(counter, 0, new long[]{100, 200, 300, 400});
 
         counter.setEnabled(false, 3000);
 
         // Partially included, because the counter is disabled after the previous update
-        updateValue(counter, new long[]{200, 300, 400, 500}, 4000);
+        counter.updateValues(new long[]{200, 300, 400, 500}, 4000);
 
         // Count only 50%, because the counter was disabled for 50% of the time
         assertCounts(counter, 0, new long[]{150, 250, 350, 450});
 
         // Not counted because the counter is disabled
-        updateValue(counter, new long[]{250, 350, 450, 550}, 5000);
+        counter.updateValues(new long[]{250, 350, 450, 550}, 5000);
 
         counter.setEnabled(true, 6000);
 
-        updateValue(counter, new long[]{300, 400, 500, 600}, 7000);
+        counter.updateValues(new long[]{300, 400, 500, 600}, 7000);
 
         // Again, take 50% of the delta
         assertCounts(counter, 0, new long[]{175, 275, 375, 475});
@@ -111,8 +121,8 @@
     public void reset() {
         LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 4);
         counter.setState(0, 1000);
-        updateValue(counter, new long[]{0, 0, 0, 0}, 1000);
-        updateValue(counter, new long[]{100, 200, 300, 400}, 2000);
+        counter.updateValues(new long[]{0, 0, 0, 0}, 1000);
+        counter.updateValues(new long[]{100, 200, 300, 400}, 2000);
 
         assertCounts(counter, 0, new long[]{100, 200, 300, 400});
 
@@ -120,8 +130,8 @@
 
         assertCounts(counter, 0, new long[]{0, 0, 0, 0});
 
-        updateValue(counter, new long[]{200, 300, 400, 500}, 3000);
-        updateValue(counter, new long[]{300, 400, 500, 600}, 4000);
+        counter.updateValues(new long[]{200, 300, 400, 500}, 3000);
+        counter.updateValues(new long[]{300, 400, 500, 600}, 4000);
 
         assertCounts(counter, 0, new long[]{100, 100, 100, 100});
     }
@@ -129,11 +139,11 @@
     @Test
     public void parceling() {
         LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 4);
-        updateValue(counter, new long[]{0, 0, 0, 0}, 1000);
+        counter.updateValues(new long[]{0, 0, 0, 0}, 1000);
         counter.setState(0, 1000);
-        updateValue(counter, new long[]{100, 200, 300, 400}, 2000);
+        counter.updateValues(new long[]{100, 200, 300, 400}, 2000);
         counter.setState(1, 2000);
-        updateValue(counter, new long[]{101, 202, 304, 408}, 3000);
+        counter.updateValues(new long[]{101, 202, 304, 408}, 3000);
 
         assertCounts(counter, 0, new long[]{100, 200, 300, 400});
         assertCounts(counter, 1, new long[]{1, 2, 4, 8});
@@ -158,27 +168,17 @@
 
         // State, last update timestamp and current counts are undefined at this point.
         newCounter.setState(0, 100);
-        updateValue(newCounter, new long[]{300, 400, 500, 600}, 100);
+        newCounter.updateValues(new long[]{300, 400, 500, 600}, 100);
 
         // A new base state and counters are established; we can continue accumulating deltas
-        updateValue(newCounter, new long[]{316, 432, 564, 728}, 200);
+        newCounter.updateValues(new long[]{316, 432, 564, 728}, 200);
 
         assertCounts(newCounter, 0, new long[]{116, 232, 364, 528});
     }
 
-    private void updateValue(LongArrayMultiStateCounter counter, long[] values, int timestamp) {
-        LongArrayMultiStateCounter.LongArrayContainer container =
-                new LongArrayMultiStateCounter.LongArrayContainer(values.length);
-        container.setValues(values);
-        counter.updateValues(container, timestamp);
-    }
-
     private void assertCounts(LongArrayMultiStateCounter counter, int state, long[] expected) {
-        LongArrayMultiStateCounter.LongArrayContainer container =
-                new LongArrayMultiStateCounter.LongArrayContainer(expected.length);
         long[] counts = new long[expected.length];
-        counter.getCounts(container, state);
-        container.getValues(counts);
+        counter.getCounts(counts, state);
         assertThat(counts).isEqualTo(expected);
     }
 
@@ -230,33 +230,4 @@
         parcel.writeInt(endPos - startPos);
         parcel.setDataPosition(endPos);
     }
-
-    @Test
-    public void combineValues() {
-        long[] values = new long[] {0, 1, 2, 3, 42};
-        LongArrayMultiStateCounter.LongArrayContainer container =
-                new LongArrayMultiStateCounter.LongArrayContainer(values.length);
-        container.setValues(values);
-
-        long[] out = new long[3];
-        int[] indexes = {2, 1, 1, 0, 0};
-        boolean nonZero = container.combineValues(out, indexes);
-        assertThat(nonZero).isTrue();
-        assertThat(out).isEqualTo(new long[]{45, 3, 0});
-
-        // All zeros
-        container.setValues(new long[]{0, 0, 0, 0, 0});
-        nonZero = container.combineValues(out, indexes);
-        assertThat(nonZero).isFalse();
-        assertThat(out).isEqualTo(new long[]{0, 0, 0});
-
-        // Index out of range
-        IndexOutOfBoundsException e1 = assertThrows(
-                IndexOutOfBoundsException.class,
-                () -> container.combineValues(out, new int[]{0, 1, -1, 0, 0}));
-        assertThat(e1.getMessage()).isEqualTo("Index -1 is out of bounds: [0, 2]");
-        IndexOutOfBoundsException e2 = assertThrows(IndexOutOfBoundsException.class,
-                () -> container.combineValues(out, new int[]{0, 1, 4, 0, 0}));
-        assertThat(e2.getMessage()).isEqualTo("Index 4 is out of bounds: [0, 2]");
-    }
 }
diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java
index 93d94c9..b4899f9 100644
--- a/graphics/java/android/graphics/ImageFormat.java
+++ b/graphics/java/android/graphics/ImageFormat.java
@@ -19,6 +19,8 @@
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 
+import com.android.internal.camera.flags.Flags;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
@@ -63,6 +65,7 @@
              RAW_DEPTH10,
              PRIVATE,
              HEIC,
+             HEIC_ULTRAHDR,
              JPEG_R
      })
      public @interface Format {
@@ -832,6 +835,16 @@
     public static final int HEIC = 0x48454946;
 
     /**
+     * High Efficiency Image File Format (HEIF) with embedded HDR gain map
+     *
+     * <p>This format defines the HEIC brand of High Efficiency Image File
+     * Format as described in ISO/IEC 23008-12:2024 with HDR gain map according
+     * to ISO/CD 21496‐1.</p>
+     */
+    @FlaggedApi(Flags.FLAG_CAMERA_HEIF_GAINMAP)
+    public static final int HEIC_ULTRAHDR = 0x1006;
+
+    /**
      * Use this function to retrieve the number of bits per pixel of an
      * ImageFormat.
      *
@@ -926,6 +939,11 @@
         if (android.media.codec.Flags.p210FormatSupport() && format == YCBCR_P210) {
             return true;
         }
+        if (Flags.cameraHeifGainmap()){
+            if (format == HEIC_ULTRAHDR) {
+                return true;
+            }
+        }
         return false;
     }
 }
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index b7a1c13..8bb32568 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -19,7 +19,7 @@
 import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
 import static com.android.text.flags.Flags.FLAG_LETTER_SPACING_JUSTIFICATION;
 import static com.android.text.flags.Flags.FLAG_DEPRECATE_ELEGANT_TEXT_HEIGHT_API;
-
+import static com.android.text.flags.Flags.FLAG_VERTICAL_TEXT_LAYOUT;
 
 import android.annotation.ColorInt;
 import android.annotation.ColorLong;
@@ -35,6 +35,7 @@
 import android.compat.annotation.EnabledSince;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.fonts.FontVariationAxis;
+import android.graphics.text.TextRunShaper;
 import android.os.Build;
 import android.os.LocaleList;
 import android.text.GraphicsOperations;
@@ -269,7 +270,24 @@
     public static final int EMBEDDED_BITMAP_TEXT_FLAG = 0x400;
     /** @hide bit mask for the flag forcing freetype's autohinter on for text */
     public static final int AUTO_HINTING_TEXT_FLAG = 0x800;
-    /** @hide bit mask for the flag enabling vertical rendering for text */
+
+    /**
+     * A flat that controls text to be written in vertical orientation
+     *
+     * <p>
+     * This flag is used for telling the underlying text layout engine that the text is for vertical
+     * direction. By enabling this flag, text measurement, drawing and shaping APIs works for
+     * vertical text layout. For example, {@link Canvas#drawText(String, float, float, Paint)} draws
+     * text from top to bottom. {@link Paint#measureText(String)} returns vertical advances instead
+     * of horizontal advances. {@link TextRunShaper} shapes text vertically and report glyph IDs for
+     * vertical layout.
+     *
+     * <p>
+     * Do not set this flag for making {@link android.text.Layout}. The {@link android.text.Layout}
+     * class and its subclasses are designed for horizontal text only and does not work for vertical
+     * text.
+     */
+    @FlaggedApi(FLAG_VERTICAL_TEXT_LAYOUT)
     public static final int VERTICAL_TEXT_FLAG = 0x1000;
 
     /**
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
index 4385327..4d7be39 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
@@ -16,6 +16,7 @@
 
 package androidx.window.extensions.area;
 
+import static android.hardware.devicestate.DeviceState.PROPERTY_EMULATED_ONLY;
 import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT;
 import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY;
 import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY;
@@ -569,7 +570,8 @@
     private boolean isDeviceFolded() {
         if (Flags.deviceStatePropertyApi()) {
             return mCurrentDeviceState.hasProperty(
-                    PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY);
+                    PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY)
+                    && !mCurrentDeviceState.hasProperty(PROPERTY_EMULATED_ONLY);
         } else {
             return ArrayUtils.contains(mFoldedDeviceStates, mCurrentDeviceState.getIdentifier());
         }
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
index 2fbf089..00d9a93 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
@@ -19,12 +19,15 @@
 import android.app.ActivityManager
 import android.content.Context
 import android.content.pm.LauncherApps
+import android.graphics.PointF
 import android.os.Handler
 import android.os.UserManager
 import android.view.IWindowManager
 import android.view.LayoutInflater
+import android.view.MotionEvent
 import android.view.View
 import android.view.WindowManager
+import androidx.core.animation.AnimatorTestRule
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -48,6 +51,7 @@
 import com.android.wm.shell.bubbles.Bubbles.SysuiProxy
 import com.android.wm.shell.bubbles.FakeBubbleFactory
 import com.android.wm.shell.bubbles.UiEventSubject.Companion.assertThat
+import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix
 import com.android.wm.shell.bubbles.properties.BubbleProperties
 import com.android.wm.shell.bubbles.storage.BubblePersistentRepository
 import com.android.wm.shell.common.DisplayController
@@ -57,6 +61,7 @@
 import com.android.wm.shell.common.SyncTransactionQueue
 import com.android.wm.shell.common.TaskStackListenerImpl
 import com.android.wm.shell.shared.TransactionPool
+import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils
 import com.android.wm.shell.shared.bubbles.BubbleBarLocation
 import com.android.wm.shell.sysui.ShellCommandHandler
 import com.android.wm.shell.sysui.ShellController
@@ -66,8 +71,10 @@
 import com.android.wm.shell.taskview.TaskViewTransitions
 import com.android.wm.shell.transition.Transitions
 import com.google.common.truth.Truth.assertThat
+import org.junit.After
 import java.util.Collections
 import org.junit.Before
+import org.junit.ClassRule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito.mock
@@ -79,18 +86,28 @@
 @RunWith(AndroidJUnit4::class)
 class BubbleBarLayerViewTest {
 
+    companion object {
+        @JvmField @ClassRule
+        val animatorTestRule: AnimatorTestRule = AnimatorTestRule()
+    }
+
     private val context = ApplicationProvider.getApplicationContext<Context>()
 
     private lateinit var bubbleBarLayerView: BubbleBarLayerView
 
     private lateinit var uiEventLoggerFake: UiEventLoggerFake
 
+    private lateinit var bubbleController: BubbleController
+
+    private lateinit var bubblePositioner: BubblePositioner
+
     private lateinit var bubble: Bubble
 
     @Before
     fun setUp() {
         ProtoLog.REQUIRE_PROTOLOGTOOL = false
         ProtoLog.init()
+        PhysicsAnimatorTestUtils.prepareForTest()
 
         uiEventLoggerFake = UiEventLoggerFake()
         val bubbleLogger = BubbleLogger(uiEventLoggerFake)
@@ -100,7 +117,7 @@
 
         val windowManager = context.getSystemService(WindowManager::class.java)
 
-        val bubblePositioner = BubblePositioner(context, windowManager)
+        bubblePositioner = BubblePositioner(context, windowManager)
         bubblePositioner.setShowingInBubbleBar(true)
 
         val bubbleData =
@@ -113,7 +130,7 @@
                 bgExecutor,
             )
 
-        val bubbleController =
+        bubbleController =
             createBubbleController(
                 bubbleData,
                 windowManager,
@@ -151,6 +168,12 @@
         bubble = FakeBubbleFactory.createChatBubbleWithViewInfo(context, viewInfo = viewInfo)
     }
 
+    @After
+    fun tearDown() {
+        PhysicsAnimatorTestUtils.tearDown()
+        getInstrumentation().waitForIdleSync()
+    }
+
     private fun createBubbleController(
         bubbleData: BubbleData,
         windowManager: WindowManager?,
@@ -224,6 +247,70 @@
         assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble)
     }
 
+    @Test
+    fun testEventLogging_dragExpandedViewLeft() {
+        bubblePositioner.bubbleBarLocation = BubbleBarLocation.RIGHT
+
+        getInstrumentation().runOnMainSync {
+            bubbleBarLayerView.showExpandedView(bubble)
+        }
+        waitForExpandedViewAnimation()
+
+        val handleView = bubbleBarLayerView.findViewById<View>(R.id.bubble_bar_handle_view)
+        assertThat(handleView).isNotNull()
+
+        // Drag from right to left
+        handleView.dispatchTouchEvent(0L, MotionEvent.ACTION_DOWN, rightEdge())
+        handleView.dispatchTouchEvent(10L, MotionEvent.ACTION_MOVE, leftEdge())
+        handleView.dispatchTouchEvent(20L, MotionEvent.ACTION_UP, leftEdge())
+
+        assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
+        assertThat(uiEventLoggerFake.logs[0].eventId)
+            .isEqualTo(BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_EXP_VIEW.id)
+        assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble)
+    }
+
+    @Test
+    fun testEventLogging_dragExpandedViewRight() {
+        bubblePositioner.bubbleBarLocation = BubbleBarLocation.LEFT
+
+        getInstrumentation().runOnMainSync {
+            bubbleBarLayerView.showExpandedView(bubble)
+        }
+        waitForExpandedViewAnimation()
+
+        val handleView = bubbleBarLayerView.findViewById<View>(R.id.bubble_bar_handle_view)
+        assertThat(handleView).isNotNull()
+
+        // Drag from left to right
+        handleView.dispatchTouchEvent(0L, MotionEvent.ACTION_DOWN, leftEdge())
+        handleView.dispatchTouchEvent(10L, MotionEvent.ACTION_MOVE, rightEdge())
+        handleView.dispatchTouchEvent(20L, MotionEvent.ACTION_UP, rightEdge())
+
+        assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
+        assertThat(uiEventLoggerFake.logs[0].eventId)
+            .isEqualTo(BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_EXP_VIEW.id)
+        assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble)
+    }
+
+    private fun leftEdge(): PointF {
+        val screenSize = bubblePositioner.availableRect
+        return PointF(screenSize.left.toFloat(), screenSize.height() / 2f)
+    }
+
+    private fun rightEdge(): PointF {
+        val screenSize = bubblePositioner.availableRect
+        return PointF(screenSize.right.toFloat(), screenSize.height() / 2f)
+    }
+
+    private fun waitForExpandedViewAnimation() {
+        // wait for idle to allow the animation to start
+        getInstrumentation().waitForIdleSync()
+        getInstrumentation().runOnMainSync { animatorTestRule.advanceTimeBy(200) }
+        PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(
+            AnimatableScaleMatrix.SCALE_X, AnimatableScaleMatrix.SCALE_Y)
+    }
+
     private inner class FakeBubbleTaskViewFactory(private val mainExecutor: ShellExecutor) :
         BubbleTaskViewFactory {
         override fun create(): BubbleTaskView {
@@ -290,4 +377,9 @@
             }
         }
     }
+
+    private fun View.dispatchTouchEvent(eventTime: Long, action: Int, point: PointF) {
+        val event = MotionEvent.obtain(0L, eventTime, action, point.x, point.y, 0)
+        getInstrumentation().runOnMainSync { dispatchTouchEvent(event) }
+    }
 }
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
index ecb2b25..d4cbe6e 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
@@ -74,6 +74,7 @@
     @Before
     fun setUp() {
         ProtoLog.REQUIRE_PROTOLOGTOOL = false
+        ProtoLog.init()
         container = FrameLayout(context)
         val windowManager = context.getSystemService(WindowManager::class.java)
         positioner = BubblePositioner(context, windowManager)
@@ -85,7 +86,7 @@
                 isSmallTablet = false,
                 isLandscape = true,
                 isRtl = false,
-                insets = Insets.of(10, 20, 30, 40)
+                insets = Insets.of(10, 20, 30, 40),
             )
         positioner.update(deviceConfig)
         positioner.bubbleBarTopOnScreen =
@@ -407,12 +408,26 @@
         assertThat(testListener.locationReleases).containsExactly(RIGHT)
     }
 
+    /** Send drag start event when on left */
+    @Test
+    fun start_onLeft_sendStartEventOnLeft() {
+        getInstrumentation().runOnMainSync { controller.onDragStart(initialLocationOnLeft = true) }
+        assertThat(testListener.locationStart).containsExactly(LEFT)
+    }
+
+    /** Send drag start event when on right */
+    @Test
+    fun start_onRight_sendStartEventOnRight() {
+        getInstrumentation().runOnMainSync { controller.onDragStart(initialLocationOnLeft = false) }
+        assertThat(testListener.locationStart).containsExactly(RIGHT)
+    }
+
     private fun getExpectedDropTargetBoundsOnLeft(): Rect =
         Rect().also {
             positioner.getBubbleBarExpandedViewBounds(
                 true /* onLeft */,
                 false /* isOverflowExpanded */,
-                it
+                it,
             )
         }
 
@@ -421,7 +436,7 @@
             positioner.getBubbleBarExpandedViewBounds(
                 false /* onLeft */,
                 false /* isOverflowExpanded */,
-                it
+                it,
             )
         }
 
@@ -446,8 +461,14 @@
     }
 
     internal class TestLocationChangeListener : BaseBubblePinController.LocationChangeListener {
+        val locationStart = mutableListOf<BubbleBarLocation>()
         val locationChanges = mutableListOf<BubbleBarLocation>()
         val locationReleases = mutableListOf<BubbleBarLocation>()
+
+        override fun onStart(location: BubbleBarLocation) {
+            locationStart.add(location)
+        }
+
         override fun onChange(location: BubbleBarLocation) {
             locationChanges.add(location)
         }
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.java
deleted file mode 100644
index 65e079e..0000000
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.java
+++ /dev/null
@@ -1,236 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.shared;
-
-import android.annotation.IntDef;
-import android.app.ActivityManager;
-import android.app.WindowConfiguration;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.wm.shell.shared.split.SplitBounds;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Simple container for recent tasks.  May contain either a single or pair of tasks.
- */
-public class GroupedRecentTaskInfo implements Parcelable {
-
-    public static final int TYPE_SINGLE = 1;
-    public static final int TYPE_SPLIT = 2;
-    public static final int TYPE_FREEFORM = 3;
-
-    @IntDef(prefix = {"TYPE_"}, value = {
-            TYPE_SINGLE,
-            TYPE_SPLIT,
-            TYPE_FREEFORM
-    })
-    public @interface GroupType {}
-
-    @NonNull
-    private final ActivityManager.RecentTaskInfo[] mTasks;
-    @Nullable
-    private final SplitBounds mSplitBounds;
-    @GroupType
-    private final int mType;
-    // TODO(b/348332802): move isMinimized inside each Task object instead once we have a
-    //  replacement for RecentTaskInfo
-    private final int[] mMinimizedTaskIds;
-
-    /**
-     * Create new for a single task
-     */
-    public static GroupedRecentTaskInfo forSingleTask(
-            @NonNull ActivityManager.RecentTaskInfo task) {
-        return new GroupedRecentTaskInfo(new ActivityManager.RecentTaskInfo[]{task}, null,
-                TYPE_SINGLE, null /* minimizedFreeformTasks */);
-    }
-
-    /**
-     * Create new for a pair of tasks in split screen
-     */
-    public static GroupedRecentTaskInfo forSplitTasks(@NonNull ActivityManager.RecentTaskInfo task1,
-            @NonNull ActivityManager.RecentTaskInfo task2, @Nullable SplitBounds splitBounds) {
-        return new GroupedRecentTaskInfo(new ActivityManager.RecentTaskInfo[]{task1, task2},
-                splitBounds, TYPE_SPLIT, null /* minimizedFreeformTasks */);
-    }
-
-    /**
-     * Create new for a group of freeform tasks
-     */
-    public static GroupedRecentTaskInfo forFreeformTasks(
-            @NonNull ActivityManager.RecentTaskInfo[] tasks,
-            @NonNull Set<Integer> minimizedFreeformTasks) {
-        return new GroupedRecentTaskInfo(
-                tasks,
-                null /* splitBounds */,
-                TYPE_FREEFORM,
-                minimizedFreeformTasks.stream().mapToInt(i -> i).toArray());
-    }
-
-    private GroupedRecentTaskInfo(
-            @NonNull ActivityManager.RecentTaskInfo[] tasks,
-            @Nullable SplitBounds splitBounds,
-            @GroupType int type,
-            @Nullable int[] minimizedFreeformTaskIds) {
-        mTasks = tasks;
-        mSplitBounds = splitBounds;
-        mType = type;
-        mMinimizedTaskIds = minimizedFreeformTaskIds;
-        ensureAllMinimizedIdsPresent(tasks, minimizedFreeformTaskIds);
-    }
-
-    private static void ensureAllMinimizedIdsPresent(
-            @NonNull ActivityManager.RecentTaskInfo[] tasks,
-            @Nullable int[] minimizedFreeformTaskIds) {
-        if (minimizedFreeformTaskIds == null) {
-            return;
-        }
-        if (!Arrays.stream(minimizedFreeformTaskIds).allMatch(
-                taskId -> Arrays.stream(tasks).anyMatch(task -> task.taskId == taskId))) {
-            throw new IllegalArgumentException("Minimized task IDs contain non-existent Task ID.");
-        }
-    }
-
-    GroupedRecentTaskInfo(Parcel parcel) {
-        mTasks = parcel.createTypedArray(ActivityManager.RecentTaskInfo.CREATOR);
-        mSplitBounds = parcel.readTypedObject(SplitBounds.CREATOR);
-        mType = parcel.readInt();
-        mMinimizedTaskIds = parcel.createIntArray();
-    }
-
-    /**
-     * Get primary {@link ActivityManager.RecentTaskInfo}
-     */
-    @NonNull
-    public ActivityManager.RecentTaskInfo getTaskInfo1() {
-        return mTasks[0];
-    }
-
-    /**
-     * Get secondary {@link ActivityManager.RecentTaskInfo}.
-     *
-     * Used in split screen.
-     */
-    @Nullable
-    public ActivityManager.RecentTaskInfo getTaskInfo2() {
-        if (mTasks.length > 1) {
-            return mTasks[1];
-        }
-        return null;
-    }
-
-    /**
-     * Get all {@link ActivityManager.RecentTaskInfo}s grouped together.
-     */
-    @NonNull
-    public List<ActivityManager.RecentTaskInfo> getTaskInfoList() {
-        return Arrays.asList(mTasks);
-    }
-
-    /**
-     * Return {@link SplitBounds} if this is a split screen entry or {@code null}
-     */
-    @Nullable
-    public SplitBounds getSplitBounds() {
-        return mSplitBounds;
-    }
-
-    /**
-     * Get type of this recents entry. One of {@link GroupType}
-     */
-    @GroupType
-    public int getType() {
-        return mType;
-    }
-
-    public int[] getMinimizedTaskIds() {
-        return mMinimizedTaskIds;
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder taskString = new StringBuilder();
-        for (int i = 0; i < mTasks.length; i++) {
-            if (i == 0) {
-                taskString.append("Task");
-            } else {
-                taskString.append(", Task");
-            }
-            taskString.append(i + 1).append(": ").append(getTaskInfo(mTasks[i]));
-        }
-        if (mSplitBounds != null) {
-            taskString.append(", SplitBounds: ").append(mSplitBounds);
-        }
-        taskString.append(", Type=");
-        switch (mType) {
-            case TYPE_SINGLE:
-                taskString.append("TYPE_SINGLE");
-                break;
-            case TYPE_SPLIT:
-                taskString.append("TYPE_SPLIT");
-                break;
-            case TYPE_FREEFORM:
-                taskString.append("TYPE_FREEFORM");
-                break;
-        }
-        taskString.append(", Minimized Task IDs: ");
-        taskString.append(Arrays.toString(mMinimizedTaskIds));
-        return taskString.toString();
-    }
-
-    private String getTaskInfo(ActivityManager.RecentTaskInfo taskInfo) {
-        if (taskInfo == null) {
-            return null;
-        }
-        return "id=" + taskInfo.taskId
-                + " baseIntent=" + (taskInfo.baseIntent != null
-                        ? taskInfo.baseIntent.getComponent()
-                        : "null")
-                + " winMode=" + WindowConfiguration.windowingModeToString(
-                        taskInfo.getWindowingMode());
-    }
-
-    @Override
-    public void writeToParcel(Parcel parcel, int flags) {
-        parcel.writeTypedArray(mTasks, flags);
-        parcel.writeTypedObject(mSplitBounds, flags);
-        parcel.writeInt(mType);
-        parcel.writeIntArray(mMinimizedTaskIds);
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    public static final @android.annotation.NonNull Creator<GroupedRecentTaskInfo> CREATOR =
-            new Creator<GroupedRecentTaskInfo>() {
-        public GroupedRecentTaskInfo createFromParcel(Parcel source) {
-            return new GroupedRecentTaskInfo(source);
-        }
-        public GroupedRecentTaskInfo[] newArray(int size) {
-            return new GroupedRecentTaskInfo[size];
-        }
-    };
-}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.aidl
similarity index 95%
rename from libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.aidl
index e21bf8f..93e635d 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.aidl
@@ -16,4 +16,4 @@
 
 package com.android.wm.shell.shared;
 
-parcelable GroupedRecentTaskInfo;
\ No newline at end of file
+parcelable GroupedTaskInfo;
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java
new file mode 100644
index 0000000..03e0ab0
--- /dev/null
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.shared;
+
+import android.annotation.IntDef;
+import android.app.ActivityManager.RecentTaskInfo;
+import android.app.TaskInfo;
+import android.app.WindowConfiguration;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.wm.shell.shared.split.SplitBounds;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Simple container for recent tasks which should be presented as a single task within the
+ * Overview UI.
+ */
+public class GroupedTaskInfo implements Parcelable {
+
+    public static final int TYPE_FULLSCREEN = 1;
+    public static final int TYPE_SPLIT = 2;
+    public static final int TYPE_FREEFORM = 3;
+
+    @IntDef(prefix = {"TYPE_"}, value = {
+            TYPE_FULLSCREEN,
+            TYPE_SPLIT,
+            TYPE_FREEFORM
+    })
+    public @interface GroupType {}
+
+    /**
+     * The type of this particular task info, can be one of TYPE_FULLSCREEN, TYPE_SPLIT or
+     * TYPE_FREEFORM.
+     */
+    @GroupType
+    protected final int mType;
+
+    /**
+     * The list of tasks associated with this single recent task info.
+     * TYPE_FULLSCREEN: Contains the stack of tasks associated with a single "task" in overview
+     * TYPE_SPLIT: Contains the two split roots of each side
+     * TYPE_FREEFORM: Contains the set of tasks currently in freeform mode
+     */
+    @NonNull
+    protected final List<TaskInfo> mTasks;
+
+    /**
+     * Only set for TYPE_SPLIT.
+     *
+     * Information about the split bounds.
+     */
+    @Nullable
+    protected final SplitBounds mSplitBounds;
+
+    /**
+     * Only set for TYPE_FREEFORM.
+     *
+     * TODO(b/348332802): move isMinimized inside each Task object instead once we have a
+     *  replacement for RecentTaskInfo
+     */
+    @Nullable
+    protected final int[] mMinimizedTaskIds;
+
+    /**
+     * Create new for a stack of fullscreen tasks
+     */
+    public static GroupedTaskInfo forFullscreenTasks(@NonNull TaskInfo task) {
+        return new GroupedTaskInfo(List.of(task), null, TYPE_FULLSCREEN,
+                null /* minimizedFreeformTasks */);
+    }
+
+    /**
+     * Create new for a pair of tasks in split screen
+     */
+    public static GroupedTaskInfo forSplitTasks(@NonNull TaskInfo task1,
+                    @NonNull TaskInfo task2, @Nullable SplitBounds splitBounds) {
+        return new GroupedTaskInfo(List.of(task1, task2), splitBounds, TYPE_SPLIT,
+                null /* minimizedFreeformTasks */);
+    }
+
+    /**
+     * Create new for a group of freeform tasks
+     */
+    public static GroupedTaskInfo forFreeformTasks(
+                    @NonNull List<TaskInfo> tasks,
+                    @NonNull Set<Integer> minimizedFreeformTasks) {
+        return new GroupedTaskInfo(tasks, null /* splitBounds */, TYPE_FREEFORM,
+                minimizedFreeformTasks.stream().mapToInt(i -> i).toArray());
+    }
+
+    private GroupedTaskInfo(
+            @NonNull List<TaskInfo> tasks,
+            @Nullable SplitBounds splitBounds,
+            @GroupType int type,
+            @Nullable int[] minimizedFreeformTaskIds) {
+        mTasks = tasks;
+        mSplitBounds = splitBounds;
+        mType = type;
+        mMinimizedTaskIds = minimizedFreeformTaskIds;
+        ensureAllMinimizedIdsPresent(tasks, minimizedFreeformTaskIds);
+    }
+
+    private void ensureAllMinimizedIdsPresent(
+            @NonNull List<TaskInfo> tasks,
+            @Nullable int[] minimizedFreeformTaskIds) {
+        if (minimizedFreeformTaskIds == null) {
+            return;
+        }
+        if (!Arrays.stream(minimizedFreeformTaskIds).allMatch(
+                taskId -> tasks.stream().anyMatch(task -> task.taskId == taskId))) {
+            throw new IllegalArgumentException("Minimized task IDs contain non-existent Task ID.");
+        }
+    }
+
+    protected GroupedTaskInfo(@NonNull Parcel parcel) {
+        mTasks = new ArrayList();
+        final int numTasks = parcel.readInt();
+        for (int i = 0; i < numTasks; i++) {
+            mTasks.add(new TaskInfo(parcel));
+        }
+        mSplitBounds = parcel.readTypedObject(SplitBounds.CREATOR);
+        mType = parcel.readInt();
+        mMinimizedTaskIds = parcel.createIntArray();
+    }
+
+    /**
+     * Get primary {@link RecentTaskInfo}
+     */
+    @NonNull
+    public TaskInfo getTaskInfo1() {
+        return mTasks.getFirst();
+    }
+
+    /**
+     * Get secondary {@link RecentTaskInfo}.
+     *
+     * Used in split screen.
+     */
+    @Nullable
+    public TaskInfo getTaskInfo2() {
+        if (mTasks.size() > 1) {
+            return mTasks.get(1);
+        }
+        return null;
+    }
+
+    /**
+     * Get all {@link RecentTaskInfo}s grouped together.
+     */
+    @NonNull
+    public List<TaskInfo> getTaskInfoList() {
+        return mTasks;
+    }
+
+    /**
+     * Return {@link SplitBounds} if this is a split screen entry or {@code null}
+     */
+    @Nullable
+    public SplitBounds getSplitBounds() {
+        return mSplitBounds;
+    }
+
+    /**
+     * Get type of this recents entry. One of {@link GroupType}
+     */
+    @GroupType
+    public int getType() {
+        return mType;
+    }
+
+    @Nullable
+    public int[] getMinimizedTaskIds() {
+        return mMinimizedTaskIds;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof GroupedTaskInfo)) {
+            return false;
+        }
+        GroupedTaskInfo other = (GroupedTaskInfo) obj;
+        return mType == other.mType
+                && Objects.equals(mTasks, other.mTasks)
+                && Objects.equals(mSplitBounds, other.mSplitBounds)
+                && Arrays.equals(mMinimizedTaskIds, other.mMinimizedTaskIds);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mType, mTasks, mSplitBounds, Arrays.hashCode(mMinimizedTaskIds));
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder taskString = new StringBuilder();
+        for (int i = 0; i < mTasks.size(); i++) {
+            if (i == 0) {
+                taskString.append("Task");
+            } else {
+                taskString.append(", Task");
+            }
+            taskString.append(i + 1).append(": ").append(getTaskInfo(mTasks.get(i)));
+        }
+        if (mSplitBounds != null) {
+            taskString.append(", SplitBounds: ").append(mSplitBounds);
+        }
+        taskString.append(", Type=");
+        switch (mType) {
+            case TYPE_FULLSCREEN:
+                taskString.append("TYPE_FULLSCREEN");
+                break;
+            case TYPE_SPLIT:
+                taskString.append("TYPE_SPLIT");
+                break;
+            case TYPE_FREEFORM:
+                taskString.append("TYPE_FREEFORM");
+                break;
+        }
+        taskString.append(", Minimized Task IDs: ");
+        taskString.append(Arrays.toString(mMinimizedTaskIds));
+        return taskString.toString();
+    }
+
+    private String getTaskInfo(TaskInfo taskInfo) {
+        if (taskInfo == null) {
+            return null;
+        }
+        return "id=" + taskInfo.taskId
+                + " baseIntent=" + (taskInfo.baseIntent != null
+                        ? taskInfo.baseIntent.getComponent()
+                        : "null")
+                + " winMode=" + WindowConfiguration.windowingModeToString(
+                        taskInfo.getWindowingMode());
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        // We don't use the parcel list methods because we want to only write the TaskInfo state
+        // and not the subclasses (Recents/RunningTaskInfo) whose fields are all deprecated
+        parcel.writeInt(mTasks.size());
+        for (int i = 0; i < mTasks.size(); i++) {
+            mTasks.get(i).writeTaskToParcel(parcel, flags);
+        }
+        parcel.writeTypedObject(mSplitBounds, flags);
+        parcel.writeInt(mType);
+        parcel.writeIntArray(mMinimizedTaskIds);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Creator<GroupedTaskInfo> CREATOR = new Creator() {
+        @Override
+        public GroupedTaskInfo createFromParcel(Parcel in) {
+            return new GroupedTaskInfo(in);
+        }
+
+        @Override
+        public GroupedTaskInfo[] newArray(int size) {
+            return new GroupedTaskInfo[size];
+        }
+    };
+}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
index 88878c6..e033f67 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
@@ -360,7 +360,7 @@
             windowConfiguration = new WindowConfiguration();
         }
 
-        Rect localBounds = new Rect();
+        Rect bounds = windowConfiguration.getBounds();
         RemoteAnimationTarget target = new RemoteAnimationTarget(
                 taskId,
                 newModeToLegacyMode(mode),
@@ -373,12 +373,12 @@
                 new Rect(0, 0, 0, 0),
                 order,
                 null,
-                localBounds,
-                new Rect(),
+                bounds,
+                bounds,
                 windowConfiguration,
                 isNotInRecents,
                 null,
-                new Rect(),
+                bounds,
                 taskInfo,
                 false,
                 INVALID_WINDOW_TYPE
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTestUtils.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTestUtils.kt
index fc3dc14..f93b35e 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTestUtils.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTestUtils.kt
@@ -20,7 +20,7 @@
 import android.util.ArrayMap
 import androidx.dynamicanimation.animation.FloatPropertyCompat
 import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils.prepareForTest
-import java.util.*
+import java.util.ArrayDeque
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.TimeUnit
 import kotlin.collections.ArrayList
@@ -74,14 +74,17 @@
 
     @JvmStatic
     fun tearDown() {
-        val latch = CountDownLatch(1)
-        animationThreadHandler.post {
+        if (Looper.myLooper() == animationThreadHandler.looper) {
             animatorTestHelpers.keys.forEach { it.cancel() }
-            latch.countDown()
+        } else {
+            val latch = CountDownLatch(1)
+            animationThreadHandler.post {
+                animatorTestHelpers.keys.forEach { it.cancel() }
+                latch.countDown()
+            }
+            latch.await(5, TimeUnit.SECONDS)
         }
 
-        latch.await()
-
         animatorTestHelpers.clear()
         animators.clear()
         allAnimatedObjects.clear()
@@ -348,8 +351,9 @@
      * Returns all of the values that have ever been reported to update listeners, per property.
      */
     @Suppress("UNCHECKED_CAST")
-    fun <T : Any> getAnimationUpdateFrames(animator: PhysicsAnimator<T>):
-            UpdateFramesPerProperty<T> {
+    fun <T : Any> getAnimationUpdateFrames(
+        animator: PhysicsAnimator<T>
+    ): UpdateFramesPerProperty<T> {
         return animatorTestHelpers[animator]?.getUpdates() as UpdateFramesPerProperty<T>
     }
 
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BaseBubblePinController.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BaseBubblePinController.kt
index 7086691..bd129a2 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BaseBubblePinController.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BaseBubblePinController.kt
@@ -56,6 +56,7 @@
         onLeft = initialLocationOnLeft
         screenCenterX = screenSizeProvider.invoke().x / 2
         dismissZone = getExclusionRect()
+        listener?.onStart(if (initialLocationOnLeft) LEFT else RIGHT)
     }
 
     /** View has moved to [x] and [y] screen coordinates */
@@ -109,6 +110,7 @@
 
     /** Get width for exclusion rect where dismiss takes over drag */
     protected abstract fun getExclusionRectWidth(): Float
+
     /** Get height for exclusion rect where dismiss takes over drag */
     protected abstract fun getExclusionRectHeight(): Float
 
@@ -184,6 +186,9 @@
 
     /** Receive updates on location changes */
     interface LocationChangeListener {
+        /** Bubble bar dragging has started. Includes the initial location of the bar */
+        fun onStart(location: BubbleBarLocation) {}
+
         /**
          * Bubble bar has been dragged to a new [BubbleBarLocation]. And the drag is still in
          * progress.
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/OWNERS b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/OWNERS
new file mode 100644
index 0000000..20d5c33
--- /dev/null
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/OWNERS
@@ -0,0 +1,4 @@
+# WM shell sub-module PiP owner
+hwwang@google.com
+gabiyev@google.com
+wuperry@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index f59fed9..dfe76b8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -487,6 +487,20 @@
         return mHomeTaskOverlayContainer;
     }
 
+    /**
+     * Returns the home task surface, not for wide use.
+     */
+    @Nullable
+    public SurfaceControl getHomeTaskSurface() {
+        for (int i = 0; i < mTasks.size(); i++) {
+            final TaskAppearedInfo info = mTasks.valueAt(i);
+            if (info.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME) {
+                return info.getLeash();
+            }
+        }
+        return null;
+    }
+
     @Override
     public void addStartingWindow(StartingWindowInfo info) {
         if (mStartingWindow != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 5f0eed9..14f8cc7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -1632,6 +1632,7 @@
         if (!isShowingAsBubbleBar()) {
             callback = b -> {
                 if (mStackView != null) {
+                    b.setSuppressFlyout(true);
                     mStackView.addBubble(b);
                     mStackView.setSelectedBubble(b);
                 } else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index 402818c..999ce17 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -124,18 +124,7 @@
 
         mBubbleExpandedViewPinController = new BubbleExpandedViewPinController(
                 context, this, mPositioner);
-        mBubbleExpandedViewPinController.setListener(
-                new BaseBubblePinController.LocationChangeListener() {
-                    @Override
-                    public void onChange(@NonNull BubbleBarLocation bubbleBarLocation) {
-                        mBubbleController.animateBubbleBarLocation(bubbleBarLocation);
-                    }
-
-                    @Override
-                    public void onRelease(@NonNull BubbleBarLocation location) {
-                        mBubbleController.setBubbleBarLocation(location);
-                    }
-                });
+        mBubbleExpandedViewPinController.setListener(new LocationChangeListener());
 
         setOnClickListener(view -> hideModalOrCollapse());
     }
@@ -238,11 +227,7 @@
             DragListener dragListener = inDismiss -> {
                 if (inDismiss && mExpandedBubble != null) {
                     mBubbleController.dismissBubble(mExpandedBubble.getKey(), DISMISS_USER_GESTURE);
-                    if (mExpandedBubble instanceof Bubble) {
-                        // Only a bubble can be dragged to dismiss
-                        mBubbleLogger.log((Bubble) mExpandedBubble,
-                                BubbleLogger.Event.BUBBLE_BAR_BUBBLE_DISMISSED_DRAG_EXP_VIEW);
-                    }
+                    logBubbleEvent(BubbleLogger.Event.BUBBLE_BAR_BUBBLE_DISMISSED_DRAG_EXP_VIEW);
                 }
             };
             mDragController = new BubbleBarExpandedViewDragController(
@@ -423,10 +408,47 @@
         }
     }
 
+    /**
+     * Log the event only if {@link #mExpandedBubble} is a {@link Bubble}.
+     * <p>
+     * Skips logging if it is {@link BubbleOverflow}.
+     */
+    private void logBubbleEvent(BubbleLogger.Event event) {
+        if (mExpandedBubble != null && mExpandedBubble instanceof Bubble bubble) {
+            mBubbleLogger.log(bubble, event);
+        }
+    }
+
     @Nullable
     @VisibleForTesting
     public BubbleBarExpandedViewDragController getDragController() {
         return mDragController;
     }
 
+    private class LocationChangeListener implements
+            BaseBubblePinController.LocationChangeListener {
+
+        private BubbleBarLocation mInitialLocation;
+
+        @Override
+        public void onStart(@NonNull BubbleBarLocation location) {
+            mInitialLocation = location;
+        }
+
+        @Override
+        public void onChange(@NonNull BubbleBarLocation bubbleBarLocation) {
+            mBubbleController.animateBubbleBarLocation(bubbleBarLocation);
+        }
+
+        @Override
+        public void onRelease(@NonNull BubbleBarLocation location) {
+            mBubbleController.setBubbleBarLocation(location);
+            if (location != mInitialLocation) {
+                BubbleLogger.Event event = location.isOnLeft(isLayoutRtl())
+                        ? BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_EXP_VIEW
+                        : BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_EXP_VIEW;
+                logBubbleEvent(event);
+            }
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
index b83b5f3..8ef20d1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
@@ -44,7 +44,8 @@
     private const val TAG = "PipUtils"
 
     // Minimum difference between two floats (e.g. aspect ratios) to consider them not equal.
-    private const val EPSILON = 1e-7
+    // TODO b/377530560: Restore epsilon once a long term fix is merged for non-config-at-end issue.
+    private const val EPSILON = 0.05f
 
     /**
      * @return the ComponentName and user id of the top non-SystemUI activity in the pinned stack.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
index d1b2347..62d5098 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
@@ -23,9 +23,15 @@
 import com.android.internal.R
 
 // TODO(b/347289970): Consider replacing with API
+/**
+ * If the top activity should be exempt from desktop windowing and forced back to fullscreen.
+ * Currently includes all system ui activities and modal dialogs. However is the top activity is not
+ * being displayed, regardless of its configuration, we will not exempt it as to remain in the
+ * desktop windowing environment.
+ */
 fun isTopActivityExemptFromDesktopWindowing(context: Context, task: TaskInfo) =
-    isSystemUiTask(context, task) || (task.isTopActivityTransparent && task.numActivities == 1
-            && !task.isTopActivityStyleFloating)
+    (isSystemUiTask(context, task) || (task.isTopActivityTransparent && task.numActivities == 1))
+            && !task.isTopActivityNoDisplay
 
 private fun isSystemUiTask(context: Context, task: TaskInfo): Boolean {
     val sysUiPackageName: String =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index 886330f..0200e18 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -286,7 +286,7 @@
                 // we need to ignore all the incoming TaskInfo until the education
                 // completes. If we come from a double tap we follow the normal flow.
                 final boolean topActivityPillarboxed =
-                        taskInfo.appCompatTaskInfo.isTopActivityPillarboxed();
+                        taskInfo.appCompatTaskInfo.isTopActivityPillarboxShaped();
                 final boolean isFirstTimeHorizontalReachabilityEdu = topActivityPillarboxed
                         && !mCompatUIConfiguration.hasSeenHorizontalReachabilityEducation(taskInfo);
                 final boolean isFirstTimeVerticalReachabilityEdu = !topActivityPillarboxed
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 2a5a519..77e041e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -401,9 +401,6 @@
             ShellInit shellInit,
             ShellCommandHandler shellCommandHandler,
             RootTaskDisplayAreaOrganizer rootTdaOrganizer) {
-        if (!com.android.window.flags.Flags.explicitRefreshRateHints()) {
-            return Optional.empty();
-        }
         final PerfHintController perfHintController =
                 new PerfHintController(context, shellInit, shellCommandHandler, rootTdaOrganizer);
         return Optional.of(perfHintController.getHinter());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index fec4c16..a472f79 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -67,6 +67,7 @@
 import com.android.wm.shell.desktopmode.CloseDesktopTaskTransitionHandler;
 import com.android.wm.shell.desktopmode.DefaultDragToDesktopTransitionHandler;
 import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler;
+import com.android.wm.shell.desktopmode.DesktopDisplayEventHandler;
 import com.android.wm.shell.desktopmode.DesktopImmersiveController;
 import com.android.wm.shell.desktopmode.DesktopMixedTransitionHandler;
 import com.android.wm.shell.desktopmode.DesktopModeDragAndDropTransitionHandler;
@@ -778,7 +779,8 @@
             ShellTaskOrganizer shellTaskOrganizer,
             ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
             ReturnToDragStartAnimator returnToDragStartAnimator,
-            @DynamicOverride DesktopRepository desktopRepository) {
+            @DynamicOverride DesktopRepository desktopRepository,
+            DesktopModeEventLogger desktopModeEventLogger) {
         return new DesktopTilingDecorViewModel(
                 context,
                 displayController,
@@ -788,7 +790,8 @@
                 shellTaskOrganizer,
                 toggleResizeDesktopTaskTransitionHandler,
                 returnToDragStartAnimator,
-                desktopRepository
+                desktopRepository,
+                desktopModeEventLogger
         );
     }
 
@@ -1016,6 +1019,30 @@
 
     @WMSingleton
     @Provides
+    static Optional<DesktopDisplayEventHandler> provideDesktopDisplayEventHandler(
+            Context context,
+            ShellInit shellInit,
+            Transitions transitions,
+            DisplayController displayController,
+            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+            IWindowManager windowManager
+    ) {
+        if (!DesktopModeStatus.canEnterDesktopMode(context)
+                || !Flags.enableDisplayWindowingModeSwitching()) {
+            return Optional.empty();
+        }
+        return Optional.of(
+                new DesktopDisplayEventHandler(
+                        context,
+                        shellInit,
+                        transitions,
+                        displayController,
+                        rootTaskDisplayAreaOrganizer,
+                        windowManager));
+    }
+
+    @WMSingleton
+    @Provides
     static AppHandleEducationDatastoreRepository provideAppHandleEducationDatastoreRepository(
             Context context) {
         return new AppHandleEducationDatastoreRepository(context);
@@ -1180,7 +1207,8 @@
     @Provides
     static Object provideIndependentShellComponentsToCreate(
             DragAndDropController dragAndDropController,
-            Optional<DesktopTasksTransitionObserver> desktopTasksTransitionObserverOptional) {
+            Optional<DesktopTasksTransitionObserver> desktopTasksTransitionObserverOptional,
+            Optional<DesktopDisplayEventHandler> desktopDisplayEventHandler) {
         return new Object();
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt
new file mode 100644
index 0000000..ba383fa
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+package com.android.wm.shell.desktopmode
+
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.content.Context
+import android.provider.Settings
+import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
+import android.view.Display.DEFAULT_DISPLAY
+import android.view.IWindowManager
+import android.view.WindowManager.TRANSIT_CHANGE
+import android.window.WindowContainerTransaction
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.transition.Transitions
+
+/** Handles display events in desktop mode */
+class DesktopDisplayEventHandler(
+    private val context: Context,
+    shellInit: ShellInit,
+    private val transitions: Transitions,
+    private val displayController: DisplayController,
+    private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+    private val windowManager: IWindowManager,
+) : OnDisplaysChangedListener {
+
+    init {
+        shellInit.addInitCallback({ onInit() }, this)
+    }
+
+    private fun onInit() {
+        displayController.addDisplayWindowListener(this)
+    }
+
+    override fun onDisplayAdded(displayId: Int) {
+        if (displayId == DEFAULT_DISPLAY) {
+            return
+        }
+        refreshDisplayWindowingMode()
+    }
+
+    override fun onDisplayRemoved(displayId: Int) {
+        if (displayId == DEFAULT_DISPLAY) {
+            return
+        }
+        refreshDisplayWindowingMode()
+    }
+
+    private fun refreshDisplayWindowingMode() {
+        // TODO: b/375319538 - Replace the check with a DisplayManager API once it's available.
+        val isExtendedDisplayEnabled = 0 != Settings.Global.getInt(
+            context.contentResolver, DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 0
+        )
+        if (!isExtendedDisplayEnabled) {
+            // No action needed in mirror or projected mode.
+            return
+        }
+
+        val hasNonDefaultDisplay = rootTaskDisplayAreaOrganizer.getDisplayIds()
+            .any { displayId -> displayId != DEFAULT_DISPLAY }
+        val targetDisplayWindowingMode =
+            if (hasNonDefaultDisplay) {
+                WINDOWING_MODE_FREEFORM
+            } else {
+                // Use the default display windowing mode when no non-default display.
+                windowManager.getWindowingMode(DEFAULT_DISPLAY)
+            }
+        val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)
+        requireNotNull(tdaInfo) { "DisplayAreaInfo of DEFAULT_DISPLAY must be non-null." }
+        if (tdaInfo.configuration.windowConfiguration.windowingMode == targetDisplayWindowingMode) {
+            // Already in the target mode.
+            return
+        }
+
+        val wct = WindowContainerTransaction()
+        wct.setWindowingMode(tdaInfo.token, targetDisplayWindowingMode)
+        transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null)
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
index 48bb2a8..cefcb75 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
@@ -205,6 +205,11 @@
         finishTransaction: SurfaceControl.Transaction,
         finishCallback: TransitionFinishCallback,
     ): Boolean {
+        val launchChange = findDesktopTaskChange(info, pending.launchingTask)
+        if (launchChange == null) {
+            logV("No launch Change, returning")
+            return false
+        }
         // Check if there's also an immersive change during this launch.
         val immersiveExitChange = pending.exitingImmersiveTask?.let { exitingTask ->
             findDesktopTaskChange(info, exitingTask)
@@ -212,8 +217,6 @@
         val minimizeChange = pending.minimizingTask?.let { minimizingTask ->
             findDesktopTaskChange(info, minimizingTask)
         }
-        val launchChange = findDesktopTaskChange(info, pending.launchingTask)
-            ?: error("Should have pending launching task change")
 
         var subAnimationCount = -1
         var combinedWct: WindowContainerTransaction? = null
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
index edcc877..c7cf310 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
@@ -275,7 +275,7 @@
         }
 
         // Then check if the activity is portrait when letterboxed
-        appCompatTaskInfo.isTopActivityLetterboxed -> appCompatTaskInfo.isTopActivityPillarboxed
+        appCompatTaskInfo.isTopActivityLetterboxed -> appCompatTaskInfo.isTopActivityPillarboxShaped
 
         // Then check if the activity is portrait
         appBounds != null -> appBounds.height() > appBounds.width()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 75f8839..162879c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -554,19 +554,6 @@
         }
     }
 
-    /** Move a desktop app to split screen. */
-    fun moveToSplit(task: RunningTaskInfo) {
-        logV( "moveToSplit taskId=%s", task.taskId)
-        desktopTilingDecorViewModel.removeTaskIfTiled(task.displayId, task.taskId)
-        val wct = WindowContainerTransaction()
-        wct.setBounds(task.token, Rect())
-        // Rather than set windowing mode to multi-window at task level, set it to
-        // undefined and inherit from split stage.
-        wct.setWindowingMode(task.token, WINDOWING_MODE_UNDEFINED)
-
-        transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
-    }
-
     private fun exitSplitIfApplicable(wct: WindowContainerTransaction, taskInfo: RunningTaskInfo) {
         if (splitScreenController.isTaskInSplitScreen(taskInfo.taskId)) {
             splitScreenController.prepareExitSplitScreen(
@@ -2050,6 +2037,18 @@
     }
 
     /**
+     * Cancel the drag-to-desktop transition.
+     *
+     * @param taskInfo the task being dragged.
+     */
+    fun onDragPositioningCancelThroughStatusBar(
+        taskInfo: RunningTaskInfo,
+    ) {
+        interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD)
+        cancelDragToDesktop(taskInfo)
+    }
+
+    /**
      * Perform checks required when drag ends under status bar area.
      *
      * @param taskInfo the task being dragged.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
index 96719fa..9411150 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
@@ -21,6 +21,7 @@
 import android.animation.ValueAnimator
 import android.graphics.Rect
 import android.os.IBinder
+import android.view.Choreographer
 import android.view.SurfaceControl
 import android.view.WindowManager.TRANSIT_CHANGE
 import android.window.TransitionInfo
@@ -126,6 +127,7 @@
                         tx.setPosition(leash, rect.left.toFloat(), rect.top.toFloat())
                             .setWindowCrop(leash, rect.width(), rect.height())
                             .show(leash)
+                            .setFrameTimeline(Choreographer.getInstance().getVsyncId())
                         onTaskResizeAnimationListener.onBoundsChange(taskId, tx, rect)
                     }
                     start()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
index f8d2011..b618bf1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
@@ -53,6 +53,7 @@
 import android.window.WindowContainerTransaction;
 
 import com.android.internal.protolog.ProtoLog;
+import com.android.window.flags.Flags;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TaskStackListenerCallback;
 import com.android.wm.shell.common.TaskStackListenerImpl;
@@ -72,6 +73,9 @@
 public class KeyguardTransitionHandler
         implements Transitions.TransitionHandler, KeyguardChangeListener,
         TaskStackListenerCallback {
+    private static final boolean ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS =
+            Flags.ensureKeyguardDoesTransitionStarting();
+
     private static final String TAG = "KeyguardTransition";
 
     private final Transitions mTransitions;
@@ -194,7 +198,7 @@
 
         // Occlude/unocclude animations are only played if the keyguard is locked.
         if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) {
-            if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_OCCLUDING) != 0) {
+            if (isKeyguardOccluding(info)) {
                 if (hasOpeningDream(info)) {
                     return startAnimation(mOccludeByDreamTransition, "occlude-by-dream",
                             transition, info, startTransaction, finishTransaction, finishCallback);
@@ -202,7 +206,7 @@
                     return startAnimation(mOccludeTransition, "occlude",
                             transition, info, startTransaction, finishTransaction, finishCallback);
                 }
-            } else if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_UNOCCLUDING) != 0) {
+            } else if (isKeyguardUnoccluding(info)) {
                 return startAnimation(mUnoccludeTransition, "unocclude",
                         transition, info, startTransaction, finishTransaction, finishCallback);
             }
@@ -325,6 +329,36 @@
         return false;
     }
 
+    private static boolean isKeyguardOccluding(@NonNull TransitionInfo info) {
+        if (!ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) {
+            return (info.getFlags() & TRANSIT_FLAG_KEYGUARD_OCCLUDING) != 0;
+        }
+
+        for (int i = 0; i < info.getChanges().size(); i++) {
+            TransitionInfo.Change change = info.getChanges().get(i);
+            if (change.hasFlags(TransitionInfo.FLAG_IS_TASK_DISPLAY_AREA)
+                    && change.getMode() == TRANSIT_TO_FRONT) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static boolean isKeyguardUnoccluding(@NonNull TransitionInfo info) {
+        if (!ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) {
+            return (info.getFlags() & TRANSIT_FLAG_KEYGUARD_UNOCCLUDING) != 0;
+        }
+
+        for (int i = 0; i < info.getChanges().size(); i++) {
+            TransitionInfo.Change change = info.getChanges().get(i);
+            if (change.hasFlags(TransitionInfo.FLAG_IS_TASK_DISPLAY_AREA)
+                    && change.getMode() == TRANSIT_TO_BACK) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private void finishAnimationImmediately(IBinder transition, StartedTransition playing) {
         final IBinder fakeTransition = new Binder();
         final TransitionInfo fakeInfo = new TransitionInfo(TRANSIT_SLEEP, 0x0);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java
index 24077a3..0264820 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java
@@ -24,7 +24,6 @@
 import android.view.SurfaceControl;
 
 import com.android.wm.shell.R;
-import com.android.wm.shell.transition.Transitions;
 
 /**
  * Abstracts the common operations on {@link SurfaceControl.Transaction} for PiP transition.
@@ -180,8 +179,7 @@
         // destination are different.
         final float scale = srcW <= srcH ? (float) destW / srcW : (float) destH / srcH;
         final Rect crop = mTmpDestinationRect;
-        crop.set(0, 0, Transitions.SHELL_TRANSITIONS_ROTATION ? destH
-                : destW, Transitions.SHELL_TRANSITIONS_ROTATION ? destW : destH);
+        crop.set(0, 0, destW, destH);
         // Inverse scale for crop to fit in screen coordinates.
         crop.scale(1 / scale);
         crop.offset(insets.left, insets.top);
@@ -200,8 +198,8 @@
             }
         }
         mTmpTransform.setScale(scale, scale);
-        mTmpTransform.postRotate(degrees);
         mTmpTransform.postTranslate(positionX, positionY);
+        mTmpTransform.postRotate(degrees);
         tx.setMatrix(leash, mTmpTransform, mTmpFloat9).setCrop(leash, crop);
         return this;
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java
index e513758..eb33ff4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java
@@ -20,6 +20,7 @@
 import static android.view.Surface.ROTATION_90;
 
 import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.RectEvaluator;
 import android.animation.ValueAnimator;
 import android.content.Context;
@@ -34,6 +35,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.R;
 import com.android.wm.shell.common.pip.PipUtils;
@@ -45,8 +47,7 @@
 /**
  * Animator that handles bounds animations for entering PIP.
  */
-public class PipEnterAnimator extends ValueAnimator
-        implements ValueAnimator.AnimatorUpdateListener, ValueAnimator.AnimatorListener {
+public class PipEnterAnimator extends ValueAnimator {
     @NonNull private final SurfaceControl mLeash;
     private final SurfaceControl.Transaction mStartTransaction;
     private final SurfaceControl.Transaction mFinishTransaction;
@@ -56,49 +57,82 @@
 
     private final RectEvaluator mRectEvaluator;
     private final Rect mEndBounds = new Rect();
-    @Nullable private final Rect mSourceRectHint;
     private final @Surface.Rotation int mRotation;
     @Nullable private Runnable mAnimationStartCallback;
     @Nullable private Runnable mAnimationEndCallback;
 
-    private final PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
+    private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
             mSurfaceControlTransactionFactory;
     Matrix mTransformTensor = new Matrix();
     final float[] mMatrixTmp = new float[9];
     @Nullable private PipContentOverlay mContentOverlay;
 
+    private PipAppIconOverlaySupplier mPipAppIconOverlaySupplier;
 
     // Internal state representing initial transform - cached to avoid recalculation.
     private final PointF mInitScale = new PointF();
     private final PointF mInitPos = new PointF();
     private final Rect mInitCrop = new Rect();
-    private final PointF mInitActivityScale = new PointF();
-    private final PointF mInitActivityPos = new PointF();
+
+    private final Animator.AnimatorListener mAnimatorListener = new AnimatorListenerAdapter() {
+        @Override
+        public void onAnimationStart(Animator animation) {
+            super.onAnimationStart(animation);
+            if (mAnimationStartCallback != null) {
+                mAnimationStartCallback.run();
+            }
+            if (mStartTransaction != null) {
+                onEnterAnimationUpdate(0f /* fraction */, mStartTransaction);
+                mStartTransaction.apply();
+            }
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            super.onAnimationEnd(animation);
+            if (mFinishTransaction != null) {
+                onEnterAnimationUpdate(1f /* fraction */, mFinishTransaction);
+            }
+            if (mAnimationEndCallback != null) {
+                mAnimationEndCallback.run();
+            }
+        }
+    };
+
+    private final AnimatorUpdateListener mAnimatorUpdateListener = new AnimatorUpdateListener() {
+        @Override
+        public void onAnimationUpdate(@NonNull ValueAnimator animation) {
+            final SurfaceControl.Transaction tx =
+                    mSurfaceControlTransactionFactory.getTransaction();
+            final float fraction = getAnimatedFraction();
+            onEnterAnimationUpdate(fraction, tx);
+            tx.apply();
+        }
+    };
 
     public PipEnterAnimator(Context context,
             @NonNull SurfaceControl leash,
             SurfaceControl.Transaction startTransaction,
             SurfaceControl.Transaction finishTransaction,
             @NonNull Rect endBounds,
-            @Nullable Rect sourceRectHint,
             @Surface.Rotation int rotation) {
         mLeash = leash;
         mStartTransaction = startTransaction;
         mFinishTransaction = finishTransaction;
         mRectEvaluator = new RectEvaluator(mAnimatedRect);
         mEndBounds.set(endBounds);
-        mSourceRectHint = sourceRectHint != null ? new Rect(sourceRectHint) : null;
         mRotation = rotation;
         mSurfaceControlTransactionFactory =
                 new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
+        mPipAppIconOverlaySupplier = this::getAppIconOverlay;
 
         final int enterAnimationDuration = context.getResources()
                 .getInteger(R.integer.config_pipEnterAnimationDuration);
         setDuration(enterAnimationDuration);
         setFloatValues(0f, 1f);
         setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
-        addListener(this);
-        addUpdateListener(this);
+        addListener(mAnimatorListener);
+        addUpdateListener(mAnimatorUpdateListener);
     }
 
     public void setAnimationStartCallback(@NonNull Runnable runnable) {
@@ -109,35 +143,6 @@
         mAnimationEndCallback = runnable;
     }
 
-    @Override
-    public void onAnimationStart(@NonNull Animator animation) {
-        if (mAnimationStartCallback != null) {
-            mAnimationStartCallback.run();
-        }
-        if (mStartTransaction != null) {
-            onEnterAnimationUpdate(0f /* fraction */, mStartTransaction);
-            mStartTransaction.apply();
-        }
-    }
-
-    @Override
-    public void onAnimationEnd(@NonNull Animator animation) {
-        if (mFinishTransaction != null) {
-            onEnterAnimationUpdate(1f /* fraction */, mFinishTransaction);
-        }
-        if (mAnimationEndCallback != null) {
-            mAnimationEndCallback.run();
-        }
-    }
-
-    @Override
-    public void onAnimationUpdate(@NonNull ValueAnimator animation) {
-        final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
-        final float fraction = getAnimatedFraction();
-        onEnterAnimationUpdate(fraction, tx);
-        tx.apply();
-    }
-
     /**
      * Updates the transaction to reflect the state of PiP leash at a certain fraction during enter.
      *
@@ -177,14 +182,6 @@
         }
     }
 
-    // no-ops
-
-    @Override
-    public void onAnimationCancel(@NonNull Animator animation) {}
-
-    @Override
-    public void onAnimationRepeat(@NonNull Animator animation) {}
-
     /**
      * Caches the initial transform relevant values for the bounds enter animation.
      *
@@ -201,18 +198,13 @@
      */
     public void setAppIconContentOverlay(Context context, Rect appBounds, Rect destinationBounds,
             ActivityInfo activityInfo, int appIconSizePx) {
-        reattachAppIconOverlay(
-                new PipAppIconOverlay(context, appBounds, destinationBounds,
-                        new IconProvider(context).getIcon(activityInfo), appIconSizePx));
-    }
-
-    private void reattachAppIconOverlay(PipAppIconOverlay overlay) {
         final SurfaceControl.Transaction tx =
                 mSurfaceControlTransactionFactory.getTransaction();
         if (mContentOverlay != null) {
             mContentOverlay.detach(tx);
         }
-        mContentOverlay = overlay;
+        mContentOverlay = mPipAppIconOverlaySupplier.get(context, appBounds, destinationBounds,
+                activityInfo, appIconSizePx);
         mContentOverlay.attach(tx, mLeash);
     }
 
@@ -229,6 +221,13 @@
         mContentOverlay = null;
     }
 
+    private PipAppIconOverlay getAppIconOverlay(
+            Context context, Rect appBounds, Rect destinationBounds,
+            ActivityInfo activityInfo, int iconSize) {
+        return new PipAppIconOverlay(context, appBounds, destinationBounds,
+                new IconProvider(context).getIcon(activityInfo), iconSize);
+    }
+
     /**
      * @return the app icon overlay leash; null if no overlay is attached.
      */
@@ -239,4 +238,21 @@
         }
         return mContentOverlay.getLeash();
     }
+
+    @VisibleForTesting
+    void setSurfaceControlTransactionFactory(
+            @NonNull PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) {
+        mSurfaceControlTransactionFactory = factory;
+    }
+
+    @VisibleForTesting
+    interface PipAppIconOverlaySupplier {
+        PipAppIconOverlay get(Context context, Rect appBounds, Rect destinationBounds,
+                ActivityInfo activityInfo, int iconSize);
+    }
+
+    @VisibleForTesting
+    void setPipAppIconOverlaySupplier(@NonNull PipAppIconOverlaySupplier supplier) {
+        mPipAppIconOverlaySupplier = supplier;
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java
index 3f9b0c3..fb1aba3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java
@@ -16,6 +16,8 @@
 
 package com.android.wm.shell.pip2.animation;
 
+import static android.view.Surface.ROTATION_90;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.RectEvaluator;
@@ -73,6 +75,7 @@
                 mAnimationStartCallback.run();
             }
             if (mStartTransaction != null) {
+                onExpandAnimationUpdate(mStartTransaction, 0f);
                 mStartTransaction.apply();
             }
         }
@@ -81,13 +84,7 @@
         public void onAnimationEnd(Animator animation) {
             super.onAnimationEnd(animation);
             if (mFinishTransaction != null) {
-                // finishTransaction might override some state (eg. corner radii) so we want to
-                // manually set the state to the end of the animation
-                mPipSurfaceTransactionHelper.scaleAndCrop(mFinishTransaction, mLeash,
-                                mSourceRectHint, mBaseBounds, mAnimatedRect, getInsets(1f),
-                                false /* isInPipDirection */, 1f)
-                        .round(mFinishTransaction, mLeash, false /* applyCornerRadius */)
-                        .shadow(mFinishTransaction, mLeash, false /* applyCornerRadius */);
+                onExpandAnimationUpdate(mFinishTransaction, 1f);
             }
             if (mAnimationEndCallback != null) {
                 mAnimationEndCallback.run();
@@ -102,14 +99,7 @@
                     final SurfaceControl.Transaction tx =
                             mSurfaceControlTransactionFactory.getTransaction();
                     final float fraction = getAnimatedFraction();
-
-                    // TODO (b/350801661): implement fixed rotation
-                    Rect insets = getInsets(fraction);
-                    mPipSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, mSourceRectHint,
-                                    mBaseBounds, mAnimatedRect,
-                                    insets, false /* isInPipDirection */, fraction)
-                            .round(tx, mLeash, false /* applyCornerRadius */)
-                            .shadow(tx, mLeash, false /* applyCornerRadius */);
+                    onExpandAnimationUpdate(tx, fraction);
                     tx.apply();
                 }
             };
@@ -167,6 +157,32 @@
         mAnimationEndCallback = runnable;
     }
 
+    private void onExpandAnimationUpdate(SurfaceControl.Transaction tx, float fraction) {
+        Rect insets = getInsets(fraction);
+        if (mRotation == Surface.ROTATION_0) {
+            mPipSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, mSourceRectHint, mBaseBounds,
+                    mAnimatedRect, insets, false /* isInPipDirection */, fraction);
+        } else {
+            // Fixed rotation case.
+            Rect start = mStartBounds;
+            Rect end = mEndBounds;
+            float degrees, x, y;
+            x = fraction * (end.left - start.left) + start.left;
+            y = fraction * (end.top - start.top) + start.top;
+
+            if (mRotation == ROTATION_90) {
+                degrees = 90 * fraction;
+            } else {
+                degrees = -90 * fraction;
+            }
+            mPipSurfaceTransactionHelper.rotateAndScaleWithCrop(tx, mLeash, mBaseBounds,
+                    mAnimatedRect, insets, degrees, x, y,
+                    true /* isExpanding */, mRotation == ROTATION_90);
+        }
+        mPipSurfaceTransactionHelper.round(tx, mLeash, false /* applyCornerRadius */)
+                .shadow(tx, mLeash, false /* applyShadowRadius */);
+    }
+
     private Rect getInsets(float fraction) {
         final Rect startInsets = mSourceRectHintInsets;
         final Rect endInsets = mZeroInsets;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java
index 012dabb..4558a9f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java
@@ -30,6 +30,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
+import com.android.wm.shell.shared.animation.Interpolators;
 
 /**
  * Animator that handles any resize related animation for PIP.
@@ -128,6 +129,7 @@
         mRectEvaluator = new RectEvaluator(mAnimatedRect);
 
         setObjectValues(startBounds, endBounds);
+        setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
         addListener(mAnimatorListener);
         addUpdateListener(mAnimatorUpdateListener);
         setEvaluator(mRectEvaluator);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java
index 58d2a85..44900ce 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java
@@ -573,7 +573,7 @@
             @PipTransitionState.TransitionState int newState, Bundle extra) {
         switch (newState) {
             case PipTransitionState.ENTERED_PIP:
-                attach(mPipTransitionState.mPinnedTaskLeash);
+                attach(mPipTransitionState.getPinnedTaskLeash());
                 break;
             case PipTransitionState.EXITED_PIP:
                 detach();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index 9a93371..d3f537b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -376,18 +376,20 @@
     private void setLauncherKeepClearAreaHeight(boolean visible, int height) {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "setLauncherKeepClearAreaHeight: visible=%b, height=%d", visible, height);
-        if (visible) {
-            Rect rect = new Rect(
-                    0, mPipDisplayLayoutState.getDisplayBounds().bottom - height,
-                    mPipDisplayLayoutState.getDisplayBounds().right,
-                    mPipDisplayLayoutState.getDisplayBounds().bottom);
-            mPipBoundsState.setNamedUnrestrictedKeepClearArea(
-                    PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, rect);
-        } else {
-            mPipBoundsState.setNamedUnrestrictedKeepClearArea(
-                    PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, null);
-        }
-        mPipTouchHandler.onShelfVisibilityChanged(visible, height);
+        mPipTransitionState.setOnIdlePipTransitionStateRunnable(() -> {
+            if (visible) {
+                Rect rect = new Rect(
+                        0, mPipDisplayLayoutState.getDisplayBounds().bottom - height,
+                        mPipDisplayLayoutState.getDisplayBounds().right,
+                        mPipDisplayLayoutState.getDisplayBounds().bottom);
+                mPipBoundsState.setNamedUnrestrictedKeepClearArea(
+                        PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, rect);
+            } else {
+                mPipBoundsState.setNamedUnrestrictedKeepClearArea(
+                        PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, null);
+            }
+            mPipTouchHandler.onShelfVisibilityChanged(visible, height);
+        });
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
index 17392bc..3738353 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
@@ -785,7 +785,7 @@
 
     private void handleFlingTransition(SurfaceControl.Transaction startTx,
             SurfaceControl.Transaction finishTx, Rect destinationBounds) {
-        startTx.setPosition(mPipTransitionState.mPinnedTaskLeash,
+        startTx.setPosition(mPipTransitionState.getPinnedTaskLeash(),
                 destinationBounds.left, destinationBounds.top);
         startTx.apply();
 
@@ -799,7 +799,7 @@
 
     private void startResizeAnimation(SurfaceControl.Transaction startTx,
             SurfaceControl.Transaction finishTx, Rect destinationBounds, int duration) {
-        SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash;
+        SurfaceControl pipLeash = mPipTransitionState.getPinnedTaskLeash();
         Preconditions.checkState(pipLeash != null,
                 "No leash cached by mPipTransitionState=" + mPipTransitionState);
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
index 751175f..d98be55 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
@@ -531,7 +531,7 @@
                 // If resize transition was scheduled from this component, handle leash updates.
                 mWaitingForBoundsChangeTransition = false;
 
-                SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash;
+                SurfaceControl pipLeash = mPipTransitionState.getPinnedTaskLeash();
                 Preconditions.checkState(pipLeash != null,
                         "No leash cached by mPipTransitionState=" + mPipTransitionState);
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
index 8b25b11..607de0e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
@@ -118,7 +118,7 @@
     public void removePipAfterAnimation() {
         SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
         PipAlphaAnimator animator = new PipAlphaAnimator(mContext,
-                mPipTransitionState.mPinnedTaskLeash, tx, PipAlphaAnimator.FADE_OUT);
+                mPipTransitionState.getPinnedTaskLeash(), tx, PipAlphaAnimator.FADE_OUT);
         animator.setAnimationEndCallback(this::scheduleRemovePipImmediately);
         animator.start();
     }
@@ -203,7 +203,7 @@
                     "%s: Attempted to user resize PIP to empty bounds, aborting.", TAG);
             return;
         }
-        SurfaceControl leash = mPipTransitionState.mPinnedTaskLeash;
+        SurfaceControl leash = mPipTransitionState.getPinnedTaskLeash();
         final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
 
         Matrix transformTensor = new Matrix();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java
index 2c7584a..2f93715 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java
@@ -29,6 +29,8 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.ProtoLog;
 import com.android.internal.util.Preconditions;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.ShellExecutor;
@@ -36,6 +38,7 @@
 import com.android.wm.shell.common.pip.PipBoundsState;
 import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.pip2.animation.PipResizeAnimator;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.shared.annotations.ShellMainThread;
 
 import java.util.ArrayList;
@@ -49,7 +52,8 @@
 public class PipTaskListener implements ShellTaskOrganizer.TaskListener,
         PipTransitionState.PipTransitionStateChangedListener {
     private static final int ASPECT_RATIO_CHANGE_DURATION = 250;
-    private static final String ANIMATING_ASPECT_RATIO_CHANGE = "animating_aspect_ratio_change";
+    @VisibleForTesting
+    static final String ANIMATING_ASPECT_RATIO_CHANGE = "animating_aspect_ratio_change";
 
     private final Context mContext;
     private final PipTransitionState mPipTransitionState;
@@ -63,6 +67,8 @@
     private boolean mWaitingForAspectRatioChange = false;
     private final List<PipParamsChangedCallback> mPipParamsChangedListeners = new ArrayList<>();
 
+    private PipResizeAnimatorSupplier mPipResizeAnimatorSupplier;
+
     public PipTaskListener(Context context,
             ShellTaskOrganizer shellTaskOrganizer,
             PipTransitionState pipTransitionState,
@@ -84,6 +90,7 @@
                         ShellTaskOrganizer.TASK_LISTENER_TYPE_PIP);
             });
         }
+        mPipResizeAnimatorSupplier = PipResizeAnimator::new;
     }
 
     void setPictureInPictureParams(@Nullable PictureInPictureParams params) {
@@ -121,6 +128,9 @@
         if (mPictureInPictureParams.equals(params)) {
             return;
         }
+        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                "onTaskInfoChanged: %s, state=%s oldParams=%s newParams=%s",
+                taskInfo.topActivity, mPipTransitionState, mPictureInPictureParams, params);
         setPictureInPictureParams(params);
         float newAspectRatio = mPictureInPictureParams.getAspectRatioFloat();
         if (PipUtils.aspectRatioChanged(newAspectRatio, mPipBoundsState.getAspectRatio())) {
@@ -167,18 +177,18 @@
                 final int duration = extra.getInt(ANIMATING_BOUNDS_CHANGE_DURATION,
                         PipTransition.BOUNDS_CHANGE_JUMPCUT_DURATION);
 
-                Preconditions.checkNotNull(mPipTransitionState.mPinnedTaskLeash,
+                Preconditions.checkNotNull(mPipTransitionState.getPinnedTaskLeash(),
                         "Leash is null for bounds transition.");
 
                 if (mWaitingForAspectRatioChange) {
-                    PipResizeAnimator animator = new PipResizeAnimator(mContext,
-                            mPipTransitionState.mPinnedTaskLeash, startTx, finishTx,
+                    mWaitingForAspectRatioChange = false;
+                    PipResizeAnimator animator = mPipResizeAnimatorSupplier.get(mContext,
+                            mPipTransitionState.getPinnedTaskLeash(), startTx, finishTx,
                             destinationBounds,
                             mPipBoundsState.getBounds(), destinationBounds, duration,
                             0f /* delta */);
-                    animator.setAnimationEndCallback(() -> {
-                        mPipScheduler.scheduleFinishResizePip(destinationBounds);
-                    });
+                    animator.setAnimationEndCallback(
+                            () -> mPipScheduler.scheduleFinishResizePip(destinationBounds));
                     animator.start();
                 }
                 break;
@@ -192,4 +202,22 @@
         default void onActionsChanged(List<RemoteAction> actions, RemoteAction closeAction) {
         }
     }
+
+    @VisibleForTesting
+    interface PipResizeAnimatorSupplier {
+        PipResizeAnimator get(@NonNull Context context,
+                @NonNull SurfaceControl leash,
+                @Nullable SurfaceControl.Transaction startTx,
+                @Nullable SurfaceControl.Transaction finishTx,
+                @NonNull Rect baseBounds,
+                @NonNull Rect startBounds,
+                @NonNull Rect endBounds,
+                int duration,
+                float delta);
+    }
+
+    @VisibleForTesting
+    void setPipResizeAnimatorSupplier(@NonNull PipResizeAnimatorSupplier supplier) {
+        mPipResizeAnimatorSupplier = supplier;
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
index 19d293e..65972fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
@@ -231,17 +231,15 @@
                 // KCA triggered movement to wait for other transitions (e.g. due to IME changes).
                 return;
             }
-            mPipTransitionState.setOnIdlePipTransitionStateRunnable(() -> {
-                boolean hasUserInteracted = (mPipBoundsState.hasUserMovedPip()
-                        || mPipBoundsState.hasUserResizedPip());
-                int delta = mPipBoundsAlgorithm.getEntryDestinationBounds().top
-                        - mPipBoundsState.getBounds().top;
+            boolean hasUserInteracted = (mPipBoundsState.hasUserMovedPip()
+                    || mPipBoundsState.hasUserResizedPip());
+            int delta = mPipBoundsAlgorithm.getEntryDestinationBounds().top
+                    - mPipBoundsState.getBounds().top;
 
-                if (!mIsImeShowing && !hasUserInteracted && delta != 0) {
-                    // If the user hasn't interacted with PiP, we respect the keep clear areas
-                    mMotionHelper.animateToOffset(mPipBoundsState.getBounds(), delta);
-                }
-            });
+            if (!mIsImeShowing && !hasUserInteracted && delta != 0) {
+                // If the user hasn't interacted with PiP, we respect the keep clear areas
+                mMotionHelper.animateToOffset(mPipBoundsState.getBounds(), delta);
+            }
         };
 
         if (PipUtils.isPip2ExperimentEnabled()) {
@@ -877,7 +875,7 @@
             mMovementWithinDismiss = touchState.getDownTouchPosition().y
                     >= mPipBoundsState.getMovementBounds().bottom;
             mMotionHelper.setSpringingToTouch(false);
-            mPipDismissTargetHandler.setTaskLeash(mPipTransitionState.mPinnedTaskLeash);
+            mPipDismissTargetHandler.setTaskLeash(mPipTransitionState.getPinnedTaskLeash());
 
             // If the menu is still visible then just poke the menu
             // so that it will timeout after the user stops touching it
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index 64d8887..ea783e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -325,9 +325,7 @@
             return false;
         }
 
-        SurfaceControl pipLeash = pipChange.getLeash();
-        Preconditions.checkNotNull(pipLeash, "Leash is null for swipe-up transition.");
-
+        final SurfaceControl pipLeash = getLeash(pipChange);
         final Rect destinationBounds = pipChange.getEndAbsBounds();
         final SurfaceControl swipePipToHomeOverlay = mPipTransitionState.getSwipePipToHomeOverlay();
         if (swipePipToHomeOverlay != null) {
@@ -349,20 +347,14 @@
                 : startRotation - endRotation;
         if (delta != ROTATION_0) {
             mPipTransitionState.setInFixedRotation(true);
-            handleBoundsTypeFixedRotation(pipChange, pipActivityChange, endRotation);
-        }
-
-        Rect sourceRectHint = null;
-        if (pipChange.getTaskInfo() != null
-                && pipChange.getTaskInfo().pictureInPictureParams != null) {
-            sourceRectHint = pipChange.getTaskInfo().pictureInPictureParams.getSourceRectHint();
+            handleBoundsEnterFixedRotation(pipChange, pipActivityChange, endRotation);
         }
 
         prepareConfigAtEndActivity(startTransaction, finishTransaction, pipChange,
                 pipActivityChange);
         startTransaction.merge(finishTransaction);
         PipEnterAnimator animator = new PipEnterAnimator(mContext, pipLeash,
-                startTransaction, finishTransaction, destinationBounds, sourceRectHint, delta);
+                startTransaction, finishTransaction, destinationBounds, delta);
         animator.setEnterStartState(pipChange);
         animator.onEnterAnimationUpdate(1.0f /* fraction */, startTransaction);
         startTransaction.apply();
@@ -405,7 +397,7 @@
         final Rect adjustedSourceRectHint = sourceRectHint != null ? new Rect(sourceRectHint)
                 : PipUtils.getEnterPipWithOverlaySrcRectHint(startBounds, aspectRatio);
 
-        final SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash;
+        final SurfaceControl pipLeash = mPipTransitionState.getPinnedTaskLeash();
 
         // For opening type transitions, if there is a change of mode TO_FRONT/OPEN,
         // make sure that change has alpha of 1f, since it's init state might be set to alpha=0f
@@ -420,20 +412,20 @@
         }
 
         final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info);
-        int startRotation = pipChange.getStartRotation();
-        int endRotation = fixedRotationChange != null
+        final int startRotation = pipChange.getStartRotation();
+        final int endRotation = fixedRotationChange != null
                 ? fixedRotationChange.getEndFixedRotation() : ROTATION_UNDEFINED;
         final int delta = endRotation == ROTATION_UNDEFINED ? ROTATION_0
                 : startRotation - endRotation;
 
         if (delta != ROTATION_0) {
             mPipTransitionState.setInFixedRotation(true);
-            handleBoundsTypeFixedRotation(pipChange, pipActivityChange,
+            handleBoundsEnterFixedRotation(pipChange, pipActivityChange,
                     fixedRotationChange.getEndFixedRotation());
         }
 
         PipEnterAnimator animator = new PipEnterAnimator(mContext, pipLeash,
-                startTransaction, finishTransaction, endBounds, adjustedSourceRectHint, delta);
+                startTransaction, finishTransaction, endBounds, delta);
         if (sourceRectHint == null) {
             // update the src-rect-hint in params in place, to set up initial animator transform.
             params.getSourceRectHint().set(adjustedSourceRectHint);
@@ -465,7 +457,7 @@
         animator.start();
     }
 
-    private void handleBoundsTypeFixedRotation(TransitionInfo.Change pipTaskChange,
+    private void handleBoundsEnterFixedRotation(TransitionInfo.Change pipTaskChange,
             TransitionInfo.Change pipActivityChange, int endRotation) {
         final Rect endBounds = pipTaskChange.getEndAbsBounds();
         final Rect endActivityBounds = pipActivityChange.getEndAbsBounds();
@@ -498,6 +490,26 @@
                 endBounds.top + activityEndOffset.y);
     }
 
+    private void handleExpandFixedRotation(TransitionInfo.Change pipTaskChange, int endRotation) {
+        final Rect endBounds = pipTaskChange.getEndAbsBounds();
+        final int width = endBounds.width();
+        final int height = endBounds.height();
+        final int left = endBounds.left;
+        final int top = endBounds.top;
+        int newTop, newLeft;
+
+        if (endRotation == Surface.ROTATION_90) {
+            newLeft = top;
+            newTop = -(left + width);
+        } else {
+            newLeft = -(height + top);
+            newTop = left;
+        }
+        // Modify the endBounds, rotating and placing them potentially off-screen, so that
+        // as we translate and rotate around the origin, we place them right into the target.
+        endBounds.set(newLeft, newTop, newLeft + height, newTop + width);
+    }
+
 
     private boolean startAlphaTypeEnterAnimation(@NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction startTransaction,
@@ -509,7 +521,7 @@
         }
 
         Rect destinationBounds = pipChange.getEndAbsBounds();
-        SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash;
+        SurfaceControl pipLeash = mPipTransitionState.getPinnedTaskLeash();
         Preconditions.checkNotNull(pipLeash, "Leash is null for alpha transition.");
 
         // Start transition with 0 alpha at the entry bounds.
@@ -550,33 +562,51 @@
             }
         }
 
-        // for multi activity, we need to manually set the leash layer
-        if (pipChange.getTaskInfo() == null) {
-            TransitionInfo.Change parent = getChangeByToken(info, pipChange.getParent());
-            if (parent != null) {
-                startTransaction.setLayer(parent.getLeash(), Integer.MAX_VALUE - 1);
-            }
+        // The parent change if we were in a multi-activity PiP; null if single activity PiP.
+        final TransitionInfo.Change parentBeforePip = pipChange.getTaskInfo() == null
+                ? getChangeByToken(info, pipChange.getParent()) : null;
+        if (parentBeforePip != null) {
+            // For multi activity, we need to manually set the leash layer
+            startTransaction.setLayer(parentBeforePip.getLeash(), Integer.MAX_VALUE - 1);
         }
 
-        Rect startBounds = pipChange.getStartAbsBounds();
-        Rect endBounds = pipChange.getEndAbsBounds();
-        SurfaceControl pipLeash = pipChange.getLeash();
-        Preconditions.checkNotNull(pipLeash, "Leash is null for exit transition.");
+        final Rect startBounds = pipChange.getStartAbsBounds();
+        final Rect endBounds = pipChange.getEndAbsBounds();
+        final SurfaceControl pipLeash = getLeash(pipChange);
 
-        Rect sourceRectHint = null;
-        if (pipChange.getTaskInfo() != null
-                && pipChange.getTaskInfo().pictureInPictureParams != null) {
+        PictureInPictureParams params = null;
+        if (pipChange.getTaskInfo() != null) {
             // single activity
-            sourceRectHint = pipChange.getTaskInfo().pictureInPictureParams.getSourceRectHint();
-        } else if (mPipTaskListener.getPictureInPictureParams().hasSourceBoundsHint()) {
+            params = pipChange.getTaskInfo().pictureInPictureParams;
+        } else if (parentBeforePip != null && parentBeforePip.getTaskInfo() != null) {
             // multi activity
-            sourceRectHint = mPipTaskListener.getPictureInPictureParams().getSourceRectHint();
+            params = parentBeforePip.getTaskInfo().pictureInPictureParams;
+        }
+        final Rect sourceRectHint = PipBoundsAlgorithm.getValidSourceHintRect(params, endBounds,
+                startBounds);
+
+        final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info);
+        final int startRotation = pipChange.getStartRotation();
+        final int endRotation = fixedRotationChange != null
+                ? fixedRotationChange.getEndFixedRotation() : ROTATION_UNDEFINED;
+        final int delta = endRotation == ROTATION_UNDEFINED ? ROTATION_0
+                : endRotation - startRotation;
+
+        if (delta != ROTATION_0) {
+            handleExpandFixedRotation(pipChange, endRotation);
         }
 
         PipExpandAnimator animator = new PipExpandAnimator(mContext, pipLeash,
                 startTransaction, finishTransaction, endBounds, startBounds, endBounds,
-                sourceRectHint, Surface.ROTATION_0);
-        animator.setAnimationEndCallback(this::finishTransition);
+                sourceRectHint, delta);
+        animator.setAnimationEndCallback(() -> {
+            if (parentBeforePip != null) {
+                // TODO b/377362511: Animate local leash instead to also handle letterbox case.
+                // For multi-activity, set the crop to be null
+                finishTransaction.setCrop(pipLeash, null);
+            }
+            finishTransition();
+        });
         animator.start();
         return true;
     }
@@ -723,6 +753,13 @@
         }
     }
 
+    @NonNull
+    private SurfaceControl getLeash(TransitionInfo.Change change) {
+        SurfaceControl leash = change.getLeash();
+        Preconditions.checkNotNull(leash, "Leash is null for change=" + change);
+        return leash;
+    }
+
     //
     // Miscellaneous callbacks and listeners
     //
@@ -760,17 +797,17 @@
 
                 mPipTransitionState.mPipTaskToken = extra.getParcelable(
                         PIP_TASK_TOKEN, WindowContainerToken.class);
-                mPipTransitionState.mPinnedTaskLeash = extra.getParcelable(
-                        PIP_TASK_LEASH, SurfaceControl.class);
+                mPipTransitionState.setPinnedTaskLeash(extra.getParcelable(
+                        PIP_TASK_LEASH, SurfaceControl.class));
                 boolean hasValidTokenAndLeash = mPipTransitionState.mPipTaskToken != null
-                        && mPipTransitionState.mPinnedTaskLeash != null;
+                        && mPipTransitionState.getPinnedTaskLeash() != null;
 
                 Preconditions.checkState(hasValidTokenAndLeash,
                         "Unexpected bundle for " + mPipTransitionState);
                 break;
             case PipTransitionState.EXITED_PIP:
                 mPipTransitionState.mPipTaskToken = null;
-                mPipTransitionState.mPinnedTaskLeash = null;
+                mPipTransitionState.setPinnedTaskLeash(null);
                 break;
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
index ccdd66b..03e06f9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
@@ -142,7 +142,7 @@
 
     // pinned PiP task's leash
     @Nullable
-    SurfaceControl mPinnedTaskLeash;
+    private SurfaceControl mPinnedTaskLeash;
 
     // Overlay leash potentially used during swipe PiP to home transition;
     // if null while mInSwipePipToHomeTransition is true, then srcRectHint was invalid.
@@ -304,6 +304,14 @@
         mSwipePipToHomeAppBounds.setEmpty();
     }
 
+    @Nullable SurfaceControl getPinnedTaskLeash() {
+        return mPinnedTaskLeash;
+    }
+
+    void setPinnedTaskLeash(@Nullable SurfaceControl leash) {
+        mPinnedTaskLeash = leash;
+    }
+
     /**
      * @return true if either in swipe or button-nav fixed rotation.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
index 799028a..4a301cc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
@@ -24,7 +24,7 @@
 
 import com.android.wm.shell.recents.IRecentsAnimationRunner;
 import com.android.wm.shell.recents.IRecentTasksListener;
-import com.android.wm.shell.shared.GroupedRecentTaskInfo;
+import com.android.wm.shell.shared.GroupedTaskInfo;
 
 /**
  * Interface that is exposed to remote callers to fetch recent tasks.
@@ -44,7 +44,7 @@
     /**
      * Gets the set of recent tasks.
      */
-    GroupedRecentTaskInfo[] getRecentTasks(int maxNum, int flags, int userId) = 3;
+    GroupedTaskInfo[] getRecentTasks(int maxNum, int flags, int userId) = 3;
 
     /**
      * Gets the set of running tasks.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl
index 371bdd5..b58f068 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl
@@ -18,6 +18,8 @@
 
 import android.app.ActivityManager.RunningTaskInfo;
 
+import com.android.wm.shell.shared.GroupedTaskInfo;
+
 /**
  * Listener interface that Launcher attaches to SystemUI to get split-screen callbacks.
  */
@@ -44,8 +46,8 @@
     void onRunningTaskChanged(in RunningTaskInfo taskInfo);
 
     /** A task has moved to front. */
-    oneway void onTaskMovedToFront(in RunningTaskInfo taskInfo);
+    void onTaskMovedToFront(in GroupedTaskInfo[] visibleTasks);
 
     /** A task info has changed. */
-    oneway void onTaskInfoChanged(in RunningTaskInfo taskInfo);
+    void onTaskInfoChanged(in RunningTaskInfo taskInfo);
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
index 8c5d1e7..364a087 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
@@ -19,7 +19,7 @@
 import android.annotation.Nullable;
 import android.graphics.Color;
 
-import com.android.wm.shell.shared.GroupedRecentTaskInfo;
+import com.android.wm.shell.shared.GroupedTaskInfo;
 import com.android.wm.shell.shared.annotations.ExternalThread;
 
 import java.util.List;
@@ -35,7 +35,7 @@
      * Gets the set of recent tasks.
      */
     default void getRecentTasks(int maxNum, int flags, int userId, Executor callbackExecutor,
-            Consumer<List<GroupedRecentTaskInfo>> callback) {
+            Consumer<List<GroupedTaskInfo>> callback) {
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index faa2015..9911669 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -24,10 +24,12 @@
 import android.Manifest;
 import android.annotation.RequiresPermission;
 import android.app.ActivityManager;
+import android.app.ActivityManager.RecentTaskInfo;
 import android.app.ActivityTaskManager;
 import android.app.IApplicationThread;
 import android.app.KeyguardManager;
 import android.app.PendingIntent;
+import android.app.TaskInfo;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -55,7 +57,7 @@
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.desktopmode.DesktopRepository;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import com.android.wm.shell.shared.GroupedRecentTaskInfo;
+import com.android.wm.shell.shared.GroupedTaskInfo;
 import com.android.wm.shell.shared.annotations.ExternalThread;
 import com.android.wm.shell.shared.annotations.ShellMainThread;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
@@ -379,7 +381,8 @@
             return;
         }
         try {
-            mListener.onTaskMovedToFront(taskInfo);
+            GroupedTaskInfo runningTask = GroupedTaskInfo.forFullscreenTasks(taskInfo);
+            mListener.onTaskMovedToFront(new GroupedTaskInfo[]{ runningTask });
         } catch (RemoteException e) {
             Slog.w(TAG, "Failed call onTaskMovedToFront", e);
         }
@@ -407,27 +410,27 @@
     }
 
     @VisibleForTesting
-    ArrayList<GroupedRecentTaskInfo> getRecentTasks(int maxNum, int flags, int userId) {
+    ArrayList<GroupedTaskInfo> getRecentTasks(int maxNum, int flags, int userId) {
         // Note: the returned task list is from the most-recent to least-recent order
-        final List<ActivityManager.RecentTaskInfo> rawList = mActivityTaskManager.getRecentTasks(
+        final List<RecentTaskInfo> rawList = mActivityTaskManager.getRecentTasks(
                 maxNum, flags, userId);
 
         // Make a mapping of task id -> task info
-        final SparseArray<ActivityManager.RecentTaskInfo> rawMapping = new SparseArray<>();
+        final SparseArray<TaskInfo> rawMapping = new SparseArray<>();
         for (int i = 0; i < rawList.size(); i++) {
-            final ActivityManager.RecentTaskInfo taskInfo = rawList.get(i);
+            final TaskInfo taskInfo = rawList.get(i);
             rawMapping.put(taskInfo.taskId, taskInfo);
         }
 
-        ArrayList<ActivityManager.RecentTaskInfo> freeformTasks = new ArrayList<>();
+        ArrayList<TaskInfo> freeformTasks = new ArrayList<>();
         Set<Integer> minimizedFreeformTasks = new HashSet<>();
 
         int mostRecentFreeformTaskIndex = Integer.MAX_VALUE;
 
         // Pull out the pairs as we iterate back in the list
-        ArrayList<GroupedRecentTaskInfo> recentTasks = new ArrayList<>();
+        ArrayList<GroupedTaskInfo> recentTasks = new ArrayList<>();
         for (int i = 0; i < rawList.size(); i++) {
-            final ActivityManager.RecentTaskInfo taskInfo = rawList.get(i);
+            final RecentTaskInfo taskInfo = rawList.get(i);
             if (!rawMapping.contains(taskInfo.taskId)) {
                 // If it's not in the mapping, then it was already paired with another task
                 continue;
@@ -460,20 +463,20 @@
             final int pairedTaskId = mSplitTasks.get(taskInfo.taskId, INVALID_TASK_ID);
             if (pairedTaskId != INVALID_TASK_ID && rawMapping.contains(
                     pairedTaskId)) {
-                final ActivityManager.RecentTaskInfo pairedTaskInfo = rawMapping.get(pairedTaskId);
+                final TaskInfo pairedTaskInfo = rawMapping.get(pairedTaskId);
                 rawMapping.remove(pairedTaskId);
-                recentTasks.add(GroupedRecentTaskInfo.forSplitTasks(taskInfo, pairedTaskInfo,
+                recentTasks.add(GroupedTaskInfo.forSplitTasks(taskInfo, pairedTaskInfo,
                         mTaskSplitBoundsMap.get(pairedTaskId)));
             } else {
-                recentTasks.add(GroupedRecentTaskInfo.forSingleTask(taskInfo));
+                recentTasks.add(GroupedTaskInfo.forFullscreenTasks(taskInfo));
             }
         }
 
         // Add a special entry for freeform tasks
         if (!freeformTasks.isEmpty()) {
             recentTasks.add(mostRecentFreeformTaskIndex,
-                    GroupedRecentTaskInfo.forFreeformTasks(
-                            freeformTasks.toArray(new ActivityManager.RecentTaskInfo[0]),
+                    GroupedTaskInfo.forFreeformTasks(
+                            freeformTasks,
                             minimizedFreeformTasks));
         }
 
@@ -514,16 +517,16 @@
      * {@param ignoreTaskToken} if it is non-null.
      */
     @Nullable
-    public ActivityManager.RecentTaskInfo findTaskInBackground(ComponentName componentName,
+    public RecentTaskInfo findTaskInBackground(ComponentName componentName,
             int userId, @Nullable WindowContainerToken ignoreTaskToken) {
         if (componentName == null) {
             return null;
         }
-        List<ActivityManager.RecentTaskInfo> tasks = mActivityTaskManager.getRecentTasks(
+        List<RecentTaskInfo> tasks = mActivityTaskManager.getRecentTasks(
                 Integer.MAX_VALUE, ActivityManager.RECENT_IGNORE_UNAVAILABLE,
                 ActivityManager.getCurrentUser());
         for (int i = 0; i < tasks.size(); i++) {
-            final ActivityManager.RecentTaskInfo task = tasks.get(i);
+            final RecentTaskInfo task = tasks.get(i);
             if (task.isVisible) {
                 continue;
             }
@@ -541,12 +544,12 @@
      * Find the background task that match the given taskId.
      */
     @Nullable
-    public ActivityManager.RecentTaskInfo findTaskInBackground(int taskId) {
-        List<ActivityManager.RecentTaskInfo> tasks = mActivityTaskManager.getRecentTasks(
+    public RecentTaskInfo findTaskInBackground(int taskId) {
+        List<RecentTaskInfo> tasks = mActivityTaskManager.getRecentTasks(
                 Integer.MAX_VALUE, ActivityManager.RECENT_IGNORE_UNAVAILABLE,
                 ActivityManager.getCurrentUser());
         for (int i = 0; i < tasks.size(); i++) {
-            final ActivityManager.RecentTaskInfo task = tasks.get(i);
+            final RecentTaskInfo task = tasks.get(i);
             if (task.isVisible) {
                 continue;
             }
@@ -570,7 +573,7 @@
         pw.println(prefix + TAG);
         pw.println(prefix + " mListener=" + mListener);
         pw.println(prefix + "Tasks:");
-        ArrayList<GroupedRecentTaskInfo> recentTasks = getRecentTasks(Integer.MAX_VALUE,
+        ArrayList<GroupedTaskInfo> recentTasks = getRecentTasks(Integer.MAX_VALUE,
                 ActivityManager.RECENT_IGNORE_UNAVAILABLE, ActivityManager.getCurrentUser());
         for (int i = 0; i < recentTasks.size(); i++) {
             pw.println(innerPrefix + recentTasks.get(i));
@@ -584,9 +587,9 @@
     private class RecentTasksImpl implements RecentTasks {
         @Override
         public void getRecentTasks(int maxNum, int flags, int userId, Executor executor,
-                Consumer<List<GroupedRecentTaskInfo>> callback) {
+                Consumer<List<GroupedTaskInfo>> callback) {
             mMainExecutor.execute(() -> {
-                List<GroupedRecentTaskInfo> tasks =
+                List<GroupedTaskInfo> tasks =
                         RecentTasksController.this.getRecentTasks(maxNum, flags, userId);
                 executor.execute(() -> callback.accept(tasks));
             });
@@ -650,7 +653,7 @@
             }
 
             @Override
-            public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) {
+            public void onTaskMovedToFront(GroupedTaskInfo[] taskInfo) {
                 mListener.call(l -> l.onTaskMovedToFront(taskInfo));
             }
 
@@ -692,17 +695,20 @@
         }
 
         @Override
-        public GroupedRecentTaskInfo[] getRecentTasks(int maxNum, int flags, int userId)
+        public GroupedTaskInfo[] getRecentTasks(int maxNum, int flags, int userId)
                 throws RemoteException {
             if (mController == null) {
                 // The controller is already invalidated -- just return an empty task list for now
-                return new GroupedRecentTaskInfo[0];
+                return new GroupedTaskInfo[0];
             }
 
-            final GroupedRecentTaskInfo[][] out = new GroupedRecentTaskInfo[][]{null};
+            final GroupedTaskInfo[][] out = new GroupedTaskInfo[][]{null};
             executeRemoteCallWithTaskPermission(mController, "getRecentTasks",
-                    (controller) -> out[0] = controller.getRecentTasks(maxNum, flags, userId)
-                            .toArray(new GroupedRecentTaskInfo[0]),
+                    (controller) -> {
+                        List<GroupedTaskInfo> tasks = controller.getRecentTasks(
+                                maxNum, flags, userId);
+                        out[0] = tasks.toArray(new GroupedTaskInfo[0]);
+                    },
                     true /* blocking */);
             return out[0];
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 6d4d4b4..40065b9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -544,10 +544,10 @@
                             .findFirst()
                             .get();
             final RemoteAnimationTarget openingTarget = TransitionUtil.newSyntheticTarget(
-                    homeTask, mShellTaskOrganizer.getHomeTaskOverlayContainer(), TRANSIT_OPEN,
+                    homeTask, mShellTaskOrganizer.getHomeTaskSurface(), TRANSIT_OPEN,
                     0, true /* isTranslucent */);
             final RemoteAnimationTarget closingTarget = TransitionUtil.newSyntheticTarget(
-                    homeTask, mShellTaskOrganizer.getHomeTaskOverlayContainer(), TRANSIT_CLOSE,
+                    homeTask, mShellTaskOrganizer.getHomeTaskSurface(), TRANSIT_CLOSE,
                     0, true /* isTranslucent */);
             final ArrayList<RemoteAnimationTarget> apps = new ArrayList<>();
             apps.add(openingTarget);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index b33f3e9..4407e5b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -245,9 +245,9 @@
                 return;
             }
             mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
-            mVisible = taskInfo.isVisible && taskInfo.isVisibleRequested;
+            mVisible = isStageVisible();
             mCallbacks.onChildTaskStatusChanged(this, taskInfo.taskId, true /* present */,
-                    mVisible);
+                    taskInfo.isVisible && taskInfo.isVisibleRequested);
         } else {
             throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
                     + "\n mRootTaskInfo: " + mRootTaskInfo);
@@ -293,6 +293,19 @@
         t.reparent(sc, findTaskSurface(taskId));
     }
 
+    /**
+     * Checks against all children task info and return true if any are marked as visible.
+     */
+    private boolean isStageVisible() {
+        for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
+            if (mChildrenTaskInfo.valueAt(i).isVisible
+                    && mChildrenTaskInfo.valueAt(i).isVisibleRequested) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private SurfaceControl findTaskSurface(int taskId) {
         if (mRootTaskInfo.taskId == taskId) {
             return mRootLeash;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index ec58292..29e4b5b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -395,10 +395,17 @@
                     continue;
                 }
                 // No default animation for this, so just update bounds/position.
-                final int rootIdx = TransitionUtil.rootIndexFor(change, info);
-                startTransaction.setPosition(change.getLeash(),
-                        change.getEndAbsBounds().left - info.getRoot(rootIdx).getOffset().x,
-                        change.getEndAbsBounds().top - info.getRoot(rootIdx).getOffset().y);
+                if (change.getParent() == null) {
+                    // For independent change without a parent, we have reparented it to the root
+                    // leash in Transitions#setupAnimHierarchy.
+                    final int rootIdx = TransitionUtil.rootIndexFor(change, info);
+                    startTransaction.setPosition(change.getLeash(),
+                            change.getEndAbsBounds().left - info.getRoot(rootIdx).getOffset().x,
+                            change.getEndAbsBounds().top - info.getRoot(rootIdx).getOffset().y);
+                } else {
+                    startTransaction.setPosition(change.getLeash(),
+                            change.getEndRelOffset().x, change.getEndRelOffset().y);
+                }
                 // Seamless display transition doesn't need to animate.
                 if (isSeamlessDisplayChange) continue;
                 if (isTask || (change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index be4fd7c..7265fb8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -24,6 +24,8 @@
 import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 
+import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions;
+
 import android.app.ActivityManager.RunningTaskInfo;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -195,7 +197,12 @@
             return;
         }
 
-        decoration.relayout(taskInfo, decoration.mHasGlobalFocus);
+        if (enableDisplayFocusInShellTransitions()) {
+            // Pass the current global focus status to avoid updates outside of a ShellTransition.
+            decoration.relayout(taskInfo, decoration.mHasGlobalFocus);
+        } else {
+            decoration.relayout(taskInfo, taskInfo.isFocused);
+        }
     }
 
     @Override
@@ -496,4 +503,4 @@
         return Settings.Global.getInt(resolver,
                 DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 0) != 0;
     }
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index e1683f3..f2d8a78 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -31,6 +31,7 @@
 import static android.view.WindowInsets.Type.statusBars;
 
 import static com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU;
+import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions;
 import static com.android.wm.shell.compatui.AppCompatUtils.isTopActivityExemptFromDesktopWindowing;
 import static com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger;
 import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR;
@@ -41,7 +42,6 @@
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
-import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
 
 import android.annotation.NonNull;
 import android.app.ActivityManager;
@@ -49,6 +49,7 @@
 import android.app.ActivityTaskManager;
 import android.app.IActivityManager;
 import android.app.IActivityTaskManager;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Point;
@@ -122,8 +123,6 @@
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
 import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition;
-import com.android.wm.shell.splitscreen.SplitScreen;
-import com.android.wm.shell.splitscreen.SplitScreen.StageType;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.sysui.KeyguardChangeListener;
 import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -446,17 +445,6 @@
     @Override
     public void setSplitScreenController(SplitScreenController splitScreenController) {
         mSplitScreenController = splitScreenController;
-        mSplitScreenController.registerSplitScreenListener(new SplitScreen.SplitScreenListener() {
-            @Override
-            public void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {
-                if (visible && stage != STAGE_TYPE_UNDEFINED) {
-                    DesktopModeWindowDecoration decor = mWindowDecorByTaskId.get(taskId);
-                    if (decor != null && DesktopModeStatus.canEnterDesktopMode(mContext)) {
-                        mDesktopTasksController.moveToSplit(decor.mTaskInfo);
-                    }
-                }
-            }
-        });
     }
 
     @Override
@@ -481,7 +469,12 @@
             removeTaskFromEventReceiver(oldTaskInfo.displayId);
             incrementEventReceiverTasks(taskInfo.displayId);
         }
-        decoration.relayout(taskInfo, decoration.mHasGlobalFocus);
+        if (enableDisplayFocusInShellTransitions()) {
+            // Pass the current global focus status to avoid updates outside of a ShellTransition.
+            decoration.relayout(taskInfo, decoration.mHasGlobalFocus);
+        } else {
+            decoration.relayout(taskInfo, taskInfo.isFocused);
+        }
         mActivityOrientationChangeHandler.ifPresent(handler ->
                 handler.handleActivityOrientationChange(oldTaskInfo, taskInfo));
     }
@@ -1290,6 +1283,7 @@
                 }
                 break;
             }
+            case MotionEvent.ACTION_CANCEL:
             case MotionEvent.ACTION_UP: {
                 if (mTransitionDragActive) {
                     final DesktopModeVisualIndicator.DragStartState dragStartState =
@@ -1304,32 +1298,11 @@
                         // Though this isn't a hover event, we need to update handle's hover state
                         // as it likely will change.
                         relevantDecor.updateHoverAndPressStatus(ev);
-                        DesktopModeVisualIndicator.IndicatorType resultType =
-                                mDesktopTasksController.onDragPositioningEndThroughStatusBar(
-                                        new PointF(ev.getRawX(), ev.getRawY()),
-                                        relevantDecor.mTaskInfo,
-                                        relevantDecor.mTaskSurface);
-                        // If we are entering split select, handle will no longer be visible and
-                        // should not be receiving any input.
-                        if (resultType == TO_SPLIT_LEFT_INDICATOR
-                                || resultType == TO_SPLIT_RIGHT_INDICATOR) {
-                            relevantDecor.disposeStatusBarInputLayer();
-                            // We should also dispose the other split task's input layer if
-                            // applicable.
-                            final int splitPosition = mSplitScreenController
-                                    .getSplitPosition(relevantDecor.mTaskInfo.taskId);
-                            if (splitPosition != SPLIT_POSITION_UNDEFINED) {
-                                final int oppositePosition =
-                                        splitPosition == SPLIT_POSITION_TOP_OR_LEFT
-                                                ? SPLIT_POSITION_BOTTOM_OR_RIGHT
-                                                : SPLIT_POSITION_TOP_OR_LEFT;
-                                final RunningTaskInfo oppositeTaskInfo =
-                                        mSplitScreenController.getTaskInfo(oppositePosition);
-                                if (oppositeTaskInfo != null) {
-                                    mWindowDecorByTaskId.get(oppositeTaskInfo.taskId)
-                                            .disposeStatusBarInputLayer();
-                                }
-                            }
+                        if (ev.getActionMasked() == ACTION_CANCEL) {
+                            mDesktopTasksController.onDragPositioningCancelThroughStatusBar(
+                                    relevantDecor.mTaskInfo);
+                        } else {
+                            endDragToDesktop(ev, relevantDecor);
                         }
                         mMoveToDesktopAnimator = null;
                         return;
@@ -1378,10 +1351,35 @@
                 }
                 break;
             }
+        }
+    }
 
-            case MotionEvent.ACTION_CANCEL: {
-                mTransitionDragActive = false;
-                mMoveToDesktopAnimator = null;
+    private void endDragToDesktop(MotionEvent ev, DesktopModeWindowDecoration relevantDecor) {
+        DesktopModeVisualIndicator.IndicatorType resultType =
+                mDesktopTasksController.onDragPositioningEndThroughStatusBar(
+                        new PointF(ev.getRawX(), ev.getRawY()),
+                        relevantDecor.mTaskInfo,
+                        relevantDecor.mTaskSurface);
+        // If we are entering split select, handle will no longer be visible and
+        // should not be receiving any input.
+        if (resultType == TO_SPLIT_LEFT_INDICATOR
+                || resultType == TO_SPLIT_RIGHT_INDICATOR) {
+            relevantDecor.disposeStatusBarInputLayer();
+            // We should also dispose the other split task's input layer if
+            // applicable.
+            final int splitPosition = mSplitScreenController
+                    .getSplitPosition(relevantDecor.mTaskInfo.taskId);
+            if (splitPosition != SPLIT_POSITION_UNDEFINED) {
+                final int oppositePosition =
+                        splitPosition == SPLIT_POSITION_TOP_OR_LEFT
+                                ? SPLIT_POSITION_BOTTOM_OR_RIGHT
+                                : SPLIT_POSITION_TOP_OR_LEFT;
+                final RunningTaskInfo oppositeTaskInfo =
+                        mSplitScreenController.getTaskInfo(oppositePosition);
+                if (oppositeTaskInfo != null) {
+                    mWindowDecorByTaskId.get(oppositeTaskInfo.taskId)
+                            .disposeStatusBarInputLayer();
+                }
             }
         }
     }
@@ -1481,6 +1479,9 @@
                 && isTopActivityExemptFromDesktopWindowing(mContext, taskInfo)) {
             return false;
         }
+        if (isPartOfDefaultHomePackage(taskInfo)) {
+            return false;
+        }
         return DesktopModeStatus.canEnterDesktopMode(mContext)
                 && !DesktopWallpaperActivity.isWallpaperTask(taskInfo)
                 && taskInfo.getWindowingMode() != WINDOWING_MODE_PINNED
@@ -1488,6 +1489,14 @@
                 && !taskInfo.configuration.windowConfiguration.isAlwaysOnTop();
     }
 
+    private boolean isPartOfDefaultHomePackage(RunningTaskInfo taskInfo) {
+        final ComponentName currentDefaultHome =
+                mContext.getPackageManager().getHomeActivities(new ArrayList<>());
+        return currentDefaultHome != null && taskInfo.baseActivity != null
+                && currentDefaultHome.getPackageName()
+                .equals(taskInfo.baseActivity.getPackageName());
+    }
+
     private void createWindowDecoration(
             ActivityManager.RunningTaskInfo taskInfo,
             SurfaceControl taskSurface,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt
index e43c3a6..61963cd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt
@@ -30,6 +30,7 @@
 import com.android.wm.shell.common.DisplayChangeController
 import com.android.wm.shell.common.DisplayController
 import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger
 import com.android.wm.shell.desktopmode.DesktopRepository
 import com.android.wm.shell.desktopmode.DesktopTasksController
 import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator
@@ -48,6 +49,7 @@
     private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler,
     private val returnToDragStartAnimator: ReturnToDragStartAnimator,
     private val taskRepository: DesktopRepository,
+    private val desktopModeEventLogger: DesktopModeEventLogger,
 ) : DisplayChangeController.OnDisplayChangingListener {
     @VisibleForTesting
     var tilingTransitionHandlerByDisplayId = SparseArray<DesktopTilingWindowDecoration>()
@@ -80,6 +82,7 @@
                             toggleResizeDesktopTaskTransitionHandler,
                             returnToDragStartAnimator,
                             taskRepository,
+                            desktopModeEventLogger,
                         )
                     tilingTransitionHandlerByDisplayId.put(displayId, newHandler)
                     newHandler
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt
index 209eb5e..6cdc517c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt
@@ -23,6 +23,7 @@
 import android.graphics.Region
 import android.os.Binder
 import android.view.LayoutInflater
+import android.view.MotionEvent
 import android.view.RoundedCorner
 import android.view.SurfaceControl
 import android.view.SurfaceControlViewHost
@@ -39,6 +40,7 @@
 import android.view.WindowlessWindowManager
 import com.android.wm.shell.R
 import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger
 import java.util.function.Supplier
 
 /**
@@ -141,8 +143,9 @@
         t.setRelativeLayer(leash, relativeLeash, 1)
     }
 
-    override fun onDividerMoveStart(pos: Int) {
+    override fun onDividerMoveStart(pos: Int, motionEvent: MotionEvent) {
         setSlippery(false)
+        transitionHandler.onDividerHandleDragStart(motionEvent)
     }
 
     /**
@@ -161,13 +164,13 @@
      * Notifies the transition handler of tiling operations ending, which might result in resizing
      * WindowContainerTransactions if the sizes of the tiled tasks changed.
      */
-    override fun onDividerMovedEnd(pos: Int) {
+    override fun onDividerMovedEnd(pos: Int, motionEvent: MotionEvent) {
         setSlippery(true)
         val t = transactionSupplier.get()
         t.setPosition(leash, pos.toFloat() - maxRoundedCornerRadius, dividerBounds.top.toFloat())
         val dividerWidth = dividerBounds.width()
         dividerBounds.set(pos, dividerBounds.top, pos + dividerWidth, dividerBounds.bottom)
-        transitionHandler.onDividerHandleDragEnd(dividerBounds, t)
+        transitionHandler.onDividerHandleDragEnd(dividerBounds, t, motionEvent)
     }
 
     private fun getWindowManagerParams(): WindowManager.LayoutParams {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
index c46767c..1c593c03 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
@@ -25,6 +25,7 @@
 import android.os.IBinder
 import android.os.UserHandle
 import android.util.Slog
+import android.view.MotionEvent
 import android.view.SurfaceControl
 import android.view.SurfaceControl.Transaction
 import android.view.WindowManager.TRANSIT_CHANGE
@@ -44,6 +45,8 @@
 import com.android.wm.shell.common.DisplayController
 import com.android.wm.shell.common.DisplayLayout
 import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
 import com.android.wm.shell.desktopmode.DesktopRepository
 import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
 import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator
@@ -70,6 +73,7 @@
     private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler,
     private val returnToDragStartAnimator: ReturnToDragStartAnimator,
     private val taskRepository: DesktopRepository,
+    private val desktopModeEventLogger: DesktopModeEventLogger,
     private val transactionSupplier: Supplier<Transaction> = Supplier { Transaction() },
 ) :
     Transitions.TransitionHandler,
@@ -218,6 +222,25 @@
         return tilingManager
     }
 
+    fun onDividerHandleDragStart(motionEvent: MotionEvent) {
+        val leftTiledTask = leftTaskResizingHelper ?: return
+        val rightTiledTask = rightTaskResizingHelper ?: return
+
+        desktopModeEventLogger.logTaskResizingStarted(
+            ResizeTrigger.TILING_DIVIDER,
+            motionEvent,
+            leftTiledTask.taskInfo,
+            displayController
+        )
+
+        desktopModeEventLogger.logTaskResizingStarted(
+            ResizeTrigger.TILING_DIVIDER,
+            motionEvent,
+            rightTiledTask.taskInfo,
+            displayController
+        )
+    }
+
     fun onDividerHandleMoved(dividerBounds: Rect, t: SurfaceControl.Transaction): Boolean {
         val leftTiledTask = leftTaskResizingHelper ?: return false
         val rightTiledTask = rightTaskResizingHelper ?: return false
@@ -266,10 +289,32 @@
         return true
     }
 
-    fun onDividerHandleDragEnd(dividerBounds: Rect, t: SurfaceControl.Transaction) {
+    fun onDividerHandleDragEnd(
+        dividerBounds: Rect,
+        t: SurfaceControl.Transaction,
+        motionEvent: MotionEvent,
+    ) {
         val leftTiledTask = leftTaskResizingHelper ?: return
         val rightTiledTask = rightTaskResizingHelper ?: return
 
+        desktopModeEventLogger.logTaskResizingEnded(
+            ResizeTrigger.TILING_DIVIDER,
+            motionEvent,
+            leftTiledTask.taskInfo,
+            leftTiledTask.newBounds.height(),
+            leftTiledTask.newBounds.width(),
+            displayController
+        )
+
+        desktopModeEventLogger.logTaskResizingEnded(
+            ResizeTrigger.TILING_DIVIDER,
+            motionEvent,
+            rightTiledTask.taskInfo,
+            rightTiledTask.newBounds.height(),
+            rightTiledTask.newBounds.width(),
+            displayController
+        )
+
         if (leftTiledTask.newBounds == leftTiledTask.bounds) {
             leftTiledTask.hideVeil()
             rightTiledTask.hideVeil()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DividerMoveCallback.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DividerMoveCallback.kt
index b3b30ad..9799d01 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DividerMoveCallback.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DividerMoveCallback.kt
@@ -15,14 +15,16 @@
  */
 package com.android.wm.shell.windowdecor.tiling
 
+import android.view.MotionEvent
+
 /** Divider move callback to whichever entity that handles the moving logic. */
 interface DividerMoveCallback {
     /** Called on the divider move start gesture. */
-    fun onDividerMoveStart(pos: Int)
+    fun onDividerMoveStart(pos: Int, motionEvent: MotionEvent)
 
     /** Called on the divider moved by dragging it. */
     fun onDividerMove(pos: Int): Boolean
 
     /** Called on divider move gesture end. */
-    fun onDividerMovedEnd(pos: Int)
+    fun onDividerMovedEnd(pos: Int, motionEvent: MotionEvent)
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt
index 8922905..111e28e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt
@@ -206,7 +206,7 @@
         when (event.actionMasked) {
             MotionEvent.ACTION_DOWN -> {
                 if (!isWithinHandleRegion(yTouchPosInDivider)) return true
-                callback.onDividerMoveStart(touchPos)
+                callback.onDividerMoveStart(touchPos, event)
                 setTouching()
                 canResize = true
             }
@@ -230,7 +230,7 @@
                 if (!canResize) return true
                 if (moving && resized) {
                     dividerBounds.left = dividerBounds.left + lastAcceptedPos - startPos
-                    callback.onDividerMovedEnd(dividerBounds.left)
+                    callback.onDividerMovedEnd(dividerBounds.left, event)
                 }
                 moving = false
                 canResize = false
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MinimizeAppWindowsTest.kt
similarity index 61%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MinimizeAppWindowsTest.kt
index 94b2bdf..e16159c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MinimizeAppWindowsTest.kt
@@ -14,9 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util.settings
+package com.android.wm.shell.functional
 
-import com.android.systemui.kosmos.Kosmos
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.scenarios.MinimizeAppWindows
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
 
-val Kosmos.userAwareSecureSettingsRepository by
-    Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+/* Functional test for [MinimizeAppWindows]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+class MinimizeAppWindowsTest : MinimizeAppWindows()
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt
new file mode 100644
index 0000000..b548363
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.scenarios
+
+import android.app.Instrumentation
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.flicker.rules.ChangeDisplayOrientationRule
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.NewTasksAppHelper
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import com.android.wm.shell.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+/**
+ * Base scenario test for minimizing all the desktop app windows one-by-one by clicking their
+ * minimize buttons.
+ */
+@Ignore("Test Base Class")
+abstract class MinimizeAppWindows
+constructor(private val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val testApp1 = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+    private val testApp2 = DesktopModeAppHelper(NonResizeableAppHelper(instrumentation))
+    private val testApp3 = DesktopModeAppHelper(NewTasksAppHelper(instrumentation))
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, Rotation.ROTATION_0)
+
+    @Before
+    fun setup() {
+        Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+        Assume.assumeTrue(Flags.enableMinimizeButton())
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+        ChangeDisplayOrientationRule.setRotation(rotation)
+        testApp1.enterDesktopWithDrag(wmHelper, device)
+        testApp2.launchViaIntent(wmHelper)
+        testApp3.launchViaIntent(wmHelper)
+    }
+
+    @Test
+    open fun minimizeAllAppWindows() {
+        testApp3.minimizeDesktopApp(wmHelper, device)
+        testApp2.minimizeDesktopApp(wmHelper, device)
+        testApp1.minimizeDesktopApp(wmHelper, device)
+    }
+
+    @After
+    fun teardown() {
+        testApp1.exit(wmHelper)
+        testApp2.exit(wmHelper)
+        testApp3.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
index 266e484..2ed7d07 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
@@ -62,6 +62,7 @@
 
     @Before
     public void setUp() throws Exception {
+        mTargetProgressCalled = new CountDownLatch(1);
         mMainThreadHandler = new Handler(Looper.getMainLooper());
         final BackMotionEvent backEvent = backMotionEventFrom(0, 0);
         mMainThreadHandler.post(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
index ecaf970..803e5d4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
@@ -43,38 +43,30 @@
                     .apply {
                         isTopActivityTransparent = true
                         numActivities = 1
-                    }))
-        assertFalse(isTopActivityExemptFromDesktopWindowing(mContext,
-            createFreeformTask(/* displayId */ 0)
-                    .apply {
-                        isTopActivityTransparent = true
-                        numActivities = 0
+                        isTopActivityNoDisplay = false
                     }))
     }
 
     @Test
-    fun testIsTopActivityExemptFromDesktopWindowing_singleTopActivity() {
-        assertTrue(isTopActivityExemptFromDesktopWindowing(mContext,
-            createFreeformTask(/* displayId */ 0)
-                    .apply {
-                        isTopActivityTransparent = true
-                        numActivities = 1
-                    }))
+    fun testIsTopActivityExemptFromDesktopWindowing_topActivityTransparent_multipleActivities() {
         assertFalse(isTopActivityExemptFromDesktopWindowing(mContext,
             createFreeformTask(/* displayId */ 0)
-                    .apply {
-                        isTopActivityTransparent = false
-                        numActivities = 1
-                    }))
+                .apply {
+                    isTopActivityTransparent = true
+                    numActivities = 2
+                    isTopActivityNoDisplay = false
+                }))
     }
 
     @Test
-    fun testIsTopActivityExemptFromDesktopWindowing__topActivityStyleFloating() {
+    fun testIsTopActivityExemptFromDesktopWindowing_topActivityTransparent_notDisplayed() {
         assertFalse(isTopActivityExemptFromDesktopWindowing(mContext,
             createFreeformTask(/* displayId */ 0)
-                    .apply {
-                        isTopActivityStyleFloating = true
-                    }))
+                .apply {
+                    isTopActivityTransparent = true
+                    numActivities = 1
+                    isTopActivityNoDisplay = true
+                }))
     }
 
     @Test
@@ -85,6 +77,19 @@
             createFreeformTask(/* displayId */ 0)
                     .apply {
                         baseActivity = baseComponent
+                        isTopActivityNoDisplay = false
                     }))
     }
+
+    @Test
+    fun testIsTopActivityExemptFromDesktopWindowing_systemUiTask_notDisplayed() {
+        val systemUIPackageName = context.resources.getString(R.string.config_systemUi)
+        val baseComponent = ComponentName(systemUIPackageName, /* class */ "")
+        assertFalse(isTopActivityExemptFromDesktopWindowing(mContext,
+            createFreeformTask(/* displayId */ 0)
+                .apply {
+                    baseActivity = baseComponent
+                    isTopActivityNoDisplay = true
+                }))
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt
new file mode 100644
index 0000000..fea8236
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.content.ContentResolver
+import android.os.Binder
+import android.provider.Settings
+import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
+import android.testing.AndroidTestingRunner
+import android.view.Display.DEFAULT_DISPLAY
+import android.view.IWindowManager
+import android.view.WindowManager.TRANSIT_CHANGE
+import android.window.DisplayAreaInfo
+import android.window.WindowContainerTransaction
+import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.never
+import com.android.wm.shell.MockToken
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.transition.Transitions
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.isNull
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.whenever
+
+/**
+ * Test class for [DesktopDisplayEventHandler]
+ *
+ * Usage: atest WMShellUnitTests:DesktopDisplayEventHandlerTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DesktopDisplayEventHandlerTest : ShellTestCase() {
+
+  @Mock lateinit var testExecutor: ShellExecutor
+  @Mock lateinit var transitions: Transitions
+  @Mock lateinit var displayController: DisplayController
+  @Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
+  @Mock private lateinit var mockWindowManager: IWindowManager
+
+  private lateinit var shellInit: ShellInit
+  private lateinit var handler: DesktopDisplayEventHandler
+
+  @Before
+  fun setUp() {
+    shellInit = spy(ShellInit(testExecutor))
+    whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
+    val tda = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
+    whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)).thenReturn(tda)
+    handler =
+        DesktopDisplayEventHandler(
+            context,
+            shellInit,
+            transitions,
+            displayController,
+            rootTaskDisplayAreaOrganizer,
+            mockWindowManager,
+        )
+    shellInit.init()
+  }
+
+  private fun testDisplayWindowingModeSwitch(
+    defaultWindowingMode: Int,
+    extendedDisplayEnabled: Boolean,
+    expectTransition: Boolean
+  ) {
+    val externalDisplayId = 100
+    val captor = ArgumentCaptor.forClass(OnDisplaysChangedListener::class.java)
+    verify(displayController).addDisplayWindowListener(captor.capture())
+    val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+    tda.configuration.windowConfiguration.windowingMode = defaultWindowingMode
+    whenever(mockWindowManager.getWindowingMode(anyInt())).thenAnswer { defaultWindowingMode }
+    val settingsSession = ExtendedDisplaySettingsSession(
+      context.contentResolver, if (extendedDisplayEnabled) 1 else 0)
+
+    settingsSession.use {
+      // The external display connected.
+      whenever(rootTaskDisplayAreaOrganizer.getDisplayIds())
+        .thenReturn(intArrayOf(DEFAULT_DISPLAY, externalDisplayId))
+      captor.value.onDisplayAdded(externalDisplayId)
+      tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+      // The external display disconnected.
+      whenever(rootTaskDisplayAreaOrganizer.getDisplayIds())
+        .thenReturn(intArrayOf(DEFAULT_DISPLAY))
+      captor.value.onDisplayRemoved(externalDisplayId)
+
+      if (expectTransition) {
+        val arg = argumentCaptor<WindowContainerTransaction>()
+        verify(transitions, times(2)).startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull())
+        assertThat(arg.firstValue.changes[tda.token.asBinder()]?.windowingMode)
+          .isEqualTo(WINDOWING_MODE_FREEFORM)
+        assertThat(arg.secondValue.changes[tda.token.asBinder()]?.windowingMode)
+          .isEqualTo(defaultWindowingMode)
+      } else {
+        verify(transitions, never()).startTransition(eq(TRANSIT_CHANGE), any(), isNull())
+      }
+    }
+  }
+
+  @Test
+  fun displayWindowingModeSwitchOnDisplayConnected_extendedDisplayDisabled() {
+    testDisplayWindowingModeSwitch(
+      defaultWindowingMode = WINDOWING_MODE_FULLSCREEN,
+      extendedDisplayEnabled = false,
+      expectTransition = false
+    )
+  }
+
+  @Test
+  fun displayWindowingModeSwitchOnDisplayConnected_fullscreenDisplay() {
+    testDisplayWindowingModeSwitch(
+      defaultWindowingMode = WINDOWING_MODE_FULLSCREEN,
+      extendedDisplayEnabled = true,
+      expectTransition = true
+    )
+  }
+
+  @Test
+  fun displayWindowingModeSwitchOnDisplayConnected_freeformDisplay() {
+    testDisplayWindowingModeSwitch(
+      defaultWindowingMode = WINDOWING_MODE_FREEFORM,
+      extendedDisplayEnabled = true,
+      expectTransition = false
+    )
+  }
+
+  private class ExtendedDisplaySettingsSession(
+    private val contentResolver: ContentResolver,
+      private val overrideValue: Int
+  ) : AutoCloseable {
+    private val settingName = DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
+    private val initialValue = Settings.Global.getInt(contentResolver, settingName, 0)
+
+    init { Settings.Global.putInt(contentResolver, settingName, overrideValue) }
+
+    override fun close() {
+      Settings.Global.putInt(contentResolver, settingName, initialValue)
+    }
+  }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
index df061e3..b06c2da 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
@@ -447,6 +447,37 @@
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+    fun startAnimation_pendingTransition_noLaunchChange_returnsFalse() {
+        val wct = WindowContainerTransaction()
+        val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
+        val nonLaunchTaskChange = createChange(createTask(WINDOWING_MODE_FREEFORM))
+        val transition = Binder()
+        whenever(transitions.startTransition(eq(TRANSIT_OPEN), eq(wct), anyOrNull()))
+            .thenReturn(transition)
+        mixedHandler.addPendingMixedTransition(
+            PendingMixedTransition.Launch(
+                transition = transition,
+                launchingTask = launchingTask.taskId,
+                minimizingTask = null,
+                exitingImmersiveTask = null,
+            )
+        )
+
+        val started = mixedHandler.startAnimation(
+            transition,
+            createTransitionInfo(
+                TRANSIT_OPEN,
+                listOf(nonLaunchTaskChange)
+            ),
+            SurfaceControl.Transaction(),
+            SurfaceControl.Transaction(),
+        ) { }
+
+        assertFalse("Should not start animation without launching desktop task", started)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
     fun addPendingAndAnimateLaunchTransition_noMinimizeChange_doesNotReparentMinimizeChange() {
         val wct = WindowContainerTransaction()
         val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index b157d55..315a46f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -1123,11 +1123,11 @@
 
   @Test
   @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
-  fun moveRunningTaskToDesktop_topActivityTranslucentWithStyleFloating_taskIsMovedToDesktop() {
+  fun moveRunningTaskToDesktop_topActivityTranslucentWithoutDisplay_taskIsMovedToDesktop() {
     val task =
       setUpFullscreenTask().apply {
         isTopActivityTransparent = true
-        isTopActivityStyleFloating = true
+        isTopActivityNoDisplay = true
         numActivities = 1
       }
 
@@ -1139,11 +1139,11 @@
 
   @Test
   @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
-  fun moveRunningTaskToDesktop_topActivityTranslucentWithoutStyleFloating_doesNothing() {
+  fun moveRunningTaskToDesktop_topActivityTranslucentWithDisplay_doesNothing() {
     val task =
       setUpFullscreenTask().apply {
         isTopActivityTransparent = true
-        isTopActivityStyleFloating = false
+        isTopActivityNoDisplay = false
         numActivities = 1
       }
 
@@ -1153,20 +1153,41 @@
 
   @Test
   @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
-  fun moveRunningTaskToDesktop_systemUIActivity_doesNothing() {
-    val task = setUpFullscreenTask()
-
+  fun moveRunningTaskToDesktop_systemUIActivityWithDisplay_doesNothing() {
     // Set task as systemUI package
     val systemUIPackageName = context.resources.getString(
       com.android.internal.R.string.config_systemUi)
     val baseComponent = ComponentName(systemUIPackageName, /* class */ "")
-    task.baseActivity = baseComponent
+    val task =
+      setUpFullscreenTask().apply {
+        baseActivity = baseComponent
+        isTopActivityNoDisplay = false
+      }
 
     controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
     verifyEnterDesktopWCTNotExecuted()
   }
 
   @Test
+  @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+  fun moveRunningTaskToDesktop_systemUIActivityWithoutDisplay_doesNothing() {
+    // Set task as systemUI package
+    val systemUIPackageName = context.resources.getString(
+      com.android.internal.R.string.config_systemUi)
+    val baseComponent = ComponentName(systemUIPackageName, /* class */ "")
+    val task =
+      setUpFullscreenTask().apply {
+        baseActivity = baseComponent
+        isTopActivityNoDisplay = true
+      }
+
+    controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
+
+    val wct = getLatestEnterDesktopWct()
+    assertThat(wct.changes[task.token.asBinder()]?.windowingMode).isEqualTo(WINDOWING_MODE_FREEFORM)
+  }
+
+  @Test
   fun moveRunningTaskToDesktop_deviceSupported_taskIsMovedToDesktop() {
     val task = setUpFullscreenTask()
 
@@ -2223,14 +2244,14 @@
 
   @Test
   @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
-  fun handleRequest_topActivityTransparentWithStyleFloating_returnSwitchToFreeformWCT() {
+  fun handleRequest_topActivityTransparentWithoutDisplay_returnSwitchToFreeformWCT() {
     val freeformTask = setUpFreeformTask()
     markTaskVisible(freeformTask)
 
     val task =
       setUpFullscreenTask().apply {
         isTopActivityTransparent = true
-        isTopActivityStyleFloating = true
+        isTopActivityNoDisplay = true
         numActivities = 1
       }
 
@@ -2241,11 +2262,14 @@
 
   @Test
   @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
-  fun handleRequest_topActivityTransparentWithoutStyleFloating_returnSwitchToFullscreenWCT() {
+  fun handleRequest_topActivityTransparentWithDisplay_returnSwitchToFullscreenWCT() {
+    val freeformTask = setUpFreeformTask()
+    markTaskVisible(freeformTask)
+
     val task =
       setUpFreeformTask().apply {
         isTopActivityTransparent = true
-        isTopActivityStyleFloating = false
+        isTopActivityNoDisplay = false
         numActivities = 1
       }
 
@@ -2256,14 +2280,19 @@
 
   @Test
   @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
-  fun handleRequest_systemUIActivity_returnSwitchToFullscreenWCT() {
-    val task = setUpFreeformTask()
+  fun handleRequest_systemUIActivityWithDisplay_returnSwitchToFullscreenWCT() {
+    val freeformTask = setUpFreeformTask()
+    markTaskVisible(freeformTask)
 
     // Set task as systemUI package
     val systemUIPackageName = context.resources.getString(
       com.android.internal.R.string.config_systemUi)
     val baseComponent = ComponentName(systemUIPackageName, /* class */ "")
-    task.baseActivity = baseComponent
+    val task =
+      setUpFreeformTask().apply {
+        baseActivity = baseComponent
+        isTopActivityNoDisplay = false
+      }
 
     val result = controller.handleRequest(Binder(), createTransition(task))
     assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
@@ -2271,6 +2300,27 @@
   }
 
   @Test
+  @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+  fun handleRequest_systemUIActivityWithoutDisplay_returnSwitchToFreeformWCT() {
+    val freeformTask = setUpFreeformTask()
+    markTaskVisible(freeformTask)
+
+    // Set task as systemUI package
+    val systemUIPackageName = context.resources.getString(
+      com.android.internal.R.string.config_systemUi)
+    val baseComponent = ComponentName(systemUIPackageName, /* class */ "")
+    val task =
+      setUpFullscreenTask().apply {
+        baseActivity = baseComponent
+        isTopActivityNoDisplay = true
+      }
+
+    val result = controller.handleRequest(Binder(), createTransition(task))
+    assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
+      .isEqualTo(WINDOWING_MODE_FREEFORM)
+  }
+
+  @Test
   @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,)
   fun handleRequest_backTransition_singleTaskNoToken_noWallpaper_doesNotHandle() {
     val task = setUpFreeformTask()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java
new file mode 100644
index 0000000..a4008c1
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip2.animation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.Resources;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.Surface;
+import android.view.SurfaceControl;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
+import com.android.wm.shell.pip2.phone.PipAppIconOverlay;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit test again {@link PipEnterAnimator}.
+ */
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner.class)
+public class PipEnterAnimatorTest {
+
+    @Mock private Context mMockContext;
+
+    @Mock private Resources mMockResources;
+
+    @Mock private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mMockFactory;
+
+    @Mock private SurfaceControl.Transaction mMockAnimateTransaction;
+
+    @Mock private SurfaceControl.Transaction mMockStartTransaction;
+
+    @Mock private SurfaceControl.Transaction mMockFinishTransaction;
+
+    @Mock private Runnable mMockStartCallback;
+
+    @Mock private Runnable mMockEndCallback;
+
+    @Mock private PipAppIconOverlay mMockPipAppIconOverlay;
+
+    @Mock private SurfaceControl mMockAppIconOverlayLeash;
+
+    @Mock private ActivityInfo mMockActivityInfo;
+
+    @Surface.Rotation private int mRotation;
+    private SurfaceControl mTestLeash;
+    private Rect mEndBounds;
+    private PipEnterAnimator mPipEnterAnimator;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mMockContext.getResources()).thenReturn(mMockResources);
+        when(mMockResources.getInteger(anyInt())).thenReturn(0);
+        when(mMockFactory.getTransaction()).thenReturn(mMockAnimateTransaction);
+        when(mMockAnimateTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
+                .thenReturn(mMockAnimateTransaction);
+        when(mMockStartTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
+                .thenReturn(mMockStartTransaction);
+        when(mMockFinishTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
+                .thenReturn(mMockFinishTransaction);
+        when(mMockPipAppIconOverlay.getLeash()).thenReturn(mMockAppIconOverlayLeash);
+
+        mTestLeash = new SurfaceControl.Builder()
+                .setContainerLayer()
+                .setName("PipExpandAnimatorTest")
+                .setCallsite("PipExpandAnimatorTest")
+                .build();
+    }
+
+    @Test
+    public void setAnimationStartCallback_enter_callbackStartCallback() {
+        mRotation = Surface.ROTATION_0;
+        mEndBounds = new Rect(100, 100, 500, 500);
+        mPipEnterAnimator = new PipEnterAnimator(mMockContext, mTestLeash,
+                mMockStartTransaction, mMockFinishTransaction,
+                mEndBounds, mRotation);
+        mPipEnterAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+
+        mPipEnterAnimator.setAnimationStartCallback(mMockStartCallback);
+        mPipEnterAnimator.setAnimationEndCallback(mMockEndCallback);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            mPipEnterAnimator.start();
+            mPipEnterAnimator.pause();
+        });
+
+        verify(mMockStartCallback).run();
+        verifyZeroInteractions(mMockEndCallback);
+    }
+
+    @Test
+    public void setAnimationEndCallback_enter_callbackStartAndEndCallback() {
+        mRotation = Surface.ROTATION_0;
+        mEndBounds = new Rect(100, 100, 500, 500);
+        mPipEnterAnimator = new PipEnterAnimator(mMockContext, mTestLeash,
+                mMockStartTransaction, mMockFinishTransaction,
+                mEndBounds, mRotation);
+        mPipEnterAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+
+        mPipEnterAnimator.setAnimationStartCallback(mMockStartCallback);
+        mPipEnterAnimator.setAnimationEndCallback(mMockEndCallback);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            mPipEnterAnimator.start();
+            mPipEnterAnimator.end();
+        });
+
+        verify(mMockStartCallback).run();
+        verify(mMockEndCallback).run();
+    }
+
+    @Test
+    public void setAppIconContentOverlay_thenGetContentOverlayLeash_returnOverlayLeash() {
+        mRotation = Surface.ROTATION_0;
+        mEndBounds = new Rect(100, 100, 500, 500);
+        mPipEnterAnimator = new PipEnterAnimator(mMockContext, mTestLeash,
+                mMockStartTransaction, mMockFinishTransaction,
+                mEndBounds, mRotation);
+        mPipEnterAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+        mPipEnterAnimator.setPipAppIconOverlaySupplier(
+                (context, appBounds, endBounds, icon, iconSize) -> mMockPipAppIconOverlay);
+
+        mPipEnterAnimator.setAppIconContentOverlay(mMockContext, mEndBounds, mEndBounds,
+                mMockActivityInfo, 64 /* iconSize */);
+
+        assertEquals(mPipEnterAnimator.getContentOverlayLeash(), mMockAppIconOverlayLeash);
+    }
+
+    @Test
+    public void setAppIconContentOverlay_thenClearAppIconOverlay_returnNullLeash() {
+        mRotation = Surface.ROTATION_0;
+        mEndBounds = new Rect(100, 100, 500, 500);
+        mPipEnterAnimator = new PipEnterAnimator(mMockContext, mTestLeash,
+                mMockStartTransaction, mMockFinishTransaction,
+                mEndBounds, mRotation);
+        mPipEnterAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+        mPipEnterAnimator.setPipAppIconOverlaySupplier(
+                (context, appBounds, endBounds, icon, iconSize) -> mMockPipAppIconOverlay);
+
+        mPipEnterAnimator.setAppIconContentOverlay(mMockContext, mEndBounds, mEndBounds,
+                mMockActivityInfo, 64 /* iconSize */);
+        mPipEnterAnimator.clearAppIconOverlay();
+
+        assertNull(mPipEnterAnimator.getContentOverlayLeash());
+    }
+
+    @Test
+    public void onEnterAnimationUpdate_withContentOverlay_animateOverlay() {
+        mRotation = Surface.ROTATION_0;
+        mEndBounds = new Rect(100, 100, 500, 500);
+        mPipEnterAnimator = new PipEnterAnimator(mMockContext, mTestLeash,
+                mMockStartTransaction, mMockFinishTransaction,
+                mEndBounds, mRotation);
+        mPipEnterAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+        mPipEnterAnimator.setPipAppIconOverlaySupplier(
+                (context, appBounds, endBounds, icon, iconSize) -> mMockPipAppIconOverlay);
+
+        float fraction = 0.5f;
+        mPipEnterAnimator.setAppIconContentOverlay(mMockContext, mEndBounds, mEndBounds,
+                mMockActivityInfo, 64 /* iconSize */);
+        mPipEnterAnimator.onEnterAnimationUpdate(fraction, mMockAnimateTransaction);
+
+        verify(mMockPipAppIconOverlay).onAnimationUpdate(
+                eq(mMockAnimateTransaction), anyFloat(), eq(fraction), eq(mEndBounds));
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java
index e19a10a..b816f0e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java
@@ -93,6 +93,17 @@
                 .thenReturn(mMockTransaction);
         when(mMockTransaction.setShadowRadius(any(SurfaceControl.class), anyFloat()))
                 .thenReturn(mMockTransaction);
+        // No-op on the mMockStartTransaction
+        when(mMockStartTransaction.setAlpha(any(SurfaceControl.class), anyFloat()))
+                .thenReturn(mMockFinishTransaction);
+        when(mMockStartTransaction.setCrop(any(SurfaceControl.class), any(Rect.class)))
+                .thenReturn(mMockFinishTransaction);
+        when(mMockStartTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
+                .thenReturn(mMockFinishTransaction);
+        when(mMockStartTransaction.setCornerRadius(any(SurfaceControl.class), anyFloat()))
+                .thenReturn(mMockFinishTransaction);
+        when(mMockStartTransaction.setShadowRadius(any(SurfaceControl.class), anyFloat()))
+                .thenReturn(mMockFinishTransaction);
         // Do the same for mMockFinishTransaction
         when(mMockFinishTransaction.setAlpha(any(SurfaceControl.class), anyFloat()))
                 .thenReturn(mMockFinishTransaction);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java
new file mode 100644
index 0000000..89cb729
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java
@@ -0,0 +1,331 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip2.phone;
+
+import static com.android.wm.shell.pip2.phone.PipTaskListener.ANIMATING_ASPECT_RATIO_CHANGE;
+
+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.when;
+import static org.mockito.kotlin.MatchersKt.eq;
+import static org.mockito.kotlin.VerificationKt.clearInvocations;
+import static org.mockito.kotlin.VerificationKt.times;
+import static org.mockito.kotlin.VerificationKt.verify;
+import static org.mockito.kotlin.VerificationKt.verifyZeroInteractions;
+
+import android.app.ActivityManager;
+import android.app.PendingIntent;
+import android.app.PictureInPictureParams;
+import android.app.RemoteAction;
+import android.content.Context;
+import android.graphics.Rect;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.Rational;
+import android.view.SurfaceControl;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.pip2.animation.PipResizeAnimator;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit test against {@link PipTaskListener}.
+ */
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner.class)
+public class PipTaskListenerTest {
+
+    @Mock private Context mMockContext;
+    @Mock private ShellTaskOrganizer mMockShellTaskOrganizer;
+    @Mock private PipTransitionState mMockPipTransitionState;
+    @Mock private SurfaceControl mMockLeash;
+    @Mock private PipScheduler mMockPipScheduler;
+    @Mock private PipBoundsState mMockPipBoundsState;
+    @Mock private PipBoundsAlgorithm mMockPipBoundsAlgorithm;
+    @Mock private ShellExecutor mMockShellExecutor;
+
+    @Mock private Icon mMockIcon;
+    @Mock private PendingIntent mMockPendingIntent;
+
+    @Mock private PipTaskListener.PipParamsChangedCallback mMockPipParamsChangedCallback;
+
+    @Mock private PipResizeAnimator mMockPipResizeAnimator;
+
+    private ArgumentCaptor<List<RemoteAction>> mRemoteActionListCaptor;
+
+    private PipTaskListener mPipTaskListener;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mRemoteActionListCaptor = ArgumentCaptor.forClass(List.class);
+        when(mMockPipTransitionState.getPinnedTaskLeash()).thenReturn(mMockLeash);
+    }
+
+    @Test
+    public void constructor_addPipTransitionStateChangedListener() {
+        mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer,
+                mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState,
+                mMockPipBoundsAlgorithm, mMockShellExecutor);
+
+        verify(mMockPipTransitionState).addPipTransitionStateChangedListener(eq(mPipTaskListener));
+    }
+
+    @Test
+    public void setPictureInPictureParams_updatePictureInPictureParams() {
+        mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer,
+                mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState,
+                mMockPipBoundsAlgorithm, mMockShellExecutor);
+        Rational aspectRatio = new Rational(4, 3);
+        String action1 = "action1";
+
+        mPipTaskListener.setPictureInPictureParams(getPictureInPictureParams(
+                aspectRatio, action1));
+
+        PictureInPictureParams params = mPipTaskListener.getPictureInPictureParams();
+        assertEquals(aspectRatio, params.getAspectRatio());
+        assertTrue(params.hasSetActions());
+        assertEquals(1, params.getActions().size());
+        assertEquals(action1, params.getActions().get(0).getTitle());
+    }
+
+    @Test
+    public void setPictureInPictureParams_withActionsChanged_callbackActionsChanged() {
+        mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer,
+                mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState,
+                mMockPipBoundsAlgorithm, mMockShellExecutor);
+        mPipTaskListener.addParamsChangedListener(mMockPipParamsChangedCallback);
+        Rational aspectRatio = new Rational(4, 3);
+        String action1 = "action1";
+        mPipTaskListener.setPictureInPictureParams(getPictureInPictureParams(
+                aspectRatio, action1));
+
+        clearInvocations(mMockPipParamsChangedCallback);
+        action1 = "modified action1";
+        mPipTaskListener.setPictureInPictureParams(getPictureInPictureParams(
+                aspectRatio, action1));
+
+        verify(mMockPipParamsChangedCallback).onActionsChanged(
+                mRemoteActionListCaptor.capture(), any());
+        assertEquals(1, mRemoteActionListCaptor.getValue().size());
+        assertEquals(action1, mRemoteActionListCaptor.getValue().get(0).getTitle());
+    }
+
+    @Test
+    public void setPictureInPictureParams_withoutActionsChanged_doesNotCallbackActionsChanged() {
+        mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer,
+                mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState,
+                mMockPipBoundsAlgorithm, mMockShellExecutor);
+        mPipTaskListener.addParamsChangedListener(mMockPipParamsChangedCallback);
+        Rational aspectRatio = new Rational(4, 3);
+        String action1 = "action1";
+        mPipTaskListener.setPictureInPictureParams(getPictureInPictureParams(
+                aspectRatio, action1));
+
+        clearInvocations(mMockPipParamsChangedCallback);
+        mPipTaskListener.setPictureInPictureParams(getPictureInPictureParams(
+                aspectRatio, action1));
+
+        verifyZeroInteractions(mMockPipParamsChangedCallback);
+    }
+
+    @Test
+    public void onTaskInfoChanged_withActionsChanged_callbackActionsChanged() {
+        mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer,
+                mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState,
+                mMockPipBoundsAlgorithm, mMockShellExecutor);
+        mPipTaskListener.addParamsChangedListener(mMockPipParamsChangedCallback);
+        Rational aspectRatio = new Rational(4, 3);
+        when(mMockPipBoundsState.getAspectRatio()).thenReturn(aspectRatio.toFloat());
+        String action1 = "action1";
+        mPipTaskListener.onTaskInfoChanged(getTaskInfo(aspectRatio, action1));
+
+        clearInvocations(mMockPipParamsChangedCallback);
+        clearInvocations(mMockPipBoundsState);
+        action1 = "modified action1";
+        mPipTaskListener.onTaskInfoChanged(getTaskInfo(aspectRatio, action1));
+
+        verify(mMockPipTransitionState, times(0))
+                .setOnIdlePipTransitionStateRunnable(any(Runnable.class));
+        verify(mMockPipParamsChangedCallback).onActionsChanged(
+                mRemoteActionListCaptor.capture(), any());
+        assertEquals(1, mRemoteActionListCaptor.getValue().size());
+        assertEquals(action1, mRemoteActionListCaptor.getValue().get(0).getTitle());
+    }
+
+    @Test
+    public void onTaskInfoChanged_withAspectRatioChanged_callbackAspectRatioChanged() {
+        mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer,
+                mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState,
+                mMockPipBoundsAlgorithm, mMockShellExecutor);
+        mPipTaskListener.addParamsChangedListener(mMockPipParamsChangedCallback);
+        Rational aspectRatio = new Rational(4, 3);
+        when(mMockPipBoundsState.getAspectRatio()).thenReturn(aspectRatio.toFloat());
+        String action1 = "action1";
+        mPipTaskListener.onTaskInfoChanged(getTaskInfo(aspectRatio, action1));
+
+        clearInvocations(mMockPipParamsChangedCallback);
+        clearInvocations(mMockPipBoundsState);
+        aspectRatio = new Rational(16, 9);
+        mPipTaskListener.onTaskInfoChanged(getTaskInfo(aspectRatio, action1));
+
+        verify(mMockPipTransitionState).setOnIdlePipTransitionStateRunnable(any(Runnable.class));
+        verifyZeroInteractions(mMockPipParamsChangedCallback);
+    }
+
+    @Test
+    public void onTaskInfoChanged_withoutParamsChanged_doesNotCallbackAspectRatioChanged() {
+        mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer,
+                mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState,
+                mMockPipBoundsAlgorithm, mMockShellExecutor);
+        mPipTaskListener.addParamsChangedListener(mMockPipParamsChangedCallback);
+        Rational aspectRatio = new Rational(4, 3);
+        when(mMockPipBoundsState.getAspectRatio()).thenReturn(aspectRatio.toFloat());
+        String action1 = "action1";
+        mPipTaskListener.onTaskInfoChanged(getTaskInfo(aspectRatio, action1));
+
+        clearInvocations(mMockPipParamsChangedCallback);
+        mPipTaskListener.onTaskInfoChanged(getTaskInfo(aspectRatio, action1));
+
+        verifyZeroInteractions(mMockPipParamsChangedCallback);
+        verify(mMockPipTransitionState, times(0))
+                .setOnIdlePipTransitionStateRunnable(any(Runnable.class));
+    }
+
+    @Test
+    public void onPipTransitionStateChanged_scheduledBoundsChangeWithAspectRatioChange_schedule() {
+        mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer,
+                mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState,
+                mMockPipBoundsAlgorithm, mMockShellExecutor);
+        Bundle extras = new Bundle();
+        extras.putBoolean(ANIMATING_ASPECT_RATIO_CHANGE, true);
+
+        mPipTaskListener.onPipTransitionStateChanged(
+                PipTransitionState.UNDEFINED, PipTransitionState.SCHEDULED_BOUNDS_CHANGE, extras);
+
+        verify(mMockPipScheduler).scheduleAnimateResizePip(any(), anyBoolean(), anyInt());
+    }
+
+    @Test
+    public void onPipTransitionStateChanged_scheduledBoundsChangeWithoutAspectRatioChange_noop() {
+        mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer,
+                mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState,
+                mMockPipBoundsAlgorithm, mMockShellExecutor);
+        Bundle extras = new Bundle();
+        extras.putBoolean(ANIMATING_ASPECT_RATIO_CHANGE, false);
+
+        mPipTaskListener.onPipTransitionStateChanged(
+                PipTransitionState.UNDEFINED,
+                PipTransitionState.SCHEDULED_BOUNDS_CHANGE,
+                extras);
+
+        verifyZeroInteractions(mMockPipScheduler);
+    }
+
+    @Test
+    public void onPipTransitionStateChanged_changingPipBoundsWaitAspectRatioChange_animate() {
+        mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer,
+                mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState,
+                mMockPipBoundsAlgorithm, mMockShellExecutor);
+        Bundle extras = new Bundle();
+        extras.putBoolean(ANIMATING_ASPECT_RATIO_CHANGE, true);
+        extras.putParcelable(PipTransition.PIP_DESTINATION_BOUNDS,
+                new Rect(0, 0, 100, 100));
+        when(mMockPipBoundsState.getBounds()).thenReturn(new Rect(0, 0, 200, 200));
+
+        mPipTaskListener.onPipTransitionStateChanged(
+                PipTransitionState.UNDEFINED,
+                PipTransitionState.SCHEDULED_BOUNDS_CHANGE,
+                extras);
+        mPipTaskListener.setPipResizeAnimatorSupplier(
+                (context, leash, startTx, finishTx, baseBounds, startBounds, endBounds,
+                        duration, delta) -> mMockPipResizeAnimator);
+        mPipTaskListener.onPipTransitionStateChanged(
+                PipTransitionState.SCHEDULED_BOUNDS_CHANGE,
+                PipTransitionState.CHANGING_PIP_BOUNDS,
+                extras);
+
+        verify(mMockPipResizeAnimator, times(1)).start();
+    }
+
+    @Test
+    public void onPipTransitionStateChanged_changingPipBoundsNotAspectRatioChange_noop() {
+        mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer,
+                mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState,
+                mMockPipBoundsAlgorithm, mMockShellExecutor);
+        Bundle extras = new Bundle();
+        extras.putBoolean(ANIMATING_ASPECT_RATIO_CHANGE, false);
+        extras.putParcelable(PipTransition.PIP_DESTINATION_BOUNDS,
+                new Rect(0, 0, 100, 100));
+        when(mMockPipBoundsState.getBounds()).thenReturn(new Rect(0, 0, 200, 200));
+
+        mPipTaskListener.onPipTransitionStateChanged(
+                PipTransitionState.UNDEFINED,
+                PipTransitionState.SCHEDULED_BOUNDS_CHANGE,
+                extras);
+        mPipTaskListener.setPipResizeAnimatorSupplier(
+                (context, leash, startTx, finishTx, baseBounds, startBounds, endBounds,
+                        duration, delta) -> mMockPipResizeAnimator);
+        mPipTaskListener.onPipTransitionStateChanged(
+                PipTransitionState.SCHEDULED_BOUNDS_CHANGE,
+                PipTransitionState.CHANGING_PIP_BOUNDS,
+                extras);
+
+        verify(mMockPipResizeAnimator, times(0)).start();
+    }
+
+    private PictureInPictureParams getPictureInPictureParams(Rational aspectRatio,
+            String... actions) {
+        final PictureInPictureParams.Builder builder = new PictureInPictureParams.Builder();
+        builder.setAspectRatio(aspectRatio);
+        final List<RemoteAction> remoteActions = new ArrayList<>();
+        for (String action : actions) {
+            remoteActions.add(new RemoteAction(mMockIcon, action, action, mMockPendingIntent));
+        }
+        if (!remoteActions.isEmpty()) {
+            builder.setActions(remoteActions);
+        }
+        return builder.build();
+    }
+
+    private ActivityManager.RunningTaskInfo getTaskInfo(Rational aspectRatio,
+            String... actions) {
+        final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
+        taskInfo.pictureInPictureParams = getPictureInPictureParams(aspectRatio, actions);
+        return taskInfo;
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt
similarity index 81%
rename from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt
index 0c100fc..2b30bc3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt
@@ -17,6 +17,7 @@
 package com.android.wm.shell.recents
 
 import android.app.ActivityManager
+import android.app.TaskInfo
 import android.graphics.Rect
 import android.os.Parcel
 import android.testing.AndroidTestingRunner
@@ -24,11 +25,10 @@
 import android.window.WindowContainerToken
 import androidx.test.filters.SmallTest
 import com.android.wm.shell.ShellTestCase
-import com.android.wm.shell.shared.GroupedRecentTaskInfo
-import com.android.wm.shell.shared.GroupedRecentTaskInfo.CREATOR
-import com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_FREEFORM
-import com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_SINGLE
-import com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_SPLIT
+import com.android.wm.shell.shared.GroupedTaskInfo
+import com.android.wm.shell.shared.GroupedTaskInfo.TYPE_FREEFORM
+import com.android.wm.shell.shared.GroupedTaskInfo.TYPE_FULLSCREEN
+import com.android.wm.shell.shared.GroupedTaskInfo.TYPE_SPLIT
 import com.android.wm.shell.shared.split.SplitBounds
 import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50
 import com.google.common.truth.Correspondence
@@ -39,15 +39,15 @@
 import org.mockito.Mockito.mock
 
 /**
- * Tests for [GroupedRecentTaskInfo]
+ * Tests for [GroupedTaskInfo]
  */
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
-class GroupedRecentTaskInfoTest : ShellTestCase() {
+class GroupedTaskInfoTest : ShellTestCase() {
 
     @Test
     fun testSingleTask_hasCorrectType() {
-        assertThat(singleTaskGroupInfo().type).isEqualTo(TYPE_SINGLE)
+        assertThat(singleTaskGroupInfo().type).isEqualTo(TYPE_FULLSCREEN)
     }
 
     @Test
@@ -117,8 +117,9 @@
         recentTaskInfo.writeToParcel(parcel, 0)
         parcel.setDataPosition(0)
         // Read the object back from the parcel
-        val recentTaskInfoParcel = CREATOR.createFromParcel(parcel)
-        assertThat(recentTaskInfoParcel.type).isEqualTo(TYPE_SINGLE)
+        val recentTaskInfoParcel: GroupedTaskInfo =
+            GroupedTaskInfo.CREATOR.createFromParcel(parcel)
+        assertThat(recentTaskInfoParcel.type).isEqualTo(TYPE_FULLSCREEN)
         assertThat(recentTaskInfoParcel.taskInfo1.taskId).isEqualTo(1)
         assertThat(recentTaskInfoParcel.taskInfo2).isNull()
     }
@@ -130,7 +131,8 @@
         recentTaskInfo.writeToParcel(parcel, 0)
         parcel.setDataPosition(0)
         // Read the object back from the parcel
-        val recentTaskInfoParcel = CREATOR.createFromParcel(parcel)
+        val recentTaskInfoParcel: GroupedTaskInfo =
+            GroupedTaskInfo.CREATOR.createFromParcel(parcel)
         assertThat(recentTaskInfoParcel.type).isEqualTo(TYPE_SPLIT)
         assertThat(recentTaskInfoParcel.taskInfo1.taskId).isEqualTo(1)
         assertThat(recentTaskInfoParcel.taskInfo2).isNotNull()
@@ -146,11 +148,12 @@
         recentTaskInfo.writeToParcel(parcel, 0)
         parcel.setDataPosition(0)
         // Read the object back from the parcel
-        val recentTaskInfoParcel = CREATOR.createFromParcel(parcel)
+        val recentTaskInfoParcel: GroupedTaskInfo =
+            GroupedTaskInfo.CREATOR.createFromParcel(parcel)
         assertThat(recentTaskInfoParcel.type).isEqualTo(TYPE_FREEFORM)
         assertThat(recentTaskInfoParcel.taskInfoList).hasSize(3)
         // Only compare task ids
-        val taskIdComparator = Correspondence.transforming<ActivityManager.RecentTaskInfo, Int>(
+        val taskIdComparator = Correspondence.transforming<TaskInfo, Int>(
             { it?.taskId }, "has taskId of"
         )
         assertThat(recentTaskInfoParcel.taskInfoList).comparingElementsUsing(taskIdComparator)
@@ -167,7 +170,8 @@
         parcel.setDataPosition(0)
 
         // Read the object back from the parcel
-        val recentTaskInfoParcel = CREATOR.createFromParcel(parcel)
+        val recentTaskInfoParcel: GroupedTaskInfo =
+            GroupedTaskInfo.CREATOR.createFromParcel(parcel)
         assertThat(recentTaskInfoParcel.type).isEqualTo(TYPE_FREEFORM)
         assertThat(recentTaskInfoParcel.minimizedTaskIds).isEqualTo(arrayOf(2).toIntArray())
     }
@@ -177,24 +181,24 @@
         token = WindowContainerToken(mock(IWindowContainerToken::class.java))
     }
 
-    private fun singleTaskGroupInfo(): GroupedRecentTaskInfo {
+    private fun singleTaskGroupInfo(): GroupedTaskInfo {
         val task = createTaskInfo(id = 1)
-        return GroupedRecentTaskInfo.forSingleTask(task)
+        return GroupedTaskInfo.forFullscreenTasks(task)
     }
 
-    private fun splitTasksGroupInfo(): GroupedRecentTaskInfo {
+    private fun splitTasksGroupInfo(): GroupedTaskInfo {
         val task1 = createTaskInfo(id = 1)
         val task2 = createTaskInfo(id = 2)
         val splitBounds = SplitBounds(Rect(), Rect(), 1, 2, SNAP_TO_2_50_50)
-        return GroupedRecentTaskInfo.forSplitTasks(task1, task2, splitBounds)
+        return GroupedTaskInfo.forSplitTasks(task1, task2, splitBounds)
     }
 
     private fun freeformTasksGroupInfo(
         freeformTaskIds: Array<Int>,
         minimizedTaskIds: Array<Int> = emptyArray()
-    ): GroupedRecentTaskInfo {
-        return GroupedRecentTaskInfo.forFreeformTasks(
-            freeformTaskIds.map { createTaskInfo(it) }.toTypedArray(),
+    ): GroupedTaskInfo {
+        return GroupedTaskInfo.forFreeformTasks(
+            freeformTaskIds.map { createTaskInfo(it) }.toList(),
             minimizedTaskIds.toSet())
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index 9b73d53..dede583 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -46,6 +46,7 @@
 import static java.lang.Integer.MAX_VALUE;
 
 import android.app.ActivityManager;
+import android.app.ActivityManager.RecentTaskInfo;
 import android.app.ActivityTaskManager;
 import android.app.KeyguardManager;
 import android.content.ComponentName;
@@ -71,7 +72,7 @@
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.desktopmode.DesktopRepository;
-import com.android.wm.shell.shared.GroupedRecentTaskInfo;
+import com.android.wm.shell.shared.GroupedTaskInfo;
 import com.android.wm.shell.shared.ShellSharedConstants;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.shared.split.SplitBounds;
@@ -193,8 +194,8 @@
 
     @Test
     public void testAddRemoveSplitNotifyChange() {
-        ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
-        ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
+        RecentTaskInfo t1 = makeTaskInfo(1);
+        RecentTaskInfo t2 = makeTaskInfo(2);
         setRawList(t1, t2);
 
         mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, mock(SplitBounds.class));
@@ -207,8 +208,8 @@
 
     @Test
     public void testAddSameSplitBoundsInfoSkipNotifyChange() {
-        ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
-        ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
+        RecentTaskInfo t1 = makeTaskInfo(1);
+        RecentTaskInfo t2 = makeTaskInfo(2);
         setRawList(t1, t2);
 
         // Verify only one update if the split info is the same
@@ -223,13 +224,13 @@
 
     @Test
     public void testGetRecentTasks() {
-        ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
-        ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
-        ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
+        RecentTaskInfo t1 = makeTaskInfo(1);
+        RecentTaskInfo t2 = makeTaskInfo(2);
+        RecentTaskInfo t3 = makeTaskInfo(3);
         setRawList(t1, t2, t3);
 
-        ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
-                MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
+        ArrayList<GroupedTaskInfo> recentTasks =
+                mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
         assertGroupedTasksListEquals(recentTasks,
                 t1.taskId, -1,
                 t2.taskId, -1,
@@ -238,12 +239,12 @@
 
     @Test
     public void testGetRecentTasks_withPairs() {
-        ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
-        ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
-        ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
-        ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4);
-        ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5);
-        ActivityManager.RecentTaskInfo t6 = makeTaskInfo(6);
+        RecentTaskInfo t1 = makeTaskInfo(1);
+        RecentTaskInfo t2 = makeTaskInfo(2);
+        RecentTaskInfo t3 = makeTaskInfo(3);
+        RecentTaskInfo t4 = makeTaskInfo(4);
+        RecentTaskInfo t5 = makeTaskInfo(5);
+        RecentTaskInfo t6 = makeTaskInfo(6);
         setRawList(t1, t2, t3, t4, t5, t6);
 
         // Mark a couple pairs [t2, t4], [t3, t5]
@@ -255,8 +256,8 @@
         mRecentTasksController.addSplitPair(t2.taskId, t4.taskId, pair1Bounds);
         mRecentTasksController.addSplitPair(t3.taskId, t5.taskId, pair2Bounds);
 
-        ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
-                MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
+        ArrayList<GroupedTaskInfo> recentTasks =
+                mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
         assertGroupedTasksListEquals(recentTasks,
                 t1.taskId, -1,
                 t2.taskId, t4.taskId,
@@ -267,14 +268,14 @@
     @Test
     public void testGetRecentTasks_ReturnsRecentTasksAsynchronously() {
         @SuppressWarnings("unchecked")
-        final List<GroupedRecentTaskInfo>[] recentTasks = new List[1];
-        Consumer<List<GroupedRecentTaskInfo>> consumer = argument -> recentTasks[0] = argument;
-        ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
-        ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
-        ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
-        ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4);
-        ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5);
-        ActivityManager.RecentTaskInfo t6 = makeTaskInfo(6);
+        final List<GroupedTaskInfo>[] recentTasks = new List[1];
+        Consumer<List<GroupedTaskInfo>> consumer = argument -> recentTasks[0] = argument;
+        RecentTaskInfo t1 = makeTaskInfo(1);
+        RecentTaskInfo t2 = makeTaskInfo(2);
+        RecentTaskInfo t3 = makeTaskInfo(3);
+        RecentTaskInfo t4 = makeTaskInfo(4);
+        RecentTaskInfo t5 = makeTaskInfo(5);
+        RecentTaskInfo t6 = makeTaskInfo(6);
         setRawList(t1, t2, t3, t4, t5, t6);
 
         // Mark a couple pairs [t2, t4], [t3, t5]
@@ -287,7 +288,8 @@
         mRecentTasksController.addSplitPair(t3.taskId, t5.taskId, pair2Bounds);
 
         mRecentTasksController.asRecentTasks()
-                .getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0, Runnable::run, consumer);
+                .getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0, Runnable::run,
+                        consumer);
         mMainExecutor.flushAll();
 
         assertGroupedTasksListEquals(recentTasks[0],
@@ -299,28 +301,28 @@
 
     @Test
     public void testGetRecentTasks_hasActiveDesktopTasks_proto2Enabled_groupFreeformTasks() {
-        ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
-        ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
-        ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
-        ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4);
+        RecentTaskInfo t1 = makeTaskInfo(1);
+        RecentTaskInfo t2 = makeTaskInfo(2);
+        RecentTaskInfo t3 = makeTaskInfo(3);
+        RecentTaskInfo t4 = makeTaskInfo(4);
         setRawList(t1, t2, t3, t4);
 
         when(mDesktopRepository.isActiveTask(1)).thenReturn(true);
         when(mDesktopRepository.isActiveTask(3)).thenReturn(true);
 
-        ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
-                MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
+        ArrayList<GroupedTaskInfo> recentTasks =
+                mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
 
         // 2 freeform tasks should be grouped into one, 3 total recents entries
         assertEquals(3, recentTasks.size());
-        GroupedRecentTaskInfo freeformGroup = recentTasks.get(0);
-        GroupedRecentTaskInfo singleGroup1 = recentTasks.get(1);
-        GroupedRecentTaskInfo singleGroup2 = recentTasks.get(2);
+        GroupedTaskInfo freeformGroup = recentTasks.get(0);
+        GroupedTaskInfo singleGroup1 = recentTasks.get(1);
+        GroupedTaskInfo singleGroup2 = recentTasks.get(2);
 
         // Check that groups have expected types
-        assertEquals(GroupedRecentTaskInfo.TYPE_FREEFORM, freeformGroup.getType());
-        assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup1.getType());
-        assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup2.getType());
+        assertEquals(GroupedTaskInfo.TYPE_FREEFORM, freeformGroup.getType());
+        assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, singleGroup1.getType());
+        assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, singleGroup2.getType());
 
         // Check freeform group entries
         assertEquals(t1, freeformGroup.getTaskInfoList().get(0));
@@ -333,11 +335,11 @@
 
     @Test
     public void testGetRecentTasks_hasActiveDesktopTasks_proto2Enabled_freeformTaskOrder() {
-        ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
-        ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
-        ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
-        ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4);
-        ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5);
+        RecentTaskInfo t1 = makeTaskInfo(1);
+        RecentTaskInfo t2 = makeTaskInfo(2);
+        RecentTaskInfo t3 = makeTaskInfo(3);
+        RecentTaskInfo t4 = makeTaskInfo(4);
+        RecentTaskInfo t5 = makeTaskInfo(5);
         setRawList(t1, t2, t3, t4, t5);
 
         SplitBounds pair1Bounds =
@@ -347,19 +349,19 @@
         when(mDesktopRepository.isActiveTask(3)).thenReturn(true);
         when(mDesktopRepository.isActiveTask(5)).thenReturn(true);
 
-        ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
-                MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
+        ArrayList<GroupedTaskInfo> recentTasks =
+                mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
 
         // 2 split screen tasks grouped, 2 freeform tasks grouped, 3 total recents entries
         assertEquals(3, recentTasks.size());
-        GroupedRecentTaskInfo splitGroup = recentTasks.get(0);
-        GroupedRecentTaskInfo freeformGroup = recentTasks.get(1);
-        GroupedRecentTaskInfo singleGroup = recentTasks.get(2);
+        GroupedTaskInfo splitGroup = recentTasks.get(0);
+        GroupedTaskInfo freeformGroup = recentTasks.get(1);
+        GroupedTaskInfo singleGroup = recentTasks.get(2);
 
         // Check that groups have expected types
-        assertEquals(GroupedRecentTaskInfo.TYPE_SPLIT, splitGroup.getType());
-        assertEquals(GroupedRecentTaskInfo.TYPE_FREEFORM, freeformGroup.getType());
-        assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup.getType());
+        assertEquals(GroupedTaskInfo.TYPE_SPLIT, splitGroup.getType());
+        assertEquals(GroupedTaskInfo.TYPE_FREEFORM, freeformGroup.getType());
+        assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, singleGroup.getType());
 
         // Check freeform group entries
         assertEquals(t3, freeformGroup.getTaskInfoList().get(0));
@@ -378,24 +380,24 @@
         ExtendedMockito.doReturn(false)
                 .when(() -> DesktopModeStatus.canEnterDesktopMode(any()));
 
-        ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
-        ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
-        ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
-        ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4);
+        RecentTaskInfo t1 = makeTaskInfo(1);
+        RecentTaskInfo t2 = makeTaskInfo(2);
+        RecentTaskInfo t3 = makeTaskInfo(3);
+        RecentTaskInfo t4 = makeTaskInfo(4);
         setRawList(t1, t2, t3, t4);
 
         when(mDesktopRepository.isActiveTask(1)).thenReturn(true);
         when(mDesktopRepository.isActiveTask(3)).thenReturn(true);
 
-        ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
-                MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
+        ArrayList<GroupedTaskInfo> recentTasks =
+                mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
 
         // Expect no grouping of tasks
         assertEquals(4, recentTasks.size());
-        assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, recentTasks.get(0).getType());
-        assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, recentTasks.get(1).getType());
-        assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, recentTasks.get(2).getType());
-        assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, recentTasks.get(3).getType());
+        assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, recentTasks.get(0).getType());
+        assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, recentTasks.get(1).getType());
+        assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, recentTasks.get(2).getType());
+        assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, recentTasks.get(3).getType());
 
         assertEquals(t1, recentTasks.get(0).getTaskInfo1());
         assertEquals(t2, recentTasks.get(1).getTaskInfo1());
@@ -405,11 +407,11 @@
 
     @Test
     public void testGetRecentTasks_proto2Enabled_includesMinimizedFreeformTasks() {
-        ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
-        ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
-        ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
-        ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4);
-        ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5);
+        RecentTaskInfo t1 = makeTaskInfo(1);
+        RecentTaskInfo t2 = makeTaskInfo(2);
+        RecentTaskInfo t3 = makeTaskInfo(3);
+        RecentTaskInfo t4 = makeTaskInfo(4);
+        RecentTaskInfo t5 = makeTaskInfo(5);
         setRawList(t1, t2, t3, t4, t5);
 
         when(mDesktopRepository.isActiveTask(1)).thenReturn(true);
@@ -417,19 +419,19 @@
         when(mDesktopRepository.isActiveTask(5)).thenReturn(true);
         when(mDesktopRepository.isMinimizedTask(3)).thenReturn(true);
 
-        ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
-                MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
+        ArrayList<GroupedTaskInfo> recentTasks =
+                mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
 
         // 3 freeform tasks should be grouped into one, 2 single tasks, 3 total recents entries
         assertEquals(3, recentTasks.size());
-        GroupedRecentTaskInfo freeformGroup = recentTasks.get(0);
-        GroupedRecentTaskInfo singleGroup1 = recentTasks.get(1);
-        GroupedRecentTaskInfo singleGroup2 = recentTasks.get(2);
+        GroupedTaskInfo freeformGroup = recentTasks.get(0);
+        GroupedTaskInfo singleGroup1 = recentTasks.get(1);
+        GroupedTaskInfo singleGroup2 = recentTasks.get(2);
 
         // Check that groups have expected types
-        assertEquals(GroupedRecentTaskInfo.TYPE_FREEFORM, freeformGroup.getType());
-        assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup1.getType());
-        assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup2.getType());
+        assertEquals(GroupedTaskInfo.TYPE_FREEFORM, freeformGroup.getType());
+        assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, singleGroup1.getType());
+        assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, singleGroup2.getType());
 
         // Check freeform group entries
         assertEquals(3, freeformGroup.getTaskInfoList().size());
@@ -445,8 +447,8 @@
     @Test
     @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE)
     public void testGetRecentTasks_hasDesktopTasks_persistenceEnabled_freeformTaskHaveBoundsSet() {
-        ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
-        ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
+        RecentTaskInfo t1 = makeTaskInfo(1);
+        RecentTaskInfo t2 = makeTaskInfo(2);
 
         t1.lastNonFullscreenBounds = new Rect(100, 200, 300, 400);
         t2.lastNonFullscreenBounds = new Rect(150, 250, 350, 450);
@@ -455,11 +457,11 @@
         when(mDesktopRepository.isActiveTask(1)).thenReturn(true);
         when(mDesktopRepository.isActiveTask(2)).thenReturn(true);
 
-        ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
-                MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
+        ArrayList<GroupedTaskInfo> recentTasks =
+                mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
 
         assertEquals(1, recentTasks.size());
-        GroupedRecentTaskInfo freeformGroup = recentTasks.get(0);
+        GroupedTaskInfo freeformGroup = recentTasks.get(0);
 
         // Check bounds
         assertEquals(t1.lastNonFullscreenBounds, freeformGroup.getTaskInfoList().get(
@@ -478,9 +480,9 @@
 
     @Test
     public void testRemovedTaskRemovesSplit() {
-        ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
-        ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
-        ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
+        RecentTaskInfo t1 = makeTaskInfo(1);
+        RecentTaskInfo t2 = makeTaskInfo(2);
+        RecentTaskInfo t3 = makeTaskInfo(3);
         setRawList(t1, t2, t3);
 
         // Add a pair
@@ -500,7 +502,7 @@
 
     @Test
     public void testTaskWindowingModeChangedNotifiesChange() {
-        ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
+        RecentTaskInfo t1 = makeTaskInfo(1);
         setRawList(t1);
 
         // Remove one of the tasks and ensure the pair is removed
@@ -607,7 +609,8 @@
 
         mRecentTasksControllerReal.onTaskMovedToFrontThroughTransition(taskInfo);
 
-        verify(mRecentTasksListener).onTaskMovedToFront(taskInfo);
+        GroupedTaskInfo runningTask = GroupedTaskInfo.forFullscreenTasks(taskInfo);
+        verify(mRecentTasksListener).onTaskMovedToFront(eq(new GroupedTaskInfo[] { runningTask }));
     }
 
     @Test
@@ -656,8 +659,8 @@
     /**
      * Helper to create a task with a given task id.
      */
-    private ActivityManager.RecentTaskInfo makeTaskInfo(int taskId) {
-        ActivityManager.RecentTaskInfo info = new ActivityManager.RecentTaskInfo();
+    private RecentTaskInfo makeTaskInfo(int taskId) {
+        RecentTaskInfo info = new RecentTaskInfo();
         info.taskId = taskId;
         info.lastNonFullscreenBounds = new Rect();
         return info;
@@ -676,10 +679,10 @@
     /**
      * Helper to set the raw task list on the controller.
      */
-    private ArrayList<ActivityManager.RecentTaskInfo> setRawList(
-            ActivityManager.RecentTaskInfo... tasks) {
-        ArrayList<ActivityManager.RecentTaskInfo> rawList = new ArrayList<>();
-        for (ActivityManager.RecentTaskInfo task : tasks) {
+    private ArrayList<RecentTaskInfo> setRawList(
+            RecentTaskInfo... tasks) {
+        ArrayList<RecentTaskInfo> rawList = new ArrayList<>();
+        for (RecentTaskInfo task : tasks) {
             rawList.add(task);
         }
         doReturn(rawList).when(mActivityTaskManager).getRecentTasks(anyInt(), anyInt(),
@@ -693,11 +696,11 @@
      * @param expectedTaskIds list of task ids that map to the flattened task ids of the tasks in
      *                        the grouped task list
      */
-    private void assertGroupedTasksListEquals(List<GroupedRecentTaskInfo> recentTasks,
+    private void assertGroupedTasksListEquals(List<GroupedTaskInfo> recentTasks,
             int... expectedTaskIds) {
         int[] flattenedTaskIds = new int[recentTasks.size() * 2];
         for (int i = 0; i < recentTasks.size(); i++) {
-            GroupedRecentTaskInfo pair = recentTasks.get(i);
+            GroupedTaskInfo pair = recentTasks.get(i);
             int taskId1 = pair.getTaskInfo1().taskId;
             flattenedTaskIds[2 * i] = taskId1;
             flattenedTaskIds[2 * i + 1] = pair.getTaskInfo2() != null
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index ef3af8e..966651f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -217,7 +217,6 @@
         // Put the same component to the top running task
         ActivityManager.RunningTaskInfo topRunningTask =
                 createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
-        doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask();
         doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask(any());
 
         mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
@@ -238,7 +237,6 @@
         // Put the same component to the top running task
         ActivityManager.RunningTaskInfo topRunningTask =
                 createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
-        doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask();
         doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask(any());
         // Put the same component into a task in the background
         ActivityManager.RecentTaskInfo sameTaskInfo = new ActivityManager.RecentTaskInfo();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index 6cde056..2442a55 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -19,6 +19,7 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
@@ -363,6 +364,25 @@
     }
 
     @Test
+    public void testTransitionFilterWindowingMode() {
+        TransitionFilter filter = new TransitionFilter();
+        filter.mRequirements =
+                new TransitionFilter.Requirement[]{new TransitionFilter.Requirement()};
+        filter.mRequirements[0].mWindowingMode = WINDOWING_MODE_FREEFORM;
+        filter.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT};
+
+        final TransitionInfo fullscreenStd = new TransitionInfoBuilder(TRANSIT_OPEN)
+                .addChange(TRANSIT_OPEN, createTaskInfo(
+                        1, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD)).build();
+        assertFalse(filter.matches(fullscreenStd));
+
+        final TransitionInfo freeformStd = new TransitionInfoBuilder(TRANSIT_OPEN)
+                .addChange(TRANSIT_OPEN, createTaskInfo(
+                        1, WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD)).build();
+        assertTrue(filter.matches(freeformStd));
+    }
+
+    @Test
     public void testTransitionFilterMultiRequirement() {
         // filter that requires at-least one opening and one closing app
         TransitionFilter filter = new TransitionFilter();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index 03aab18..956100d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -478,25 +478,10 @@
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
-    fun testDecorationIsCreatedForTopTranslucentActivitiesWithStyleFloating() {
+    fun testDecorationIsNotCreatedForTopTranslucentActivities() {
         val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN).apply {
             isTopActivityTransparent = true
-            isTopActivityStyleFloating = true
-            numActivities = 1
-        }
-        doReturn(true).`when` { DesktopModeStatus.canEnterDesktopMode(any()) }
-        setUpMockDecorationsForTasks(task)
-
-        onTaskOpening(task)
-        assertTrue(windowDecorByTaskIdSpy.contains(task.taskId))
-    }
-
-    @Test
-    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
-    fun testDecorationIsNotCreatedForTopTranslucentActivitiesWithoutStyleFloating() {
-        val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN).apply {
-            isTopActivityTransparent = true
-            isTopActivityStyleFloating = false
+            isTopActivityNoDisplay = false
             numActivities = 1
         }
         onTaskOpening(task)
@@ -507,13 +492,14 @@
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
     fun testDecorationIsNotCreatedForSystemUIActivities() {
-        val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN)
-
         // Set task as systemUI package
         val systemUIPackageName = context.resources.getString(
             com.android.internal.R.string.config_systemUi)
         val baseComponent = ComponentName(systemUIPackageName, /* class */ "")
-        task.baseActivity = baseComponent
+        val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN).apply {
+            baseActivity = baseComponent
+            isTopActivityNoDisplay = false
+        }
 
         onTaskOpening(task)
 
@@ -1321,6 +1307,48 @@
         verify(decor).closeMaximizeMenu()
     }
 
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS)
+    fun testOnTaskInfoChanged_enableShellTransitionsFlag() {
+        val task = createTask(
+            windowingMode = WINDOWING_MODE_FREEFORM
+        )
+        val taskSurface = SurfaceControl()
+        val decoration = setUpMockDecorationForTask(task)
+
+        onTaskOpening(task, taskSurface)
+        assertTrue(windowDecorByTaskIdSpy.contains(task.taskId))
+
+        decoration.mHasGlobalFocus = true
+        desktopModeWindowDecorViewModel.onTaskInfoChanged(task)
+        verify(decoration).relayout(task, true)
+
+        decoration.mHasGlobalFocus = false
+        desktopModeWindowDecorViewModel.onTaskInfoChanged(task)
+        verify(decoration).relayout(task, false)
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS)
+    fun testOnTaskInfoChanged_disableShellTransitionsFlag() {
+        val task = createTask(
+            windowingMode = WINDOWING_MODE_FREEFORM
+        )
+        val taskSurface = SurfaceControl()
+        val decoration = setUpMockDecorationForTask(task)
+
+        onTaskOpening(task, taskSurface)
+        assertTrue(windowDecorByTaskIdSpy.contains(task.taskId))
+
+        task.isFocused = true
+        desktopModeWindowDecorViewModel.onTaskInfoChanged(task)
+        verify(decoration).relayout(task, true)
+
+        task.isFocused = false
+        desktopModeWindowDecorViewModel.onTaskInfoChanged(task)
+        verify(decoration).relayout(task, false)
+    }
+
     private fun createOpenTaskDecoration(
         @WindowingMode windowingMode: Int,
         taskSurface: SurfaceControl = SurfaceControl(),
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt
index 80ad1df..d44c015 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt
@@ -25,6 +25,7 @@
 import com.android.wm.shell.common.DisplayController
 import com.android.wm.shell.common.SyncTransactionQueue
 import com.android.wm.shell.desktopmode.DesktopRepository
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger
 import com.android.wm.shell.desktopmode.DesktopTasksController
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
 import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator
@@ -52,6 +53,7 @@
     private val transitionsMock: Transitions = mock()
     private val shellTaskOrganizerMock: ShellTaskOrganizer = mock()
     private val desktopRepository: DesktopRepository = mock()
+    private val desktopModeEventLogger: DesktopModeEventLogger = mock()
     private val toggleResizeDesktopTaskTransitionHandlerMock:
         ToggleResizeDesktopTaskTransitionHandler =
         mock()
@@ -74,6 +76,7 @@
                 toggleResizeDesktopTaskTransitionHandlerMock,
                 returnToDragStartAnimatorMock,
                 desktopRepository,
+                desktopModeEventLogger,
             )
         whenever(contextMock.createContextAsUser(any(), any())).thenReturn(contextMock)
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt
index f371f52..057d8fa3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt
@@ -21,6 +21,7 @@
 import android.graphics.Rect
 import android.os.IBinder
 import android.testing.AndroidTestingRunner
+import android.view.MotionEvent
 import android.view.SurfaceControl
 import android.view.WindowManager.TRANSIT_CHANGE
 import android.view.WindowManager.TRANSIT_TO_FRONT
@@ -33,6 +34,8 @@
 import com.android.wm.shell.common.DisplayController
 import com.android.wm.shell.common.DisplayLayout
 import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
 import com.android.wm.shell.desktopmode.DesktopRepository
 import com.android.wm.shell.desktopmode.DesktopTasksController
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
@@ -91,7 +94,9 @@
     private val info: TransitionInfo = mock()
     private val finishCallback: Transitions.TransitionFinishCallback = mock()
     private val desktopRepository: DesktopRepository = mock()
+    private val desktopModeEventLogger: DesktopModeEventLogger = mock()
     private val desktopTilingDividerWindowManager: DesktopTilingDividerWindowManager = mock()
+    private val motionEvent: MotionEvent = mock()
     private lateinit var tilingDecoration: DesktopTilingWindowDecoration
 
     private val split_divider_width = 10
@@ -112,6 +117,7 @@
                 toggleResizeDesktopTaskTransitionHandler,
                 returnToDragStartAnimator,
                 desktopRepository,
+                desktopModeEventLogger,
             )
         whenever(context.createContextAsUser(any(), any())).thenReturn(context)
     }
@@ -371,13 +377,13 @@
 
         // End moving, no startTransition because bounds did not change.
         tiledTaskHelper.newBounds.set(BOUNDS)
-        tilingDecoration.onDividerHandleDragEnd(BOUNDS, transaction)
+        tilingDecoration.onDividerHandleDragEnd(BOUNDS, transaction, motionEvent)
         verify(tiledTaskHelper, times(2)).hideVeil()
         verify(transitions, never()).startTransition(any(), any(), any())
 
         // Move then end again with bounds changing to ensure startTransition is called.
         tilingDecoration.onDividerHandleMoved(BOUNDS, transaction)
-        tilingDecoration.onDividerHandleDragEnd(BOUNDS, transaction)
+        tilingDecoration.onDividerHandleDragEnd(BOUNDS, transaction, motionEvent)
         verify(transitions, times(1))
             .startTransition(eq(TRANSIT_CHANGE), any(), eq(tilingDecoration))
         // No hide veil until start animation is called.
@@ -389,6 +395,64 @@
     }
 
     @Test
+    fun tiledTasksResizedUsingDividerHandle_shouldLogResizingEvents() {
+        // Setup
+        val task1 = createFreeformTask()
+        val task2 = createFreeformTask()
+        val stableBounds = STABLE_BOUNDS_MOCK
+        whenever(displayController.getDisplayLayout(any())).thenReturn(displayLayout)
+        whenever(displayLayout.getStableBounds(any())).thenAnswer { i ->
+            (i.arguments.first() as Rect).set(stableBounds)
+        }
+        whenever(context.resources).thenReturn(resources)
+        whenever(resources.getDimensionPixelSize(any())).thenReturn(split_divider_width)
+        desktopWindowDecoration.mTaskInfo = task1
+        task1.minWidth = 0
+        task1.minHeight = 0
+        initTiledTaskHelperMock(task1)
+        desktopWindowDecoration.mDecorWindowContext = context
+        whenever(resources.getBoolean(any())).thenReturn(true)
+
+        // Act
+        tilingDecoration.onAppTiled(
+            task1,
+            desktopWindowDecoration,
+            DesktopTasksController.SnapPosition.RIGHT,
+            BOUNDS,
+        )
+        tilingDecoration.onAppTiled(
+            task2,
+            desktopWindowDecoration,
+            DesktopTasksController.SnapPosition.LEFT,
+            BOUNDS,
+        )
+        tilingDecoration.leftTaskResizingHelper = tiledTaskHelper
+        tilingDecoration.rightTaskResizingHelper = tiledTaskHelper
+        tilingDecoration.onDividerHandleDragStart(motionEvent)
+        // Log start event for task1 and task2, but the tasks are the same in
+        // this test, so we verify the same log twice.
+        verify(desktopModeEventLogger, times(2)).logTaskResizingStarted(
+            ResizeTrigger.TILING_DIVIDER,
+            motionEvent,
+            task1,
+            displayController,
+        )
+
+        tilingDecoration.onDividerHandleMoved(BOUNDS, transaction)
+        tilingDecoration.onDividerHandleDragEnd(BOUNDS, transaction, motionEvent)
+        // Log end event for task1 and task2, but the tasks are the same in
+        // this test, so we verify the same log twice.
+        verify(desktopModeEventLogger, times(2)).logTaskResizingEnded(
+            ResizeTrigger.TILING_DIVIDER,
+            motionEvent,
+            task1,
+            BOUNDS.height(),
+            BOUNDS.width(),
+            displayController,
+        )
+    }
+
+    @Test
     fun taskTiled_shouldBeRemoved_whenTileBroken() {
         val task1 = createFreeformTask()
         val stableBounds = STABLE_BOUNDS_MOCK
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/TilingDividerViewTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/TilingDividerViewTest.kt
index c96ce95..734815c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/TilingDividerViewTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/TilingDividerViewTest.kt
@@ -68,7 +68,7 @@
         val downMotionEvent =
             getMotionEvent(downTime, MotionEvent.ACTION_DOWN, x.toFloat(), y.toFloat())
         tilingDividerView.handleMotionEvent(viewMock, downMotionEvent)
-        verify(dividerMoveCallbackMock, times(1)).onDividerMoveStart(any())
+        verify(dividerMoveCallbackMock, times(1)).onDividerMoveStart(any(), any())
 
         whenever(dividerMoveCallbackMock.onDividerMove(any())).thenReturn(true)
         val motionEvent =
@@ -79,7 +79,7 @@
         val upMotionEvent =
             getMotionEvent(downTime, MotionEvent.ACTION_UP, x.toFloat(), y.toFloat())
         tilingDividerView.handleMotionEvent(viewMock, upMotionEvent)
-        verify(dividerMoveCallbackMock, times(1)).onDividerMovedEnd(any())
+        verify(dividerMoveCallbackMock, times(1)).onDividerMovedEnd(any(), any())
     }
 
     @Test
@@ -92,12 +92,12 @@
         val downMotionEvent =
             getMotionEvent(downTime, MotionEvent.ACTION_DOWN, x.toFloat(), y.toFloat())
         tilingDividerView.handleMotionEvent(viewMock, downMotionEvent)
-        verify(dividerMoveCallbackMock, times(1)).onDividerMoveStart(any())
+        verify(dividerMoveCallbackMock, times(1)).onDividerMoveStart(any(), any())
 
         val upMotionEvent =
             getMotionEvent(downTime, MotionEvent.ACTION_UP, x.toFloat(), y.toFloat())
         tilingDividerView.handleMotionEvent(viewMock, upMotionEvent)
-        verify(dividerMoveCallbackMock, never()).onDividerMovedEnd(any())
+        verify(dividerMoveCallbackMock, never()).onDividerMovedEnd(any(), any())
     }
 
     private fun getMotionEvent(eventTime: Long, action: Int, x: Float, y: Float): MotionEvent {
diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp
index 9cd6e25..e5fb755 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -49,6 +49,7 @@
     minikinPaint.fontStyle = resolvedFace->fStyle;
     minikinPaint.fontFeatureSettings = paint->getFontFeatureSettings();
     minikinPaint.fontVariationSettings = paint->getFontVariationOverride();
+    minikinPaint.verticalText = paint->isVerticalText();
 
     const std::optional<minikin::FamilyVariant>& familyVariant = paint->getFamilyVariant();
     if (familyVariant.has_value()) {
diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h
index 7eb849f..594ea31 100644
--- a/libs/hwui/hwui/Paint.h
+++ b/libs/hwui/hwui/Paint.h
@@ -158,6 +158,7 @@
     SkSamplingOptions sampling() const {
         return SkSamplingOptions(this->filterMode());
     }
+    bool isVerticalText() const { return mVerticalText; }
 
     void setVariationOverride(minikin::VariationSettings&& varSettings) {
         mFontVariationOverride = std::move(varSettings);
@@ -202,6 +203,7 @@
     bool mUnderline = false;
     bool mDevKern = false;
     minikin::RunFlag mRunFlag = minikin::RunFlag::NONE;
+    bool mVerticalText = false;
 };
 
 }  // namespace android
diff --git a/libs/hwui/hwui/PaintImpl.cpp b/libs/hwui/hwui/PaintImpl.cpp
index 6dfcedc..fa5325d 100644
--- a/libs/hwui/hwui/PaintImpl.cpp
+++ b/libs/hwui/hwui/PaintImpl.cpp
@@ -49,7 +49,8 @@
         , mStrikeThru(paint.mStrikeThru)
         , mUnderline(paint.mUnderline)
         , mDevKern(paint.mDevKern)
-        , mRunFlag(paint.mRunFlag) {}
+        , mRunFlag(paint.mRunFlag)
+        , mVerticalText(paint.mVerticalText) {}
 
 Paint::~Paint() {}
 
@@ -71,6 +72,7 @@
     mUnderline = other.mUnderline;
     mDevKern = other.mDevKern;
     mRunFlag = other.mRunFlag;
+    mVerticalText = other.mVerticalText;
     return *this;
 }
 
@@ -83,7 +85,8 @@
            a.mFamilyVariant == b.mFamilyVariant && a.mHyphenEdit == b.mHyphenEdit &&
            a.mTypeface == b.mTypeface && a.mAlign == b.mAlign &&
            a.mFilterBitmap == b.mFilterBitmap && a.mStrikeThru == b.mStrikeThru &&
-           a.mUnderline == b.mUnderline && a.mDevKern == b.mDevKern && a.mRunFlag == b.mRunFlag;
+           a.mUnderline == b.mUnderline && a.mDevKern == b.mDevKern && a.mRunFlag == b.mRunFlag &&
+           a.mVerticalText == b.mVerticalText;
 }
 
 void Paint::reset() {
@@ -97,6 +100,7 @@
     mStrikeThru = false;
     mUnderline = false;
     mDevKern = false;
+    mVerticalText = false;
     mRunFlag = minikin::RunFlag::NONE;
 }
 
@@ -135,6 +139,7 @@
 // flags related to minikin::Paint
 static const uint32_t sUnderlineFlag    = 0x08;
 static const uint32_t sStrikeThruFlag   = 0x10;
+static const uint32_t sVerticalTextFlag = 0x1000;
 static const uint32_t sTextRunLeftEdge = 0x2000;
 static const uint32_t sTextRunRightEdge = 0x4000;
 // flags no longer supported on native side (but mirrored for compatibility)
@@ -190,6 +195,7 @@
     flags |= -(int)mUnderline    & sUnderlineFlag;
     flags |= -(int)mDevKern      & sDevKernFlag;
     flags |= -(int)mFilterBitmap & sFilterBitmapFlag;
+    flags |= -(int)mVerticalText & sVerticalTextFlag;
     if (mRunFlag & minikin::RunFlag::LEFT_EDGE) {
         flags |= sTextRunLeftEdge;
     }
@@ -206,6 +212,7 @@
     mUnderline    = (flags & sUnderlineFlag) != 0;
     mDevKern      = (flags & sDevKernFlag) != 0;
     mFilterBitmap = (flags & sFilterBitmapFlag) != 0;
+    mVerticalText = (flags & sVerticalTextFlag) != 0;
 
     std::underlying_type<minikin::RunFlag>::type rawFlag = minikin::RunFlag::NONE;
     if (flags & sTextRunLeftEdge) {
diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java
index 55c8ed5..530d48d 100644
--- a/media/java/android/media/ImageReader.java
+++ b/media/java/android/media/ImageReader.java
@@ -40,6 +40,8 @@
 import android.os.Trace;
 import android.view.Surface;
 
+import com.android.internal.camera.flags.Flags;
+
 import dalvik.system.VMRuntime;
 
 import java.io.IOException;
@@ -1208,6 +1210,11 @@
                 default:
                     width = nativeGetWidth();
             }
+            if (Flags.cameraHeifGainmap()) {
+                if (getFormat() == ImageFormat.HEIC_ULTRAHDR){
+                    width = ImageReader.this.getWidth();
+                }
+            }
             return width;
         }
 
@@ -1227,6 +1234,11 @@
                 default:
                     height = nativeGetHeight();
             }
+            if (Flags.cameraHeifGainmap()) {
+                if (getFormat() == ImageFormat.HEIC_ULTRAHDR){
+                    height = ImageReader.this.getHeight();
+                }
+            }
             return height;
         }
 
diff --git a/media/java/android/media/ImageUtils.java b/media/java/android/media/ImageUtils.java
index f4caad7..c767806 100644
--- a/media/java/android/media/ImageUtils.java
+++ b/media/java/android/media/ImageUtils.java
@@ -23,6 +23,8 @@
 import android.util.Log;
 import android.util.Size;
 
+import com.android.internal.camera.flags.Flags;
+
 import libcore.io.Memory;
 
 import java.nio.ByteBuffer;
@@ -44,6 +46,11 @@
      * are used.
      */
     public static int getNumPlanesForFormat(int format) {
+        if (Flags.cameraHeifGainmap()) {
+            if (format == ImageFormat.HEIC_ULTRAHDR) {
+                return 1;
+            }
+        }
         switch (format) {
             case ImageFormat.YV12:
             case ImageFormat.YUV_420_888:
@@ -229,6 +236,11 @@
     public static int getEstimatedNativeAllocBytes(int width, int height, int format,
             int numImages) {
         double estimatedBytePerPixel;
+        if (Flags.cameraHeifGainmap()) {
+            if (format == ImageFormat.HEIC_ULTRAHDR) {
+                estimatedBytePerPixel = 0.3;
+            }
+        }
         switch (format) {
             // 10x compression from RGB_888
             case ImageFormat.JPEG:
@@ -283,6 +295,11 @@
     }
 
     private static Size getEffectivePlaneSizeForImage(Image image, int planeIdx) {
+        if (Flags.cameraHeifGainmap()) {
+            if (image.getFormat() == ImageFormat.HEIC_ULTRAHDR){
+                return new Size(image.getWidth(), image.getHeight());
+            }
+        }
         switch (image.getFormat()) {
             case ImageFormat.YCBCR_P010:
             case ImageFormat.YV12:
diff --git a/media/java/android/media/flags/editing.aconfig b/media/java/android/media/flags/editing.aconfig
index 185f579..0adc478 100644
--- a/media/java/android/media/flags/editing.aconfig
+++ b/media/java/android/media/flags/editing.aconfig
@@ -15,3 +15,10 @@
   description: "Enable B frames for Stagefright recorder."
   bug: "341121900"
 }
+
+flag {
+  name: "muxer_mp4_enable_apv"
+  namespace: "media_solutions"
+  description: "Enable APV support in mp4 writer."
+  bug: "370061501"
+}
diff --git a/media/java/android/media/quality/MediaQualityContract.java b/media/java/android/media/quality/MediaQualityContract.java
index 5cbc81d..472d798 100644
--- a/media/java/android/media/quality/MediaQualityContract.java
+++ b/media/java/android/media/quality/MediaQualityContract.java
@@ -21,11 +21,16 @@
 import android.media.tv.flags.Flags;
 
 /**
+ * The contract between the media quality service and applications. Contains definitions for the
+ * commonly used parameter names.
  * @hide
  */
 @FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW)
 public class MediaQualityContract {
 
+    /**
+     * @hide
+     */
     public interface BaseParameters {
         String PARAMETER_ID = "_id";
         String PARAMETER_NAME = "_name";
@@ -33,13 +38,50 @@
         String PARAMETER_INPUT_ID = "_input_id";
 
     }
-    public static final class PictureQuality implements BaseParameters {
+
+    /**
+     * Parameters picture quality.
+     * @hide
+     */
+    public static final class PictureQuality {
+        /**
+         * The brightness.
+         *
+         * <p>Type: INTEGER
+         */
         public static final String PARAMETER_BRIGHTNESS = "brightness";
+
+        /**
+         * The contrast.
+         *
+         * <p>The ratio between the luminance of the brightest white and the darkest black.
+         * <p>Type: INTEGER
+         */
         public static final String PARAMETER_CONTRAST = "contrast";
+
+        /**
+         * The sharpness.
+         *
+         * <p>Sharpness indicates the clarity of detail.
+         * <p>Type: INTEGER
+         */
         public static final String PARAMETER_SHARPNESS = "sharpness";
+
+        /**
+         * The saturation.
+         *
+         * <p>Saturation indicates the intensity of the color.
+         * <p>Type: INTEGER
+         */
         public static final String PARAMETER_SATURATION = "saturation";
+
+        private PictureQuality() {
+        }
     }
 
+    /**
+     * @hide
+     */
     public static final class SoundQuality implements BaseParameters {
         public static final String PARAMETER_BALANCE = "balance";
         public static final String PARAMETER_BASS = "bass";
diff --git a/media/java/android/media/quality/MediaQualityManager.java b/media/java/android/media/quality/MediaQualityManager.java
index 1237d67..38a2025 100644
--- a/media/java/android/media/quality/MediaQualityManager.java
+++ b/media/java/android/media/quality/MediaQualityManager.java
@@ -34,7 +34,8 @@
 import java.util.concurrent.Executor;
 
 /**
- * Expose TV setting APIs for the application to use
+ * Central system API to the overall media quality, which arbitrates interaction between
+ * applications and media quality service.
  * @hide
  */
 @FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW)
@@ -177,6 +178,7 @@
      * Gets picture profile by given profile ID.
      * @return the corresponding picture profile if available; {@code null} if the ID doesn't
      *         exist or the profile is not accessible to the caller.
+     * @hide
      */
     public PictureProfile getPictureProfileById(long profileId) {
         try {
@@ -187,7 +189,10 @@
     }
 
 
-    /** @SystemApi gets profiles that available to the given package */
+    /**
+     * @SystemApi gets profiles that available to the given package
+     * @hide
+     */
     @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
     public List<PictureProfile> getPictureProfilesByPackage(String packageName) {
         try {
@@ -197,7 +202,10 @@
         }
     }
 
-    /** Gets profiles that available to the caller package */
+    /**
+     * Gets profiles that available to the caller.
+     */
+    @NonNull
     public List<PictureProfile> getAvailablePictureProfiles() {
         try {
             return mService.getAvailablePictureProfiles();
@@ -206,7 +214,10 @@
         }
     }
 
-    /** @SystemApi all stored picture profiles of all packages */
+    /**
+     * @SystemApi all stored picture profiles of all packages
+     * @hide
+     */
     @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
     public List<PictureProfile> getAllPictureProfiles() {
         try {
@@ -221,6 +232,7 @@
      * Creates a picture profile and store it in the system.
      *
      * @return the stored profile with an assigned profile ID.
+     * @hide
      */
     public PictureProfile createPictureProfile(PictureProfile pp) {
         try {
@@ -233,6 +245,7 @@
 
     /**
      * Updates an existing picture profile and store it in the system.
+     * @hide
      */
     public void updatePictureProfile(long profileId, PictureProfile pp) {
         try {
@@ -245,6 +258,7 @@
 
     /**
      * Removes a picture profile from the system.
+     * @hide
      */
     public void removePictureProfile(long profileId) {
         try {
@@ -291,6 +305,7 @@
      * Gets sound profile by given profile ID.
      * @return the corresponding sound profile if available; {@code null} if the ID doesn't
      *         exist or the profile is not accessible to the caller.
+     * @hide
      */
     public SoundProfile getSoundProfileById(long profileId) {
         try {
@@ -301,7 +316,10 @@
     }
 
 
-    /** @SystemApi gets profiles that available to the given package */
+    /**
+     * @SystemApi gets profiles that available to the given package
+     * @hide
+     */
     @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
     public List<SoundProfile> getSoundProfilesByPackage(String packageName) {
         try {
@@ -311,7 +329,10 @@
         }
     }
 
-    /** Gets profiles that available to the caller package */
+    /**
+     * Gets profiles that available to the caller package
+     * @hide
+     */
     public List<SoundProfile> getAvailableSoundProfiles() {
         try {
             return mService.getAvailableSoundProfiles();
@@ -320,7 +341,10 @@
         }
     }
 
-    /** @SystemApi all stored sound profiles of all packages */
+    /**
+     * @SystemApi all stored sound profiles of all packages
+     * @hide
+     */
     @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
     public List<SoundProfile> getAllSoundProfiles() {
         try {
@@ -335,6 +359,7 @@
      * Creates a sound profile and store it in the system.
      *
      * @return the stored profile with an assigned profile ID.
+     * @hide
      */
     public SoundProfile createSoundProfile(SoundProfile sp) {
         try {
@@ -347,6 +372,7 @@
 
     /**
      * Updates an existing sound profile and store it in the system.
+     * @hide
      */
     public void updateSoundProfile(long profileId, SoundProfile sp) {
         try {
@@ -359,6 +385,7 @@
 
     /**
      * Removes a sound profile from the system.
+     * @hide
      */
     public void removeSoundProfile(long profileId) {
         try {
@@ -370,6 +397,7 @@
 
     /**
      * Gets capability information of the given parameters.
+     * @hide
      */
     public List<ParamCapability> getParamCapabilities(List<String> names) {
         try {
@@ -396,6 +424,7 @@
      * different use cases.
      *
      * @param enabled {@code true} to enable, {@code false} to disable.
+     * @hide
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
     public void setAutoPictureQualityEnabled(boolean enabled) {
@@ -408,6 +437,7 @@
 
     /**
      * Returns {@code true} if auto picture quality is enabled; {@code false} otherwise.
+     * @hide
      */
     public boolean isAutoPictureQualityEnabled() {
         try {
@@ -422,6 +452,7 @@
      * <p>Super resolution is a feature to improve resolution.
      *
      * @param enabled {@code true} to enable, {@code false} to disable.
+     * @hide
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
     public void setSuperResolutionEnabled(boolean enabled) {
@@ -434,6 +465,7 @@
 
     /**
      * Returns {@code true} if super resolution is enabled; {@code false} otherwise.
+     * @hide
      */
     public boolean isSuperResolutionEnabled() {
         try {
@@ -449,6 +481,7 @@
      * different use cases.
      *
      * @param enabled {@code true} to enable, {@code false} to disable.
+     * @hide
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
     public void setAutoSoundQualityEnabled(boolean enabled) {
@@ -461,6 +494,7 @@
 
     /**
      * Returns {@code true} if auto sound quality is enabled; {@code false} otherwise.
+     * @hide
      */
     public boolean isAutoSoundQualityEnabled() {
         try {
@@ -507,6 +541,7 @@
      * Set the ambient backlight settings.
      *
      * @param settings The settings to use for the backlight detector.
+     * @hide
      */
     public void setAmbientBacklightSettings(
             @NonNull AmbientBacklightSettings settings) {
@@ -522,6 +557,7 @@
      * Enables or disables the ambient backlight detection.
      *
      * @param enabled {@code true} to enable, {@code false} to disable.
+     * @hide
      */
     public void setAmbientBacklightEnabled(boolean enabled) {
         try {
@@ -700,6 +736,7 @@
     public abstract static class AmbientBacklightCallback {
         /**
          * Called when new ambient backlight event is emitted.
+         * @hide
          */
         public void onAmbientBacklightEvent(AmbientBacklightEvent event) {
         }
diff --git a/media/java/android/media/quality/PictureProfile.java b/media/java/android/media/quality/PictureProfile.java
index 36c49d8..8fb5712 100644
--- a/media/java/android/media/quality/PictureProfile.java
+++ b/media/java/android/media/quality/PictureProfile.java
@@ -17,6 +17,8 @@
 package android.media.quality;
 
 import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.media.tv.TvInputInfo;
 import android.media.tv.flags.Flags;
 import android.os.Bundle;
 import android.os.Parcel;
@@ -24,30 +26,55 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.RequiresPermission;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 /**
+ * Profile for picture quality.
  * @hide
  */
-
 @FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW)
-public class PictureProfile implements Parcelable {
+public final class PictureProfile implements Parcelable {
     @Nullable
-    private Long mId;
+    private String mId;
+    private final int mType;
     @NonNull
     private final String mName;
     @Nullable
     private final String mInputId;
-    @Nullable
+    @NonNull
     private final String mPackageName;
     @NonNull
     private final Bundle mParams;
 
-    protected PictureProfile(Parcel in) {
-        if (in.readByte() == 0) {
-            mId = null;
-        } else {
-            mId = in.readLong();
-        }
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = false, prefix = "TYPE_", value = {
+            TYPE_SYSTEM,
+            TYPE_APPLICATION})
+    public @interface ProfileType {}
+
+    /**
+     * System profile type.
+     *
+     * <p>A profile of system type is managed by the system, and readable to the package define in
+     * {@link #getPackageName()}.
+     */
+    public static final int TYPE_SYSTEM = 1;
+    /**
+     * Application profile type.
+     *
+     * <p>A profile of application type is managed by the package define in
+     * {@link #getPackageName()}.
+     */
+    public static final int TYPE_APPLICATION = 2;
+
+
+    private PictureProfile(@NonNull Parcel in) {
+        mId = in.readString();
+        mType = in.readInt();
         mName = in.readString();
         mInputId = in.readString();
         mPackageName = in.readString();
@@ -55,13 +82,9 @@
     }
 
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        if (mId == null) {
-            dest.writeByte((byte) 0);
-        } else {
-            dest.writeByte((byte) 1);
-            dest.writeLong(mId);
-        }
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(mId);
+        dest.writeInt(mType);
         dest.writeString(mName);
         dest.writeString(mInputId);
         dest.writeString(mPackageName);
@@ -73,6 +96,7 @@
         return 0;
     }
 
+    @NonNull
     public static final Creator<PictureProfile> CREATOR = new Creator<PictureProfile>() {
         @Override
         public PictureProfile createFromParcel(Parcel in) {
@@ -92,95 +116,166 @@
      * @hide
      */
     public PictureProfile(
-            @Nullable Long id,
+            @Nullable String id,
+            int type,
             @NonNull String name,
             @Nullable String inputId,
-            @Nullable String packageName,
+            @NonNull String packageName,
             @NonNull Bundle params) {
         this.mId = id;
+        this.mType = type;
         this.mName = name;
-        com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, name);
         this.mInputId = inputId;
         this.mPackageName = packageName;
         this.mParams = params;
     }
 
+    /**
+     * Gets profile ID.
+     *
+     * <p>A profile ID is a globally unique ID generated and assigned by the system. For profile
+     * objects retrieved from system (e.g {@link MediaQualityManager#getAvailablePictureProfiles()})
+     * this profile ID is non-null; For profiles built locally with {@link Builder}, it's
+     * {@code null}.
+     *
+     * @return the unique profile ID; {@code null} if the profile is built locally with
+     * {@link Builder}.
+     */
     @Nullable
-    public Long getProfileId() {
+    public String getProfileId() {
         return mId;
     }
 
+    /**
+     * Only used by system to assign the ID.
+     * @hide
+     */
+    public void setProfileId(String id) {
+        mId = id;
+    }
+
+    /**
+     * Gets profile type.
+     */
+    @ProfileType
+    public int getProfileType() {
+        return mType;
+    }
+
+    /**
+     * Gets the profile name.
+     */
     @NonNull
     public String getName() {
         return mName;
     }
 
+    /**
+     * Gets the input ID if the profile is for a TV input.
+     *
+     * @return the corresponding TV input ID; {@code null} if the profile is not associated with a
+     * TV input.
+     *
+     * @see TvInputInfo#getId()
+     */
     @Nullable
     public String getInputId() {
         return mInputId;
     }
 
+    /**
+     * Gets the package name of this profile.
+     *
+     * <p>The package name defines the user of a profile. Only this specific package and system app
+     * can access to this profile.
+     *
+     * @return the package name; {@code null} if the profile is built locally using
+     * {@link Builder} and the package is not set.
+     */
     @Nullable
     public String getPackageName() {
         return mPackageName;
     }
+
+    /**
+     * Gets the parameters of this profile.
+     *
+     * <p>The keys of commonly used parameters can be found in
+     * {@link MediaQualityContract.PictureQuality}.
+     */
     @NonNull
     public Bundle getParameters() {
         return new Bundle(mParams);
     }
 
     /**
-     * A builder for {@link PictureProfile}
+     * A builder for {@link PictureProfile}.
+     * @hide
      */
-    public static class Builder {
+    public static final class Builder {
         @Nullable
-        private Long mId;
+        private String mId;
+        private int mType = TYPE_APPLICATION;
         @NonNull
         private String mName;
         @Nullable
         private String mInputId;
-        @Nullable
+        @NonNull
         private String mPackageName;
         @NonNull
         private Bundle mParams;
 
         /**
          * Creates a new Builder.
-         *
-         * @hide
          */
         public Builder(@NonNull String name) {
             mName = name;
-            com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, name);
         }
 
         /**
-         * Copy constructor.
-         *
-         * @hide
+         * Copy constructor of builder.
          */
         public Builder(@NonNull PictureProfile p) {
             mId = null; // ID needs to be reset
+            mType = p.getProfileType();
             mName = p.getName();
             mPackageName = p.getPackageName();
             mInputId = p.getInputId();
+            mParams = p.getParameters();
         }
 
         /* @hide using by MediaQualityService */
 
         /**
-         * Sets profile ID.
-         * @hide using by MediaQualityService
+         * Only used by system to assign the ID.
+         * @hide
          */
         @NonNull
-        public Builder setProfileId(@Nullable Long id) {
+        public Builder setProfileId(@Nullable String id) {
             mId = id;
             return this;
         }
 
         /**
-         * Sets input ID.
+         * Sets profile type.
+         *
+         * @hide @SystemApi
          */
+        @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
+        @NonNull
+        public Builder setProfileType(@ProfileType int value) {
+            mType = value;
+            return this;
+        }
+
+        /**
+         * Sets input ID.
+         *
+         * @see PictureProfile#getInputId()
+         *
+         * @hide @SystemApi
+         */
+        @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
         @NonNull
         public Builder setInputId(@NonNull String value) {
             mInputId = value;
@@ -189,7 +284,12 @@
 
         /**
          * Sets package name of the profile.
+         *
+         * @see PictureProfile#getPackageName()
+         *
+         * @hide @SystemApi
          */
+        @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
         @NonNull
         public Builder setPackageName(@NonNull String value) {
             mPackageName = value;
@@ -198,6 +298,8 @@
 
         /**
          * Sets profile parameters.
+         *
+         * @see PictureProfile#getParameters()
          */
         @NonNull
         public Builder setParameters(@NonNull Bundle params) {
@@ -213,6 +315,7 @@
 
             PictureProfile o = new PictureProfile(
                     mId,
+                    mType,
                     mName,
                     mInputId,
                     mPackageName,
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 6658918..abfc244 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -16,6 +16,8 @@
 
 package android.media.tv;
 
+import static android.media.tv.flags.Flags.tifExtensionStandardization;
+
 import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
 import android.annotation.IntDef;
@@ -159,6 +161,11 @@
             new RemoteCallbackList<>();
 
     private TvInputManager mTvInputManager;
+    /**
+     * @hide
+     */
+    protected TvInputServiceExtensionManager mTvInputServiceExtensionManager =
+            new TvInputServiceExtensionManager();
 
     @Override
     public final IBinder onBind(Intent intent) {
@@ -211,12 +218,23 @@
             }
 
             @Override
-            public List<String>  getAvailableExtensionInterfaceNames() {
-                return TvInputService.this.getAvailableExtensionInterfaceNames();
+            public List<String> getAvailableExtensionInterfaceNames() {
+                List<String> extensionNames =
+                        TvInputService.this.getAvailableExtensionInterfaceNames();
+                if (tifExtensionStandardization()) {
+                    extensionNames.addAll(
+                            TvInputServiceExtensionManager.getStandardExtensionInterfaceNames());
+                }
+                return extensionNames;
             }
 
             @Override
             public IBinder getExtensionInterface(String name) {
+                if (tifExtensionStandardization() && name != null) {
+                    if (TvInputServiceExtensionManager.checkIsStandardizedInterfaces(name)) {
+                        return mTvInputServiceExtensionManager.getExtensionIBinder(name);
+                    }
+                }
                 return TvInputService.this.getExtensionInterface(name);
             }
 
diff --git a/media/java/android/media/tv/TvInputServiceExtensionManager.java b/media/java/android/media/tv/TvInputServiceExtensionManager.java
new file mode 100644
index 0000000..c514f6e
--- /dev/null
+++ b/media/java/android/media/tv/TvInputServiceExtensionManager.java
@@ -0,0 +1,827 @@
+/*
+ * Copyright (C) 2024 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.media.tv;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringDef;
+import android.media.tv.flags.Flags;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+
+/**
+ * This class provides a list of available standardized TvInputService extension interface names
+ * and a container storing IBinder objects that implement these interfaces created by SoC/OEMs.
+ * It also provides an API for SoC/OEMs to register implemented IBinder objects.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_TIF_EXTENSION_STANDARDIZATION)
+public final class TvInputServiceExtensionManager {
+    private static final String TAG = "TvInputServiceExtensionManager";
+    private static final String SCAN_PACKAGE = "android.media.tv.extension.scan.";
+    private static final String OAD_PACKAGE = "android.media.tv.extension.oad.";
+    private static final String CAM_PACKAGE = "android.media.tv.extension.cam.";
+    private static final String RATING_PACKAGE = "android.media.tv.extension.rating.";
+    private static final String TIME_PACKAGE = "android.media.tv.extension.time.";
+    private static final String TELETEXT_PACKAGE = "android.media.tv.extension.teletext.";
+    private static final String SCAN_BSU_PACKAGE = "android.media.tv.extension.scanbsu.";
+    private static final String CLIENT_TOKEN_PACKAGE = "android.media.tv.extension.clienttoken.";
+    private static final String SCREEN_MODE_PACKAGE = "android.media.tv.extension.screenmode.";
+    private static final String SIGNAL_PACKAGE = "android.media.tv.extension.signal.";
+    private static final String SERVICE_DATABASE_PACKAGE = "android.media.tv.extension.servicedb.";
+    private static final String PVR_PACKAGE = "android.media.tv.extension.pvr.";
+    private static final String EVENT_PACKAGE = "android.media.tv.extension.event.";
+    private static final String ANALOG_PACKAGE = "android.media.tv.extension.analog.";
+    private static final String TUNE_PACKAGE = "android.media.tv.extension.tune.";
+
+    /** @hide */
+    @IntDef(prefix = {"REGISTER_"}, value = {
+            REGISTER_SUCCESS,
+            REGISTER_FAIL_NAME_NOT_STANDARDIZED,
+            REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED,
+            REGISTER_FAIL_REMOTE_EXCEPTION
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface RegisterResult {}
+
+    /**
+     * Registering binder returns success when it abides standardized interface structure
+     */
+    public static final int REGISTER_SUCCESS = 0;
+    /**
+     * Registering binder returns failure when the extension name is not in the standardization
+     * list
+     */
+    public static final int REGISTER_FAIL_NAME_NOT_STANDARDIZED = 1;
+    /**
+     * Registering binder returns failure when the IBinder does not implement standardized interface
+     */
+    public static final int REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED = 2;
+    /**
+     * Registering binder returns failure when remote server is not available
+     */
+    public static final int REGISTER_FAIL_REMOTE_EXCEPTION = 3;
+
+    /** @hide */
+    @StringDef({
+            ISCAN_INTERFACE,
+            ISCAN_SESSION,
+            ISCAN_LISTENER,
+            IHDPLUS_INFO,
+            IOPERATOR_DETECTION,
+            IOPERATOR_DETECTION_LISTENER,
+            IREGION_CHANNEL_LIST,
+            IREGION_CHANNEL_LIST_LISTENER,
+            ITARGET_REGION,
+            ITARGET_REGION_LISTENER,
+            ILCN_CONFLICT,
+            ILCN_CONFLICT_LISTENER,
+            ILCNV2_CHANNEL_LIST,
+            ILCNV2_CHANNEL_LIST_LISTENER,
+            IFAVORITE_NETWORK,
+            IFAVORITE_NETWORK_LISTENER,
+            ITKGS_INFO,
+            ITKGS_INFO_LISTENER,
+            ISCAN_SAT_SEARCH,
+            IOAD_UPDATE_INTERFACE,
+            ICAM_APP_INFO_SERVICE,
+            ICAM_APP_INFO_LISTENER,
+            ICAM_MONITORING_SERVICE,
+            ICAM_INFO_LISTENER,
+            ICI_OPERATOR_INTERFACE,
+            ICI_OPERATOR_LISTENER,
+            ICAM_PROFILE_INTERFACE,
+            ICONTENT_CONTROL_SERVICE,
+            ICAM_DRM_INFO_LISTENER,
+            ICAM_PIN_SERVICE,
+            ICAM_PIN_CAPABILITY_LISTENER,
+            ICAM_PIN_STATUS_LISTENER,
+            ICAM_HOST_CONTROL_SERVICE,
+            ICAM_HOST_CONTROL_ASK_RELEASE_REPLY_CALLBACK,
+            ICAM_HOST_CONTROL_INFO_LISTENER,
+            ICAM_HOST_CONTROL_TUNE_QUIETLY_FLAG,
+            ICAM_HOST_CONTROL_TUNE_QUIETLY_FLAG_LISTENER,
+            IMMI_INTERFACE,
+            IMMI_SESSION,
+            IMMI_STATUS_CALLBACK,
+            IENTER_MENU_ERROR_CALLBACK,
+            IDOWNLOADABLE_RATING_TABLE_MONITOR,
+            IRATING_INTERFACE,
+            IPMT_RATING_INTERFACE,
+            IPMT_RATING_LISTENER,
+            IVBI_RATING_INTERFACE,
+            IVBI_RATING_LISTENER,
+            IPROGRAM_INFO,
+            IPROGRAM_INFO_LISTENER,
+            IBROADCAST_TIME,
+            IDATA_SERVICE_SIGNAL_INFO,
+            IDATA_SERVICE_SIGNAL_INFO_LISTENER,
+            ITELETEXT_PAGE_SUB_CODE,
+            ISCAN_BACKGROUND_SERVICE_UPDATE,
+            ISCAN_BACKGROUND_SERVICE_UPDATE_LISTENER,
+            ICLIENT_TOKEN,
+            ISCREEN_MODE_SETTINGS,
+            IHDMI_SIGNAL_INTERFACE,
+            IHDMI_SIGNAL_INFO_LISTENER,
+            IAUDIO_SIGNAL_INFO,
+            IANALOG_AUDIO_INFO,
+            IAUDIO_SIGNAL_INFO_LISTENER,
+            IVIDEO_SIGNAL_INFO,
+            IVIDEO_SIGNAL_INFO_LISTENER,
+            ISERVICE_LIST_EDIT,
+            ISERVICE_LIST_EDIT_LISTENER,
+            ISERVICE_LIST,
+            ISERVICE_LIST_TRANSFER_INTERFACE,
+            ISERVICE_LIST_EXPORT_SESSION,
+            ISERVICE_LIST_EXPORT_LISTENER,
+            ISERVICE_LIST_IMPORT_SESSION,
+            ISERVICE_LIST_IMPORT_LISTENER,
+            ISERVICE_LIST_SET_CHANNEL_LIST_SESSION,
+            ISERVICE_LIST_SET_CHANNEL_LIST_LISTENER,
+            ICHANNEL_LIST_TRANSFER,
+            IRECORDED_CONTENTS,
+            IDELETE_RECORDED_CONTENTS_CALLBACK,
+            IGET_INFO_RECORDED_CONTENTS_CALLBACK,
+            IEVENT_MONITOR,
+            IEVENT_MONITOR_LISTENER,
+            IEVENT_DOWNLOAD,
+            IEVENT_DOWNLOAD_LISTENER,
+            IEVENT_DOWNLOAD_SESSION,
+            IANALOG_ATTRIBUTE_INTERFACE,
+            ICHANNEL_TUNED_INTERFACE,
+            ICHANNEL_TUNED_LISTENER,
+            ITUNER_FRONTEND_SIGNAL_INFO_INTERFACE,
+            ITUNER_FRONTEND_SIGNAL_INFO_LISTENER,
+            IMUX_TUNE_SESSION,
+            IMUX_TUNE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface StandardizedExtensionName {}
+    /**
+     * Interface responsible for creating scan session and obtain parameters.
+     * @hide
+     */
+    public static final String ISCAN_INTERFACE = SCAN_PACKAGE + "IScanInterface";
+    /**
+     * Interface that handles scan session and get/store related information.
+     * @hide
+     */
+    public static final String ISCAN_SESSION = SCAN_PACKAGE + "IScanSession";
+    /**
+     * Interface that notifies changes related to scan session.
+     * @hide
+     */
+    public static final String ISCAN_LISTENER = SCAN_PACKAGE + "IScanListener";
+    /**
+     * Interface for setting HDPlus information.
+     * @hide
+     */
+    public static final String IHDPLUS_INFO = SCAN_PACKAGE + "IHDPlusInfo";
+    /**
+     * Interface for handling operator detection for scanning.
+     * @hide
+     */
+    public static final String IOPERATOR_DETECTION = SCAN_PACKAGE + "IOperatorDetection";
+    /**
+     * Interface for changes related to operator detection searches.
+     * @hide
+     */
+    public static final String IOPERATOR_DETECTION_LISTENER = SCAN_PACKAGE
+            + "IOperatorDetectionListener";
+    /**
+     * Interface for handling region channel list for scanning.
+     * @hide
+     */
+    public static final String IREGION_CHANNEL_LIST = SCAN_PACKAGE + "IRegionChannelList";
+    /**
+     * Interface for changes related to changes in region channel list search.
+     * @hide
+     */
+    public static final String IREGION_CHANNEL_LIST_LISTENER = SCAN_PACKAGE
+            + "IRegionChannelListListener";
+    /**
+     * Interface for handling target region information.
+     * @hide
+     */
+    public static final String ITARGET_REGION = SCAN_PACKAGE + "ITargetRegion";
+    /**
+     * Interface for changes related to target regions during scanning.
+     * @hide
+     */
+    public static final String ITARGET_REGION_LISTENER = SCAN_PACKAGE + "ITargetRegionListener";
+    /**
+     * Interface for handling LCN conflict groups.
+     * @hide
+     */
+    public static final String ILCN_CONFLICT = SCAN_PACKAGE + "ILcnConflict";
+    /**
+     * Interface for detecting LCN conflicts during scanning.
+     * @hide
+     */
+    public static final String ILCN_CONFLICT_LISTENER = SCAN_PACKAGE + "ILcnConflictListener";
+    /**
+     * Interface for handling LCN V2 channel list information.
+     * @hide
+     */
+    public static final String ILCNV2_CHANNEL_LIST = SCAN_PACKAGE + "ILcnV2ChannelList";
+    /**
+     * Interface for detecting LCN V2 channel list during scanning.
+     * @hide
+     */
+    public static final String ILCNV2_CHANNEL_LIST_LISTENER = SCAN_PACKAGE
+            + "ILcnV2ChannelListListener";
+    /**
+     * Interface for handling favorite network related information.
+     * @hide
+     */
+    public static final String IFAVORITE_NETWORK = SCAN_PACKAGE + "IFavoriteNetwork";
+    /**
+     * Interface for detecting favorite network during scanning.
+     * @hide
+     */
+    public static final String IFAVORITE_NETWORK_LISTENER = SCAN_PACKAGE
+            + "IFavoriteNetworkListener";
+    /**
+     * Interface for handling Turksat channel update system service.
+     * @hide
+     */
+    public static final String ITKGS_INFO = SCAN_PACKAGE + "ITkgsInfo";
+    /**
+     * Interface for changes related to TKGS information.
+     * @hide
+     */
+    public static final String ITKGS_INFO_LISTENER = SCAN_PACKAGE + "ITkgsInfoListener";
+    /**
+     * Interface for satellite search related to low noise block downconverter.
+     * @hide
+     */
+    public static final String ISCAN_SAT_SEARCH = SCAN_PACKAGE + "IScanSatSearch";
+    /**
+     * Interface for Over-the-Air Download.
+     * @hide
+     */
+    public static final String IOAD_UPDATE_INTERFACE = OAD_PACKAGE + "IOadUpdateInterface";
+    /**
+     * Interface for handling conditional access module app related information.
+     * @hide
+     */
+    public static final String ICAM_APP_INFO_SERVICE = CAM_PACKAGE + "ICamAppInfoService";
+    /**
+     * Interface for changes on conditional access module app related information.
+     * @hide
+     */
+    public static final String ICAM_APP_INFO_LISTENER = CAM_PACKAGE + "ICamAppInfoListener";
+    /**
+     * Interface for handling conditional access module related information.
+     * @hide
+     */
+    public static final String ICAM_MONITORING_SERVICE = CAM_PACKAGE + "ICamMonitoringService";
+    /**
+     * Interface for changes on conditional access module related information.
+     * @hide
+     */
+    public static final String ICAM_INFO_LISTENER = CAM_PACKAGE + "ICamInfoListener";
+    /**
+     * Interface for handling control of CI+ operations.
+     * @hide
+     */
+    public static final String ICI_OPERATOR_INTERFACE = CAM_PACKAGE + "ICiOperatorInterface";
+    /**
+     * Interfaces for changes on CI+ operations.
+     * @hide
+     */
+    public static final String ICI_OPERATOR_LISTENER = CAM_PACKAGE + "ICiOperatorListener";
+    /**
+     * Interface for handling conditional access module profile related information.
+     * @hide
+     */
+    public static final String ICAM_PROFILE_INTERFACE = CAM_PACKAGE + "ICamProfileInterface";
+    /**
+     * Interface for handling conditional access module DRM related information.
+     * @hide
+     */
+    public static final String ICONTENT_CONTROL_SERVICE = CAM_PACKAGE + "IContentControlService";
+    /**
+     * Interface for changes on DRM.
+     * @hide
+     */
+    public static final String ICAM_DRM_INFO_LISTENER = CAM_PACKAGE + "ICamDrmInfoListener";
+    /**
+     * Interface for handling conditional access module pin related information.
+     * @hide
+     */
+    public static final String ICAM_PIN_SERVICE = CAM_PACKAGE + "ICamPinService";
+    /**
+     * Interface for changes on conditional access module pin capability.
+     * @hide
+     */
+    public static final String ICAM_PIN_CAPABILITY_LISTENER = CAM_PACKAGE
+            + "ICamPinCapabilityListener";
+    /**
+     * Interface for changes on conditional access module pin status.
+     * @hide
+     */
+    public static final String ICAM_PIN_STATUS_LISTENER = CAM_PACKAGE + "ICamPinStatusListener";
+    /**
+     * Interface for handling conditional access module host control service.
+     * @hide
+     */
+    public static final String ICAM_HOST_CONTROL_SERVICE = CAM_PACKAGE + "ICamHostControlService";
+    /**
+     * Interface for handling conditional access module ask release reply.
+     * @hide
+     */
+    public static final String ICAM_HOST_CONTROL_ASK_RELEASE_REPLY_CALLBACK = CAM_PACKAGE
+            + "ICamHostControlAskReleaseReplyCallback";
+    /**
+     * Interface for changes on conditional access module host control service.
+     * @hide
+     */
+    public static final String ICAM_HOST_CONTROL_INFO_LISTENER = CAM_PACKAGE
+            + "ICamHostControlInfoListener";
+    /**
+     * Interface for handling conditional access module host control service tune_quietly_flag.
+     * @hide
+     */
+    public static final String ICAM_HOST_CONTROL_TUNE_QUIETLY_FLAG = CAM_PACKAGE
+            + "ICamHostControlTuneQuietlyFlag";
+    /**
+     * Interface for changes on conditional access module host control service tune_quietly_flag.
+     * @hide
+     */
+    public static final String ICAM_HOST_CONTROL_TUNE_QUIETLY_FLAG_LISTENER = CAM_PACKAGE
+            + "ICamHostControlTuneQuietlyFlagListener";
+    /**
+     * Interface for handling conditional access module multi media interface.
+     * @hide
+     */
+    public static final String IMMI_INTERFACE = CAM_PACKAGE + "IMmiInterface";
+    /**
+     * Interface for controlling conditional access module multi media session.
+     * @hide
+     */
+    public static final String IMMI_SESSION = CAM_PACKAGE + "IMmiSession";
+    /**
+     * Interface for changes on conditional access module multi media session status.
+     * @hide
+     */
+    public static final String IMMI_STATUS_CALLBACK = CAM_PACKAGE + "IMmiStatusCallback";
+    /**
+     * Interface for changes on conditional access app info related to entering menu.
+     * @hide
+     */
+    public static final String IENTER_MENU_ERROR_CALLBACK = CAM_PACKAGE + "IEnterMenuErrorCallback";
+    /**
+     * Interface for handling RRT downloadable rating data.
+     * @hide
+     */
+    public static final String IDOWNLOADABLE_RATING_TABLE_MONITOR = RATING_PACKAGE
+            + "IDownloadableRatingTableMonitor";
+    /**
+     * Interface for handling RRT rating related information.
+     * @hide
+     */
+    public static final String IRATING_INTERFACE = RATING_PACKAGE + "IRatingInterface";
+    /**
+     * Interface for handling PMT rating related information.
+     * @hide
+     */
+    public static final String IPMT_RATING_INTERFACE = RATING_PACKAGE + "IPmtRatingInterface";
+    /**
+     * Interface for changes on PMT rating related information.
+     * @hide
+     */
+    public static final String IPMT_RATING_LISTENER = RATING_PACKAGE + "IPmtRatingListener";
+    /**
+     * Interface for handling IVBI rating related information.
+     * @hide
+     */
+    public static final String IVBI_RATING_INTERFACE = RATING_PACKAGE + "IVbiRatingInterface";
+    /**
+     * Interface for changes on IVBI rating related information.
+     * @hide
+     */
+    public static final String IVBI_RATING_LISTENER = RATING_PACKAGE + "IVbiRatingListener";
+    /**
+     * Interface for handling program rating related information.
+     * @hide
+     */
+    public static final String IPROGRAM_INFO = RATING_PACKAGE + "IProgramInfo";
+    /**
+     * Interface for changes on program rating related information.
+     * @hide
+     */
+    public static final String IPROGRAM_INFO_LISTENER = RATING_PACKAGE + "IProgramInfoListener";
+    /**
+     * Interface for getting broadcast time related information.
+     * @hide
+     */
+    public static final String IBROADCAST_TIME = TIME_PACKAGE + "BroadcastTime";
+    /**
+     * Interface for handling data service signal information on teletext.
+     * @hide
+     */
+    public static final String IDATA_SERVICE_SIGNAL_INFO = TELETEXT_PACKAGE
+            + "IDataServiceSignalInfo";
+    /**
+     * Interface for changes on data service signal information on teletext.
+     * @hide
+     */
+    public static final String IDATA_SERVICE_SIGNAL_INFO_LISTENER = TELETEXT_PACKAGE
+            + "IDataServiceSignalInfoListener";
+    /**
+     * Interface for handling teletext page information.
+     * @hide
+     */
+    public static final String ITELETEXT_PAGE_SUB_CODE = TELETEXT_PACKAGE + "ITeletextPageSubCode";
+    /**
+     * Interface for handling scan background service update.
+     * @hide
+     */
+    public static final String ISCAN_BACKGROUND_SERVICE_UPDATE = SCAN_BSU_PACKAGE
+            + "IScanBackgroundServiceUpdate";
+    /**
+     * Interface for changes on background service update
+     * @hide
+     */
+    public static final String ISCAN_BACKGROUND_SERVICE_UPDATE_LISTENER = SCAN_BSU_PACKAGE
+            + "IScanBackgroundServiceUpdateListener";
+    /**
+     * Interface for generating client token.
+     * @hide
+     */
+    public static final String ICLIENT_TOKEN = CLIENT_TOKEN_PACKAGE + "IClientToken";
+    /**
+     * Interfaces for handling screen mode information.
+     * @hide
+     */
+    public static final String ISCREEN_MODE_SETTINGS = SCREEN_MODE_PACKAGE + "IScreenModeSettings";
+    /**
+     * Interfaces for handling HDMI signal information update.
+     * @hide
+     */
+    public static final String IHDMI_SIGNAL_INTERFACE = SIGNAL_PACKAGE + "IHdmiSignalInterface";
+    /**
+     * Interfaces for changes on HDMI signal information update.
+     * @hide
+     */
+    public static final String IHDMI_SIGNAL_INFO_LISTENER = SIGNAL_PACKAGE
+            + "IHdmiSignalInfoListener";
+    /**
+     * Interfaces for handling audio signal information update.
+     * @hide
+     */
+    public static final String IAUDIO_SIGNAL_INFO = SIGNAL_PACKAGE + "IAudioSignalInfo";
+    /**
+     * Interfaces for handling analog audio signal information update.
+     * @hide
+     */
+    public static final String IANALOG_AUDIO_INFO = SIGNAL_PACKAGE + "IAnalogAudioInfo";
+    /**
+     * Interfaces for change on audio signal information update.
+     * @hide
+     */
+    public static final String IAUDIO_SIGNAL_INFO_LISTENER = SIGNAL_PACKAGE
+            + "IAudioSignalInfoListener";
+    /**
+     * Interfaces for handling video signal information update.
+     * @hide
+     */
+    public static final String IVIDEO_SIGNAL_INFO = SIGNAL_PACKAGE + "IVideoSignalInfo";
+    /**
+     * Interfaces for changes on video signal information update.
+     * @hide
+     */
+    public static final String IVIDEO_SIGNAL_INFO_LISTENER = SIGNAL_PACKAGE
+            + "IVideoSignalInfoListener";
+    /**
+     * Interfaces for handling service database updates.
+     * @hide
+     */
+    public static final String ISERVICE_LIST_EDIT = SERVICE_DATABASE_PACKAGE + "IServiceListEdit";
+    /**
+     * Interfaces for changes on service database updates.
+     * @hide
+     */
+    public static final String ISERVICE_LIST_EDIT_LISTENER = SERVICE_DATABASE_PACKAGE
+            + "IServiceListEditListener";
+    /**
+     * Interfaces for getting service database related information.
+     * @hide
+     */
+    public static final String ISERVICE_LIST = SERVICE_DATABASE_PACKAGE + "IServiceList";
+    /**
+     * Interfaces for transferring service database related information.
+     * @hide
+     */
+    public static final String ISERVICE_LIST_TRANSFER_INTERFACE = SERVICE_DATABASE_PACKAGE
+            + "IServiceListTransferInterface";
+    /**
+     * Interfaces for exporting service database session.
+     * @hide
+     */
+    public static final String ISERVICE_LIST_EXPORT_SESSION = SERVICE_DATABASE_PACKAGE
+            + "IServiceListExportSession";
+    /**
+     * Interfaces for changes on exporting service database session.
+     * @hide
+     */
+    public static final String ISERVICE_LIST_EXPORT_LISTENER = SERVICE_DATABASE_PACKAGE
+            + "IServiceListExportListener";
+    /**
+     * Interfaces for importing service database session.
+     * @hide
+     */
+    public static final String ISERVICE_LIST_IMPORT_SESSION = SERVICE_DATABASE_PACKAGE
+            + "IServiceListImportSession";
+    /**
+     * Interfaces for changes on importing service database session.
+     * @hide
+     */
+    public static final String ISERVICE_LIST_IMPORT_LISTENER = SERVICE_DATABASE_PACKAGE
+            + "IServiceListImportListener";
+    /**
+     * Interfaces for setting channel list resources.
+     * @hide
+     */
+    public static final String ISERVICE_LIST_SET_CHANNEL_LIST_SESSION = SERVICE_DATABASE_PACKAGE
+            + "IServiceListSetChannelListSession";
+    /**
+     * Interfaces for changes on setting channel list resources.
+     * @hide
+     */
+    public static final String ISERVICE_LIST_SET_CHANNEL_LIST_LISTENER = SERVICE_DATABASE_PACKAGE
+            + "IServiceListSetChannelListListener";
+    /**
+     * Interfaces for transferring channel list resources.
+     * @hide
+     */
+    public static final String ICHANNEL_LIST_TRANSFER = SERVICE_DATABASE_PACKAGE
+            + "IChannelListTransfer";
+    /**
+     * Interfaces for record contents updates.
+     * @hide
+     */
+    public static final String IRECORDED_CONTENTS = PVR_PACKAGE + "IRecordedContents";
+    /**
+     * Interfaces for changes on deleting record contents.
+     * @hide
+     */
+    public static final String IDELETE_RECORDED_CONTENTS_CALLBACK = PVR_PACKAGE
+            + "IDeleteRecordedContentsCallback";
+    /**
+     * Interfaces for changes on getting record contents.
+     * @hide
+     */
+    public static final String IGET_INFO_RECORDED_CONTENTS_CALLBACK = PVR_PACKAGE
+            + "IGetInfoRecordedContentsCallback";
+    /**
+     * Interfaces for monitoring present event information.
+     * @hide
+     */
+    public static final String IEVENT_MONITOR = EVENT_PACKAGE + "IEventMonitor";
+    /**
+     * Interfaces for changes on present event information.
+     * @hide
+     */
+    public static final String IEVENT_MONITOR_LISTENER = EVENT_PACKAGE + "IEventMonitorListener";
+    /**
+     * Interfaces for handling download event information.
+     * @hide
+     */
+    public static final String IEVENT_DOWNLOAD = EVENT_PACKAGE + "IEventDownload";
+    /**
+     * Interfaces for changes on downloading event information.
+     * @hide
+     */
+    public static final String IEVENT_DOWNLOAD_LISTENER = EVENT_PACKAGE + "IEventDownloadListener";
+    /**
+     * Interfaces for handling download event information for DVB and DTMB.
+     * @hide
+     */
+    public static final String IEVENT_DOWNLOAD_SESSION = EVENT_PACKAGE + "IEventDownloadSession";
+    /**
+     * Interfaces for handling analog color system.
+     * @hide
+     */
+    public static final String IANALOG_ATTRIBUTE_INTERFACE = ANALOG_PACKAGE
+            + "IAnalogAttributeInterface";
+    /**
+     * Interfaces for monitoring channel tuned information.
+     * @hide
+     */
+    public static final String ICHANNEL_TUNED_INTERFACE = TUNE_PACKAGE + "IChannelTunedInterface";
+    /**
+     * Interfaces for changes on channel tuned information.
+     * @hide
+     */
+    public static final String ICHANNEL_TUNED_LISTENER = TUNE_PACKAGE + "IChannelTunedListener";
+    /**
+     * Interfaces for handling tuner frontend signal info.
+     * @hide
+     */
+    public static final String ITUNER_FRONTEND_SIGNAL_INFO_INTERFACE = SIGNAL_PACKAGE
+            + "ITunerFrontendSignalInfoInterface";
+    /**
+     * Interfaces for changes on tuner frontend signal info.
+     * @hide
+     */
+    public static final String ITUNER_FRONTEND_SIGNAL_INFO_LISTENER = SIGNAL_PACKAGE
+            + "ITunerFrontendSignalInfoListener";
+    /**
+     * Interfaces for handling mux tune operations.
+     * @hide
+     */
+    public static final String IMUX_TUNE_SESSION = TUNE_PACKAGE + "IMuxTuneSession";
+    /**
+     * Interfaces for initing mux tune session.
+     * @hide
+     */
+    public static final String IMUX_TUNE = TUNE_PACKAGE + "IMuxTune";
+
+    // Set of standardized AIDL interface canonical names
+    private static final Set<String> sTisExtensions = new HashSet<>(Set.of(
+            ISCAN_INTERFACE,
+            ISCAN_SESSION,
+            ISCAN_LISTENER,
+            IHDPLUS_INFO,
+            IOPERATOR_DETECTION,
+            IOPERATOR_DETECTION_LISTENER,
+            IREGION_CHANNEL_LIST,
+            IREGION_CHANNEL_LIST_LISTENER,
+            ITARGET_REGION,
+            ITARGET_REGION_LISTENER,
+            ILCN_CONFLICT,
+            ILCN_CONFLICT_LISTENER,
+            ILCNV2_CHANNEL_LIST,
+            ILCNV2_CHANNEL_LIST_LISTENER,
+            IFAVORITE_NETWORK,
+            IFAVORITE_NETWORK_LISTENER,
+            ITKGS_INFO,
+            ITKGS_INFO_LISTENER,
+            ISCAN_SAT_SEARCH,
+            IOAD_UPDATE_INTERFACE,
+            ICAM_APP_INFO_SERVICE,
+            ICAM_APP_INFO_LISTENER,
+            ICAM_MONITORING_SERVICE,
+            ICAM_INFO_LISTENER,
+            ICI_OPERATOR_INTERFACE,
+            ICI_OPERATOR_LISTENER,
+            ICAM_PROFILE_INTERFACE,
+            ICONTENT_CONTROL_SERVICE,
+            ICAM_DRM_INFO_LISTENER,
+            ICAM_PIN_SERVICE,
+            ICAM_PIN_CAPABILITY_LISTENER,
+            ICAM_PIN_STATUS_LISTENER,
+            ICAM_HOST_CONTROL_SERVICE,
+            ICAM_HOST_CONTROL_ASK_RELEASE_REPLY_CALLBACK,
+            ICAM_HOST_CONTROL_INFO_LISTENER,
+            ICAM_HOST_CONTROL_TUNE_QUIETLY_FLAG,
+            ICAM_HOST_CONTROL_TUNE_QUIETLY_FLAG_LISTENER,
+            IMMI_INTERFACE,
+            IMMI_SESSION,
+            IMMI_STATUS_CALLBACK,
+            IENTER_MENU_ERROR_CALLBACK,
+            IDOWNLOADABLE_RATING_TABLE_MONITOR,
+            IRATING_INTERFACE,
+            IPMT_RATING_INTERFACE,
+            IPMT_RATING_LISTENER,
+            IVBI_RATING_INTERFACE,
+            IVBI_RATING_LISTENER,
+            IPROGRAM_INFO,
+            IPROGRAM_INFO_LISTENER,
+            IBROADCAST_TIME,
+            IDATA_SERVICE_SIGNAL_INFO,
+            IDATA_SERVICE_SIGNAL_INFO_LISTENER,
+            ITELETEXT_PAGE_SUB_CODE,
+            ISCAN_BACKGROUND_SERVICE_UPDATE,
+            ISCAN_BACKGROUND_SERVICE_UPDATE_LISTENER,
+            ICLIENT_TOKEN,
+            ISCREEN_MODE_SETTINGS,
+            IHDMI_SIGNAL_INTERFACE,
+            IHDMI_SIGNAL_INFO_LISTENER,
+            IAUDIO_SIGNAL_INFO,
+            IANALOG_AUDIO_INFO,
+            IAUDIO_SIGNAL_INFO_LISTENER,
+            IVIDEO_SIGNAL_INFO,
+            IVIDEO_SIGNAL_INFO_LISTENER,
+            ISERVICE_LIST_EDIT,
+            ISERVICE_LIST_EDIT_LISTENER,
+            ISERVICE_LIST,
+            ISERVICE_LIST_TRANSFER_INTERFACE,
+            ISERVICE_LIST_EXPORT_SESSION,
+            ISERVICE_LIST_EXPORT_LISTENER,
+            ISERVICE_LIST_IMPORT_SESSION,
+            ISERVICE_LIST_IMPORT_LISTENER,
+            ISERVICE_LIST_SET_CHANNEL_LIST_SESSION,
+            ISERVICE_LIST_SET_CHANNEL_LIST_LISTENER,
+            ICHANNEL_LIST_TRANSFER,
+            IRECORDED_CONTENTS,
+            IDELETE_RECORDED_CONTENTS_CALLBACK,
+            IGET_INFO_RECORDED_CONTENTS_CALLBACK,
+            IEVENT_MONITOR,
+            IEVENT_MONITOR_LISTENER,
+            IEVENT_DOWNLOAD,
+            IEVENT_DOWNLOAD_LISTENER,
+            IEVENT_DOWNLOAD_SESSION,
+            IANALOG_ATTRIBUTE_INTERFACE,
+            ICHANNEL_TUNED_INTERFACE,
+            ICHANNEL_TUNED_LISTENER,
+            ITUNER_FRONTEND_SIGNAL_INFO_INTERFACE,
+            ITUNER_FRONTEND_SIGNAL_INFO_LISTENER,
+            IMUX_TUNE_SESSION,
+            IMUX_TUNE
+    ));
+
+    // Store the mapping between interface names and IBinder
+    private Map<String, IBinder> mExtensionInterfaceIBinderMapping = new HashMap<>();
+
+    TvInputServiceExtensionManager() {
+    }
+
+    /**
+     * Function to return available extension interface names
+     *
+     * @hide
+     */
+    public static @NonNull List<String> getStandardExtensionInterfaceNames() {
+        return new ArrayList<>(sTisExtensions);
+    }
+
+    /**
+     * Function to check if the extension is in the standardization list
+     */
+    static boolean checkIsStandardizedInterfaces(@NonNull String extensionName) {
+        return sTisExtensions.contains(extensionName);
+    }
+
+    /**
+     * This function should be used by OEM to register IBinder objects that implement
+     * standardized AIDL interfaces.
+     *
+     * @param extensionName Extension Interface Name
+     * @param binder        IBinder object to be registered
+     * @return REGISTER_SUCCESS on success of registering IBinder object
+     *         REGISTER_FAIL_NAME_NOT_STANDARDIZED on failure due to registering extension with
+     *              non-standardized name
+     *         REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED on failure due to IBinder not
+     *              implementing standardized AIDL interface
+     *         REGISTER_FAIL_REMOTE_EXCEPTION on failure due to remote exception
+     *
+     * @hide
+     */
+    @RegisterResult
+    public int registerExtensionIBinder(@StandardizedExtensionName @NonNull String extensionName,
+            @NonNull IBinder binder) {
+        if (!checkIsStandardizedInterfaces(extensionName)) {
+            return REGISTER_FAIL_NAME_NOT_STANDARDIZED;
+        }
+        try {
+            if (binder.getInterfaceDescriptor().equals(extensionName)) {
+                mExtensionInterfaceIBinderMapping.put(extensionName, binder);
+                return REGISTER_SUCCESS;
+            } else {
+                return REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Fetching IBinder object failure due to " + e);
+            return REGISTER_FAIL_REMOTE_EXCEPTION;
+        }
+    }
+
+    /**
+     * Function to get corresponding IBinder object
+     */
+    @Nullable IBinder getExtensionIBinder(@NonNull String extensionName) {
+        return mExtensionInterfaceIBinderMapping.get(extensionName);
+    }
+
+}
diff --git a/media/java/android/mtp/OWNERS b/media/java/android/mtp/OWNERS
index 6b5336e..77ed08b 100644
--- a/media/java/android/mtp/OWNERS
+++ b/media/java/android/mtp/OWNERS
@@ -1,10 +1,9 @@
 set noparent
 
-aprasath@google.com
 anothermark@google.com
-kumarashishg@google.com
-sarup@google.com
+febinthattil@google.com
+aprasath@google.com
 jsharkey@android.com
 jameswei@google.com
 rmojumder@google.com
-
+kumarashishg@google.com
diff --git a/media/tests/MtpTests/OWNERS b/media/tests/MtpTests/OWNERS
index 6b5336e..bdb6cdb 100644
--- a/media/tests/MtpTests/OWNERS
+++ b/media/tests/MtpTests/OWNERS
@@ -1,10 +1,9 @@
 set noparent
 
-aprasath@google.com
 anothermark@google.com
-kumarashishg@google.com
-sarup@google.com
+febinthattil@google.com
+aprasath@google.com
 jsharkey@android.com
 jameswei@google.com
 rmojumder@google.com
-
+kumarashishg@google.com
\ No newline at end of file
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index 96b7c13..00812042 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -207,6 +207,7 @@
     method public boolean isDefaultServiceForCategory(android.content.ComponentName, String);
     method @FlaggedApi("android.nfc.enable_card_emulation_euicc") public boolean isEuiccSupported();
     method public boolean registerAidsForService(android.content.ComponentName, String, java.util.List<java.lang.String>);
+    method @FlaggedApi("android.nfc.nfc_event_listener") public void registerNfcEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.cardemulation.CardEmulation.NfcEventListener);
     method @FlaggedApi("android.nfc.nfc_read_polling_loop") public boolean registerPollingLoopFilterForService(@NonNull android.content.ComponentName, @NonNull String, boolean);
     method @FlaggedApi("android.nfc.nfc_read_polling_loop") public boolean registerPollingLoopPatternFilterForService(@NonNull android.content.ComponentName, @NonNull String, boolean);
     method public boolean removeAidsForService(android.content.ComponentName, String);
@@ -216,6 +217,7 @@
     method public boolean setPreferredService(android.app.Activity, android.content.ComponentName);
     method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean setShouldDefaultToObserveModeForService(@NonNull android.content.ComponentName, boolean);
     method public boolean supportsAidPrefixRegistration();
+    method @FlaggedApi("android.nfc.nfc_event_listener") public void unregisterNfcEventListener(@NonNull android.nfc.cardemulation.CardEmulation.NfcEventListener);
     method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean unsetOffHostForService(@NonNull android.content.ComponentName);
     method public boolean unsetPreferredService(android.app.Activity);
     field @Deprecated public static final String ACTION_CHANGE_DEFAULT = "android.nfc.cardemulation.action.ACTION_CHANGE_DEFAULT";
@@ -233,13 +235,16 @@
     field public static final int SELECTION_MODE_PREFER_DEFAULT = 0; // 0x0
   }
 
+  @FlaggedApi("android.nfc.nfc_event_listener") public static interface CardEmulation.NfcEventListener {
+    method @FlaggedApi("android.nfc.nfc_event_listener") public default void onObserveModeStateChanged(boolean);
+    method @FlaggedApi("android.nfc.nfc_event_listener") public default void onPreferredServiceChanged(boolean);
+  }
+
   public abstract class HostApduService extends android.app.Service {
     ctor public HostApduService();
     method public final void notifyUnhandled();
     method public final android.os.IBinder onBind(android.content.Intent);
     method public abstract void onDeactivated(int);
-    method @FlaggedApi("android.nfc.nfc_event_listener") public void onObserveModeStateChanged(boolean);
-    method @FlaggedApi("android.nfc.nfc_event_listener") public void onPreferredServiceChanged(boolean);
     method public abstract byte[] processCommandApdu(byte[], android.os.Bundle);
     method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void processPollingFrames(@NonNull java.util.List<android.nfc.cardemulation.PollingFrame>);
     method public final void sendResponseApdu(byte[]);
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index 24e14e6..6aa8a2b 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -91,6 +91,7 @@
     method public void onDisable(@NonNull java.util.function.Consumer<java.lang.Boolean>);
     method public void onDisableFinished(int);
     method public void onDisableStarted();
+    method public void onEeListenActivated(boolean);
     method public void onEnable(@NonNull java.util.function.Consumer<java.lang.Boolean>);
     method public void onEnableFinished(int);
     method public void onEnableStarted();
@@ -105,7 +106,7 @@
     method public void onRfFieldActivated(boolean);
     method public void onRoutingChanged();
     method public void onStateUpdated(int);
-    method public void onTagConnected(boolean, @NonNull android.nfc.Tag);
+    method public void onTagConnected(boolean);
     method public void onTagDispatch(@NonNull java.util.function.Consumer<java.lang.Boolean>);
   }
 
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl b/nfc/java/android/nfc/ComponentNameAndUser.aidl
similarity index 89%
copy from libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
copy to nfc/java/android/nfc/ComponentNameAndUser.aidl
index e21bf8f..e677998 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
+++ b/nfc/java/android/nfc/ComponentNameAndUser.aidl
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.shared;
+package android.nfc;
 
-parcelable GroupedRecentTaskInfo;
\ No newline at end of file
+parcelable ComponentNameAndUser;
\ No newline at end of file
diff --git a/nfc/java/android/nfc/ComponentNameAndUser.java b/nfc/java/android/nfc/ComponentNameAndUser.java
new file mode 100644
index 0000000..59e6c62
--- /dev/null
+++ b/nfc/java/android/nfc/ComponentNameAndUser.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2024 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.nfc;
+
+import android.annotation.UserIdInt;
+import android.content.ComponentName;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * @hide
+ */
+public class ComponentNameAndUser implements Parcelable {
+    @UserIdInt private final int mUserId;
+    private ComponentName mComponentName;
+
+    public ComponentNameAndUser(@UserIdInt int userId, ComponentName componentName) {
+        mUserId = userId;
+        mComponentName = componentName;
+    }
+
+    /**
+     * @hide
+     */
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * @hide
+     */
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mUserId);
+        out.writeParcelable(mComponentName, flags);
+    }
+
+    public static final Parcelable.Creator<ComponentNameAndUser> CREATOR =
+            new Parcelable.Creator<ComponentNameAndUser>() {
+                public ComponentNameAndUser createFromParcel(Parcel in) {
+                    return new ComponentNameAndUser(in);
+                }
+
+                public ComponentNameAndUser[] newArray(int size) {
+                    return new ComponentNameAndUser[size];
+                }
+            };
+
+    private ComponentNameAndUser(Parcel in) {
+        mUserId = in.readInt();
+        mComponentName = in.readParcelable(null, ComponentName.class);
+    }
+
+    @UserIdInt
+    public int getUserId() {
+        return mUserId;
+    }
+
+    public ComponentName getComponentName() {
+        return mComponentName;
+    }
+
+    @Override
+    public String toString() {
+        return mComponentName + " for user id: " + mUserId;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj != null && obj instanceof ComponentNameAndUser) {
+            ComponentNameAndUser other = (ComponentNameAndUser) obj;
+            return other.getUserId() == mUserId
+                    && Objects.equals(other.getComponentName(), mComponentName);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        if (mComponentName == null) {
+            return mUserId;
+        }
+        return mComponentName.hashCode() + mUserId;
+    }
+}
diff --git a/nfc/java/android/nfc/INfcCardEmulation.aidl b/nfc/java/android/nfc/INfcCardEmulation.aidl
index 8535e4a..5e2e92d 100644
--- a/nfc/java/android/nfc/INfcCardEmulation.aidl
+++ b/nfc/java/android/nfc/INfcCardEmulation.aidl
@@ -17,6 +17,8 @@
 package android.nfc;
 
 import android.content.ComponentName;
+import android.nfc.INfcEventListener;
+
 import android.nfc.cardemulation.AidGroup;
 import android.nfc.cardemulation.ApduServiceInfo;
 import android.os.RemoteCallback;
@@ -55,4 +57,7 @@
     boolean isAutoChangeEnabled();
     List<String> getRoutingStatus();
     void overwriteRoutingTable(int userHandle, String emptyAid, String protocol, String tech, String sc);
+
+    void registerNfcEventListener(in INfcEventListener listener);
+    void unregisterNfcEventListener(in INfcEventListener listener);
 }
diff --git a/nfc/java/android/nfc/INfcEventListener.aidl b/nfc/java/android/nfc/INfcEventListener.aidl
new file mode 100644
index 0000000..5162c26
--- /dev/null
+++ b/nfc/java/android/nfc/INfcEventListener.aidl
@@ -0,0 +1,11 @@
+package android.nfc;
+
+import android.nfc.ComponentNameAndUser;
+
+/**
+ * @hide
+ */
+oneway interface INfcEventListener {
+    void onPreferredServiceChanged(in ComponentNameAndUser ComponentNameAndUser);
+    void onObserveModeStateChanged(boolean isEnabled);
+}
\ No newline at end of file
diff --git a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
index 48c7ee6..7f1fd15 100644
--- a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
+++ b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
@@ -27,7 +27,7 @@
  * @hide
  */
 interface INfcOemExtensionCallback {
-   void onTagConnected(boolean connected, in Tag tag);
+   void onTagConnected(boolean connected);
    void onStateUpdated(int state);
    void onApplyRouting(in ResultReceiver isSkipped);
    void onNdefRead(in ResultReceiver isSkipped);
@@ -46,6 +46,7 @@
    void onCardEmulationActivated(boolean isActivated);
    void onRfFieldActivated(boolean isActivated);
    void onRfDiscoveryStarted(boolean isDiscoveryStarted);
+   void onEeListenActivated(boolean isActivated);
    void onGetOemAppSearchIntent(in List<String> firstPackage, in ResultReceiver intentConsumer);
    void onNdefMessage(in Tag tag, in NdefMessage message, in ResultReceiver hasOemExecutableContent);
    void onLaunchHceAppChooserActivity(in String selectedAid, in List<ApduServiceInfo> services, in ComponentName failedComponent, in String category);
diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java
index 474ff8c..1d2085c 100644
--- a/nfc/java/android/nfc/NfcOemExtension.java
+++ b/nfc/java/android/nfc/NfcOemExtension.java
@@ -80,6 +80,7 @@
     private boolean mCardEmulationActivated = false;
     private boolean mRfFieldActivated = false;
     private boolean mRfDiscoveryStarted = false;
+    private boolean mEeListenActivated = false;
 
     /**
      * Broadcast Action: Sent on NFC stack initialization when NFC OEM extensions are enabled.
@@ -195,9 +196,8 @@
          * ex - if tag is connected  notify cover and Nfctest app if app is in testing mode
          *
          * @param connected status of the tag true if tag is connected otherwise false
-         * @param tag Tag details
          */
-        void onTagConnected(boolean connected, @NonNull Tag tag);
+        void onTagConnected(boolean connected);
 
         /**
          * Update the Nfc Adapter State
@@ -327,6 +327,13 @@
         void onRfDiscoveryStarted(boolean isDiscoveryStarted);
 
         /**
+        * Notifies the NFCEE (NFC Execution Environment) Listen has been activated.
+        *
+        * @param isActivated true, if EE Listen is ON, else EE Listen is OFF.
+        */
+        void onEeListenActivated(boolean isActivated);
+
+        /**
          * Gets the intent to find the OEM package in the OEM App market. If the consumer returns
          * {@code null} or a timeout occurs, the intent from the first available package will be
          * used instead.
@@ -437,6 +444,7 @@
                 callback.onCardEmulationActivated(mCardEmulationActivated);
                 callback.onRfFieldActivated(mRfFieldActivated);
                 callback.onRfDiscoveryStarted(mRfDiscoveryStarted);
+                callback.onEeListenActivated(mEeListenActivated);
             });
         }
     }
@@ -684,9 +692,9 @@
     private final class NfcOemExtensionCallback extends INfcOemExtensionCallback.Stub {
 
         @Override
-        public void onTagConnected(boolean connected, Tag tag) throws RemoteException {
+        public void onTagConnected(boolean connected) throws RemoteException {
             mCallbackMap.forEach((cb, ex) ->
-                    handleVoid2ArgCallback(connected, tag, cb::onTagConnected, ex));
+                    handleVoidCallback(connected, cb::onTagConnected, ex));
         }
 
         @Override
@@ -711,6 +719,13 @@
         }
 
         @Override
+        public void onEeListenActivated(boolean isActivated) throws RemoteException {
+            mEeListenActivated = isActivated;
+            mCallbackMap.forEach((cb, ex) ->
+                    handleVoidCallback(isActivated, cb::onEeListenActivated, ex));
+        }
+
+        @Override
         public void onStateUpdated(int state) throws RemoteException {
             mCallbackMap.forEach((cb, ex) ->
                     handleVoidCallback(state, cb::onStateUpdated, ex));
diff --git a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
index d75318f..9ff83fe 100644
--- a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -52,10 +52,12 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.TreeMap;
 import java.util.regex.Pattern;
 
 /**
@@ -204,7 +206,8 @@
         this(info, onHost, description, staticAidGroups, dynamicAidGroups,
                 requiresUnlock, requiresScreenOn, bannerResource, uid,
                 settingsActivityName, offHost, staticOffHost, isEnabled,
-                new HashMap<String, Boolean>(), new HashMap<Pattern, Boolean>());
+                new HashMap<String, Boolean>(), new TreeMap<>(
+                        Comparator.comparing(Pattern::toString)));
     }
 
     /**
@@ -340,7 +343,8 @@
             mStaticAidGroups = new HashMap<String, AidGroup>();
             mDynamicAidGroups = new HashMap<String, AidGroup>();
             mAutoTransact = new HashMap<String, Boolean>();
-            mAutoTransactPatterns = new HashMap<Pattern, Boolean>();
+            mAutoTransactPatterns = new TreeMap<Pattern, Boolean>(
+                    Comparator.comparing(Pattern::toString));
             mOnHost = onHost;
 
             final int depth = parser.getDepth();
diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java
index d8f04c5..eb28c3b 100644
--- a/nfc/java/android/nfc/cardemulation/CardEmulation.java
+++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java
@@ -17,6 +17,7 @@
 package android.nfc.cardemulation;
 
 import android.Manifest;
+import android.annotation.CallbackExecutor;
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -33,15 +34,18 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.nfc.ComponentNameAndUser;
 import android.nfc.Constants;
 import android.nfc.Flags;
 import android.nfc.INfcCardEmulation;
+import android.nfc.INfcEventListener;
 import android.nfc.NfcAdapter;
 import android.os.Build;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
+import android.util.ArrayMap;
 import android.util.Log;
 
 import java.lang.annotation.Retention;
@@ -50,6 +54,8 @@
 import java.util.HexFormat;
 import java.util.List;
 import java.util.Locale;
+import java.util.Objects;
+import java.util.concurrent.Executor;
 import java.util.regex.Pattern;
 
 /**
@@ -1076,4 +1082,107 @@
             default -> throw new IllegalStateException("Unexpected value: " + route);
         };
     }
+
+    /** Listener for preferred service state changes. */
+    @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER)
+    public interface NfcEventListener {
+        /**
+         * This method is called when this package gains or loses preferred Nfc service status,
+         * either the Default Wallet Role holder (see {@link
+         * android.app.role.RoleManager#ROLE_WALLET}) or the preferred service of the foreground
+         * activity set with {@link #setPreferredService(Activity, ComponentName)}
+         *
+         * @param isPreferred true is this service has become the preferred Nfc service, false if it
+         *     is no longer the preferred service
+         */
+        @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER)
+        default void onPreferredServiceChanged(boolean isPreferred) {}
+
+        /**
+         * This method is called when observe mode has been enabled or disabled.
+         *
+         * @param isEnabled true if observe mode has been enabled, false if it has been disabled
+         */
+        @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER)
+        default void onObserveModeStateChanged(boolean isEnabled) {}
+    }
+
+    private final ArrayMap<NfcEventListener, Executor> mNfcEventListeners = new ArrayMap<>();
+
+    final INfcEventListener mINfcEventListener =
+            new INfcEventListener.Stub() {
+                public void onPreferredServiceChanged(ComponentNameAndUser componentNameAndUser) {
+                    if (!android.nfc.Flags.nfcEventListener()) {
+                        return;
+                    }
+                    boolean isPreferred =
+                            componentNameAndUser != null
+                                    && componentNameAndUser.getUserId()
+                                            == mContext.getUser().getIdentifier()
+                                    && componentNameAndUser.getComponentName() != null
+                                    && Objects.equals(
+                                            mContext.getPackageName(),
+                                            componentNameAndUser.getComponentName()
+                                                    .getPackageName());
+                    synchronized (mNfcEventListeners) {
+                        mNfcEventListeners.forEach(
+                                (listener, executor) -> {
+                                    executor.execute(
+                                            () -> listener.onPreferredServiceChanged(isPreferred));
+                                });
+                    }
+                }
+
+                public void onObserveModeStateChanged(boolean isEnabled) {
+                    if (!android.nfc.Flags.nfcEventListener()) {
+                        return;
+                    }
+                    synchronized (mNfcEventListeners) {
+                        mNfcEventListeners.forEach(
+                                (listener, executor) -> {
+                                    executor.execute(
+                                            () -> listener.onObserveModeStateChanged(isEnabled));
+                                });
+                    }
+                }
+            };
+
+    /**
+     * Register a listener for NFC Events.
+     *
+     * @param executor The Executor to run the call back with
+     * @param listener The listener to register
+     */
+    @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER)
+    public void registerNfcEventListener(
+            @NonNull @CallbackExecutor Executor executor, @NonNull NfcEventListener listener) {
+        if (!android.nfc.Flags.nfcEventListener()) {
+            return;
+        }
+        synchronized (mNfcEventListeners) {
+            mNfcEventListeners.put(listener, executor);
+            if (mNfcEventListeners.size() == 1) {
+                callService(() -> sService.registerNfcEventListener(mINfcEventListener));
+            }
+        }
+    }
+
+    /**
+     * Unregister a preferred service listener that was previously registered with {@link
+     * #registerNfcEventListener(Executor, NfcEventListener)}
+     *
+     * @param listener The previously registered listener to unregister
+     */
+    @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER)
+    public void unregisterNfcEventListener(@NonNull NfcEventListener listener) {
+        if (!android.nfc.Flags.nfcEventListener()) {
+            return;
+        }
+        synchronized (mNfcEventListeners) {
+            mNfcEventListeners.remove(listener);
+            if (mNfcEventListeners.size() == 0) {
+                callService(() -> sService.unregisterNfcEventListener(mINfcEventListener));
+            }
+        }
+    }
 }
diff --git a/nfc/java/android/nfc/cardemulation/HostApduService.java b/nfc/java/android/nfc/cardemulation/HostApduService.java
index cd8e19c..4f601f0 100644
--- a/nfc/java/android/nfc/cardemulation/HostApduService.java
+++ b/nfc/java/android/nfc/cardemulation/HostApduService.java
@@ -239,15 +239,6 @@
      */
     public static final int MSG_POLLING_LOOP = 4;
 
-    /**
-     * @hide
-     */
-    public static final int MSG_OBSERVE_MODE_CHANGE = 5;
-
-    /**
-     * @hide
-     */
-    public static final int MSG_PREFERRED_SERVICE_CHANGED = 6;
 
     /**
      * @hide
@@ -343,16 +334,6 @@
                         processPollingFrames(pollingFrames);
                     }
                     break;
-                case MSG_OBSERVE_MODE_CHANGE:
-                    if (android.nfc.Flags.nfcEventListener()) {
-                        onObserveModeStateChanged(msg.arg1 == 1);
-                    }
-                    break;
-                case MSG_PREFERRED_SERVICE_CHANGED:
-                    if (android.nfc.Flags.nfcEventListener()) {
-                        onPreferredServiceChanged(msg.arg1 == 1);
-                    }
-                    break;
                 default:
                 super.handleMessage(msg);
             }
@@ -462,25 +443,4 @@
      */
     public abstract void onDeactivated(int reason);
 
-
-    /**
-     * This method is called when this service is the preferred Nfc service and
-     * Observe mode has been enabled or disabled.
-     *
-     * @param isEnabled true if observe mode has been enabled, false if it has been disabled
-     */
-    @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER)
-    public void onObserveModeStateChanged(boolean isEnabled) {
-
-    }
-
-    /**
-     * This method is called when this service gains or loses preferred Nfc service status.
-     *
-     * @param isPreferred true is this service has become the preferred Nfc service,
-     * false if it is no longer the preferred service
-     */
-    @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER)
-    public void onPreferredServiceChanged(boolean isPreferred) {
-    }
 }
diff --git a/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java
index f1103e1..992f581 100644
--- a/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java
+++ b/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java
@@ -896,7 +896,8 @@
         int systemUserId = UserHandle.SYSTEM.getIdentifier();
         int[] userIds = { systemUserId };
         try {
-            for (File file : FileUtils.listFilesOrEmpty(Environment.getDataSystemDeDirectory())) {
+            for (File file : FileUtils.listFilesOrEmpty(
+                    Environment.getDataSystemDeviceProtectedDirectory())) {
                 try {
                     final int userId = Integer.parseInt(file.getName());
                     if (userId != systemUserId) {
diff --git a/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index 8277e57..311def8 100644
--- a/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -499,8 +499,8 @@
         // Check if the package is listed among the system modules or is an
         // APK inside an updatable APEX.
         try {
-            final PackageInfo pkg = mContext.getPackageManager()
-                    .getPackageInfo(packageName, 0 /* flags */);
+            PackageManager pm = mContext.getPackageManager();
+            final PackageInfo pkg = pm.getPackageInfo(packageName, 0 /* flags */);
             String apexPackageName = pkg.getApexPackageName();
             if (apexPackageName != null) {
                 packageName = apexPackageName;
diff --git a/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java
index 2acedd5..be339cd 100644
--- a/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java
+++ b/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java
@@ -300,6 +300,31 @@
         sPackageWatchdog = this;
     }
 
+    /**
+     * Creating this temp constructor to match module constructor.
+     * Note: To be only used in tests.
+     * Creates a PackageWatchdog that allows injecting dependencies,
+     * except for connectivity module connector.
+     */
+    @VisibleForTesting
+    PackageWatchdog(Context context, AtomicFile policyFile, Handler shortTaskHandler,
+            Handler longTaskHandler, ExplicitHealthCheckController controller,
+            SystemClock clock) {
+        mContext = context;
+        mPolicyFile = policyFile;
+        mShortTaskHandler = shortTaskHandler;
+        mLongTaskHandler = longTaskHandler;
+        mHealthCheckController = controller;
+        mConnectivityModuleConnector = ConnectivityModuleConnector.getInstance();
+        mSystemClock = clock;
+        mNumberOfNativeCrashPollsRemaining = NUMBER_OF_NATIVE_CRASH_POLLS;
+        mBootThreshold = new BootThreshold(DEFAULT_BOOT_LOOP_TRIGGER_COUNT,
+                DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS);
+
+        loadFromFile();
+        sPackageWatchdog = this;
+    }
+
     /** Creates or gets singleton instance of PackageWatchdog. */
     public static  @NonNull PackageWatchdog getInstance(@NonNull Context context) {
         synchronized (sPackageWatchdogLock) {
diff --git a/packages/SettingsLib/Graph/graph.proto b/packages/SettingsLib/Graph/graph.proto
index cbe602e..6b93cd7 100644
--- a/packages/SettingsLib/Graph/graph.proto
+++ b/packages/SettingsLib/Graph/graph.proto
@@ -77,6 +77,8 @@
   // Intent to show and locate the preference (might have highlight animation on
   // the preference).
   optional IntentProto launch_intent = 14;
+  // Descriptor of the preference value.
+  optional PreferenceValueDescriptorProto value_descriptor = 15;
 
   // Target of an Intent
   message ActionTarget {
@@ -103,9 +105,28 @@
 message PreferenceValueProto {
   oneof value {
     bool boolean_value = 1;
+    int32 int_value = 2;
   }
 }
 
+// Proto of preference value descriptor.
+message PreferenceValueDescriptorProto {
+  oneof type {
+    bool boolean_type = 1;
+    RangeValueProto range_value = 2;
+  }
+}
+
+// Proto of preference value that is between a range.
+message RangeValueProto {
+  // The lower bound (inclusive) of the range.
+  optional int32 min = 1;
+  // The upper bound (inclusive) of the range.
+  optional int32 max = 2;
+  // The increment step within the range. 0 means unset, which implies step size is 1.
+  optional int32 step = 3;
+}
+
 // Proto of android.content.Intent
 message IntentProto {
   // The action of the Intent.
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt
index fdffe5d..5ceee6d 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt
@@ -65,6 +65,7 @@
     val visitedScreens: Set<String> = setOf(),
     val locale: Locale? = null,
     val includeValue: Boolean = true,
+    val includeValueDescriptor: Boolean = true,
 )
 
 object GetPreferenceGraphRequestCodec : MessageCodec<GetPreferenceGraphRequest> {
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
index cf6bf70..2256bb3 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
@@ -49,6 +49,7 @@
 import com.android.settingslib.metadata.PreferenceScreenRegistry
 import com.android.settingslib.metadata.PreferenceSummaryProvider
 import com.android.settingslib.metadata.PreferenceTitleProvider
+import com.android.settingslib.metadata.RangeValue
 import com.android.settingslib.preference.PreferenceScreenFactory
 import com.android.settingslib.preference.PreferenceScreenProvider
 import java.util.Locale
@@ -66,6 +67,7 @@
     private val builder by lazy { PreferenceGraphProto.newBuilder() }
     private val visitedScreens = mutableSetOf<String>().apply { addAll(request.visitedScreens) }
     private val includeValue = request.includeValue
+    private val includeValueDescriptor = request.includeValueDescriptor
 
     private suspend fun init() {
         for (key in request.screenKeys) {
@@ -284,14 +286,37 @@
             restricted = metadata.isRestricted(context)
         }
         persistent = metadata.isPersistent(context)
-        if (
-            includeValue &&
-                persistent &&
-                metadata is BooleanValue &&
-                metadata is PersistentPreference<*>
-        ) {
-            metadata.storage(context).getValue(metadata.key, Boolean::class.javaObjectType)?.let {
-                value = preferenceValueProto { booleanValue = it }
+        if (persistent) {
+            if (includeValue && metadata is PersistentPreference<*>) {
+                value = preferenceValueProto {
+                    when (metadata) {
+                        is BooleanValue ->
+                            metadata
+                                .storage(context)
+                                .getValue(metadata.key, Boolean::class.javaObjectType)
+                                ?.let { booleanValue = it }
+                        is RangeValue -> {
+                            metadata
+                                .storage(context)
+                                .getValue(metadata.key, Int::class.javaObjectType)
+                                ?.let { intValue = it }
+                        }
+                        else -> {}
+                    }
+                }
+            }
+            if (includeValueDescriptor) {
+                valueDescriptor = preferenceValueDescriptorProto {
+                    when (metadata) {
+                        is BooleanValue -> booleanType = true
+                        is RangeValue -> rangeValue = rangeValueProto {
+                                min = metadata.getMinValue(context)
+                                max = metadata.getMaxValue(context)
+                                step = metadata.getIncrementStep(context)
+                            }
+                        else -> {}
+                    }
+                }
             }
         }
         if (metadata is PreferenceScreenMetadata) {
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt
index d8db1bb..6e4db1d 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt
@@ -27,8 +27,10 @@
 import com.android.settingslib.metadata.BooleanValue
 import com.android.settingslib.metadata.PersistentPreference
 import com.android.settingslib.metadata.PreferenceAvailabilityProvider
+import com.android.settingslib.metadata.PreferenceMetadata
 import com.android.settingslib.metadata.PreferenceRestrictionProvider
 import com.android.settingslib.metadata.PreferenceScreenRegistry
+import com.android.settingslib.metadata.RangeValue
 import com.android.settingslib.metadata.ReadWritePermit
 
 /** Request to set preference value. */
@@ -114,27 +116,39 @@
         if (metadata is PreferenceAvailabilityProvider && !metadata.isAvailable(application)) {
             return PreferenceSetterResult.UNAVAILABLE
         }
+
+        fun <T> PreferenceMetadata.checkWritePermit(value: T): Int {
+            @Suppress("UNCHECKED_CAST") val preference = (this as PersistentPreference<T>)
+            return when (preference.getWritePermit(application, value, myUid, callingUid)) {
+                ReadWritePermit.ALLOW -> PreferenceSetterResult.OK
+                ReadWritePermit.DISALLOW -> PreferenceSetterResult.DISALLOW
+                ReadWritePermit.REQUIRE_APP_PERMISSION ->
+                    PreferenceSetterResult.REQUIRE_APP_PERMISSION
+                ReadWritePermit.REQUIRE_USER_AGREEMENT ->
+                    PreferenceSetterResult.REQUIRE_USER_AGREEMENT
+                else -> PreferenceSetterResult.INTERNAL_ERROR
+            }
+        }
+
         val storage = metadata.storage(application)
         val value = request.value
         try {
             if (value.hasBooleanValue()) {
                 if (metadata !is BooleanValue) return PreferenceSetterResult.INVALID_REQUEST
                 val booleanValue = value.booleanValue
-                @Suppress("UNCHECKED_CAST")
-                val booleanPreference = metadata as PersistentPreference<Boolean>
-                when (
-                    booleanPreference.getWritePermit(application, booleanValue, myUid, callingUid)
-                ) {
-                    ReadWritePermit.ALLOW -> {}
-                    ReadWritePermit.DISALLOW -> return PreferenceSetterResult.DISALLOW
-                    ReadWritePermit.REQUIRE_APP_PERMISSION ->
-                        return PreferenceSetterResult.REQUIRE_APP_PERMISSION
-                    ReadWritePermit.REQUIRE_USER_AGREEMENT ->
-                        return PreferenceSetterResult.REQUIRE_USER_AGREEMENT
-                    else -> return PreferenceSetterResult.INTERNAL_ERROR
-                }
+                val resultCode = metadata.checkWritePermit(booleanValue)
+                if (resultCode != PreferenceSetterResult.OK) return resultCode
                 storage.setValue(key, Boolean::class.javaObjectType, booleanValue)
                 return PreferenceSetterResult.OK
+            } else if (value.hasIntValue()) {
+                val intValue = value.intValue
+                val resultCode = metadata.checkWritePermit(intValue)
+                if (resultCode != PreferenceSetterResult.OK) return resultCode
+                if (metadata is RangeValue && !metadata.isValidValue(application, intValue)) {
+                    return PreferenceSetterResult.INVALID_REQUEST
+                }
+                storage.setValue(key, Int::class.javaObjectType, intValue)
+                return PreferenceSetterResult.OK
             }
         } catch (e: Exception) {
             return PreferenceSetterResult.INTERNAL_ERROR
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt
index d7dae77..dee32d9 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt
@@ -24,7 +24,9 @@
 import com.android.settingslib.graph.proto.PreferenceProto
 import com.android.settingslib.graph.proto.PreferenceProto.ActionTarget
 import com.android.settingslib.graph.proto.PreferenceScreenProto
+import com.android.settingslib.graph.proto.PreferenceValueDescriptorProto
 import com.android.settingslib.graph.proto.PreferenceValueProto
+import com.android.settingslib.graph.proto.RangeValueProto
 import com.android.settingslib.graph.proto.TextProto
 
 /** Returns root or null. */
@@ -89,6 +91,16 @@
 inline fun preferenceValueProto(init: PreferenceValueProto.Builder.() -> Unit) =
     PreferenceValueProto.newBuilder().also(init).build()
 
+/** Kotlin DSL-style builder for [PreferenceValueDescriptorProto]. */
+@JvmSynthetic
+inline fun preferenceValueDescriptorProto(init: PreferenceValueDescriptorProto.Builder.() -> Unit) =
+    PreferenceValueDescriptorProto.newBuilder().also(init).build()
+
+/** Kotlin DSL-style builder for [RangeValueProto]. */
+@JvmSynthetic
+inline fun rangeValueProto(init: RangeValueProto.Builder.() -> Unit) =
+    RangeValueProto.newBuilder().also(init).build()
+
 /** Kotlin DSL-style builder for [TextProto]. */
 @JvmSynthetic
 inline fun textProto(init: TextProto.Builder.() -> Unit) = TextProto.newBuilder().also(init).build()
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
index 72cf403..49acc1d 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
@@ -115,7 +115,7 @@
     fun isFlagEnabled(context: Context): Boolean = true
 
     val preferenceBindingFactory: PreferenceBindingFactory
-        get() = DefaultPreferenceBindingFactory
+        get() = PreferenceBindingFactory.defaultFactory
 
     override fun createPreferenceScreen(factory: PreferenceScreenFactory) =
         factory.getOrCreatePreferenceScreen().apply {
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt
index 87c289f..51b9aac 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt
@@ -45,10 +45,15 @@
 
     /** Returns the [PreferenceBinding] associated with the [PreferenceMetadata]. */
     fun getPreferenceBinding(metadata: PreferenceMetadata): PreferenceBinding?
+
+    companion object {
+        /** Default preference binding factory. */
+        @JvmStatic var defaultFactory: PreferenceBindingFactory = DefaultPreferenceBindingFactory()
+    }
 }
 
 /** Default [PreferenceBindingFactory]. */
-object DefaultPreferenceBindingFactory : PreferenceBindingFactory {
+open class DefaultPreferenceBindingFactory : PreferenceBindingFactory {
 
     override fun getPreferenceBinding(metadata: PreferenceMetadata) =
         metadata as? PreferenceBinding
@@ -66,5 +71,6 @@
     PreferenceBindingFactory {
 
     override fun getPreferenceBinding(metadata: PreferenceMetadata) =
-        bindings[metadata.key] ?: DefaultPreferenceBindingFactory.getPreferenceBinding(metadata)
+        bindings[metadata.key]
+            ?: PreferenceBindingFactory.defaultFactory.getPreferenceBinding(metadata)
 }
diff --git a/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/PreferenceBindingTestUtils.kt b/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/PreferenceBindingTestUtils.kt
index f3142d0..00bad52 100644
--- a/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/PreferenceBindingTestUtils.kt
+++ b/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/PreferenceBindingTestUtils.kt
@@ -25,7 +25,7 @@
 /** Creates [Preference] widget and binds with metadata. */
 @VisibleForTesting
 fun <P : Preference> PreferenceMetadata.createAndBindWidget(context: Context): P {
-    val binding = DefaultPreferenceBindingFactory.getPreferenceBinding(this)
+    val binding = PreferenceBindingFactory.defaultFactory.getPreferenceBinding(this)!!
     return (binding.createWidget(context) as P).also {
         if (this is PersistentPreference<*>) {
             storage(context)?.let { keyValueStore ->
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml
index cc42dab..944bef6 100644
--- a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml
@@ -21,6 +21,7 @@
     android:layout_height="wrap_content"
     android:layout_weight="1"
     android:paddingVertical="@dimen/settingslib_expressive_space_small1"
+    android:paddingHorizontal="@dimen/settingslib_expressive_space_small1"
     android:filterTouchesWhenObscured="false">
 
     <TextView
@@ -40,7 +41,6 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_below="@android:id/title"
-        android:layout_alignLeft="@android:id/title"
         android:layout_alignStart="@android:id/title"
         android:layout_gravity="start"
         android:textAlignment="viewStart"
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedDropDownPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedDropDownPreference.java
index 6578eb7..c36ade9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedDropDownPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedDropDownPreference.java
@@ -22,14 +22,20 @@
 import androidx.preference.DropDownPreference;
 import androidx.preference.PreferenceViewHolder;
 
-public class RestrictedDropDownPreference extends DropDownPreference {
-    RestrictedPreferenceHelper mHelper;
+public class RestrictedDropDownPreference extends DropDownPreference implements
+        RestrictedPreferenceHelperProvider {
+    private final RestrictedPreferenceHelper mHelper;
 
     public RestrictedDropDownPreference(@NonNull Context context) {
         super(context);
         mHelper = new RestrictedPreferenceHelper(context, this, null);
     }
 
+    @Override
+    public @NonNull RestrictedPreferenceHelper getRestrictedPreferenceHelper() {
+        return mHelper;
+    }
+
     /**
      * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this
      * package. Marks the preference as disabled if so.
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedInterface.kt b/packages/SettingsLib/src/com/android/settingslib/RestrictedInterface.kt
deleted file mode 100644
index 14f9a19..0000000
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedInterface.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2024 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
-
-import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
-
-interface RestrictedInterface {
-    fun useAdminDisabledSummary(useSummary: Boolean)
-
-    fun checkRestrictionAndSetDisabled(userRestriction: String)
-
-    fun checkRestrictionAndSetDisabled(userRestriction: String, userId: Int)
-
-    /**
-     * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this
-     * package. Marks the preference as disabled if so.
-     *
-     * @param settingIdentifier The key identifying the setting
-     * @param packageName       the package to check the settingIdentifier for
-     */
-    fun checkEcmRestrictionAndSetDisabled(
-        settingIdentifier: String,
-        packageName: String
-    )
-
-    val isDisabledByAdmin: Boolean
-
-    fun setDisabledByAdmin(admin: EnforcedAdmin?)
-
-    val isDisabledByEcm: Boolean
-
-    val uid: Int
-
-    val packageName: String?
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java
index 495410b..332042a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java
@@ -34,7 +34,9 @@
  * Preference class that supports being disabled by a user restriction
  * set by a device admin.
  */
-public class RestrictedPreference extends TwoTargetPreference {
+public class RestrictedPreference extends TwoTargetPreference implements
+        RestrictedPreferenceHelperProvider {
+
     RestrictedPreferenceHelper mHelper;
 
     public RestrictedPreference(Context context, AttributeSet attrs,
@@ -67,6 +69,11 @@
     }
 
     @Override
+    public @NonNull RestrictedPreferenceHelper getRestrictedPreferenceHelper() {
+        return mHelper;
+    }
+
+    @Override
     public void onBindViewHolder(PreferenceViewHolder holder) {
         super.onBindViewHolder(holder);
         mHelper.onBindViewHolder(holder);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelperProvider.kt
similarity index 68%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelperProvider.kt
index 94b2bdf..f286084 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelperProvider.kt
@@ -14,9 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util.settings
+package com.android.settingslib
 
-import com.android.systemui.kosmos.Kosmos
+/** Provider of [RestrictedPreferenceHelper]. */
+interface RestrictedPreferenceHelperProvider {
 
-val Kosmos.userAwareSecureSettingsRepository by
-    Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+    /** Returns the [RestrictedPreferenceHelper] applied to the preference. */
+    fun getRestrictedPreferenceHelper(): RestrictedPreferenceHelper
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSelectorWithWidgetPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSelectorWithWidgetPreference.java
index c52c7ea..573869d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSelectorWithWidgetPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSelectorWithWidgetPreference.java
@@ -32,8 +32,9 @@
 /**
  * Selector with widget preference that can be disabled by a device admin using a user restriction.
  */
-public class RestrictedSelectorWithWidgetPreference extends SelectorWithWidgetPreference {
-    private RestrictedPreferenceHelper mHelper;
+public class RestrictedSelectorWithWidgetPreference extends SelectorWithWidgetPreference implements
+        RestrictedPreferenceHelperProvider {
+    private final RestrictedPreferenceHelper mHelper;
 
     /**
      * Perform inflation from XML and apply a class-specific base style.
@@ -82,8 +83,11 @@
      */
     public RestrictedSelectorWithWidgetPreference(@NonNull Context context) {
         this(context, null);
-        mHelper =
-                new RestrictedPreferenceHelper(context, /* preference= */ this, /* attrs= */ null);
+    }
+
+    @Override
+    public @NonNull RestrictedPreferenceHelper getRestrictedPreferenceHelper() {
+        return mHelper;
     }
 
     @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSliderPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSliderPreference.java
index 1dc5281..b690816 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSliderPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSliderPreference.java
@@ -19,7 +19,6 @@
 import android.annotation.SuppressLint;
 import android.content.Context;
 import android.os.Process;
-import android.os.UserHandle;
 import android.util.AttributeSet;
 
 import androidx.annotation.NonNull;
@@ -34,8 +33,10 @@
  * Slide Preference that supports being disabled by a user restriction
  * set by a device admin.
  */
-public class RestrictedSliderPreference extends SliderPreference implements RestrictedInterface {
-    RestrictedPreferenceHelper mHelper;
+public class RestrictedSliderPreference extends SliderPreference implements
+        RestrictedPreferenceHelperProvider {
+
+    private final RestrictedPreferenceHelper mHelper;
 
     public RestrictedSliderPreference(@NonNull Context context, @Nullable AttributeSet attrs,
             int defStyleAttr, @Nullable String packageName, int uid) {
@@ -66,6 +67,11 @@
     }
 
     @Override
+    public @NonNull RestrictedPreferenceHelper getRestrictedPreferenceHelper() {
+        return mHelper;
+    }
+
+    @Override
     public void onBindViewHolder(@NonNull PreferenceViewHolder holder) {
         super.onBindViewHolder(holder);
         mHelper.onBindViewHolder(holder);
@@ -81,12 +87,12 @@
 
     @Override
     public void setEnabled(boolean enabled) {
-        if (enabled && isDisabledByAdmin()) {
+        if (enabled && mHelper.isDisabledByAdmin()) {
             mHelper.setDisabledByAdmin(null);
             return;
         }
 
-        if (enabled && isDisabledByEcm()) {
+        if (enabled && mHelper.isDisabledByEcm()) {
             mHelper.setDisabledByEcm(null);
             return;
         }
@@ -95,55 +101,6 @@
     }
 
     @Override
-    public void useAdminDisabledSummary(boolean useSummary) {
-        mHelper.useAdminDisabledSummary(useSummary);
-    }
-
-    @Override
-    public void checkRestrictionAndSetDisabled(@NonNull String userRestriction) {
-        mHelper.checkRestrictionAndSetDisabled(userRestriction, UserHandle.myUserId());
-    }
-
-    @Override
-    public void checkRestrictionAndSetDisabled(@NonNull String userRestriction, int userId) {
-        mHelper.checkRestrictionAndSetDisabled(userRestriction, userId);
-    }
-
-    @Override
-    public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier,
-            @NonNull String packageName) {
-        mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName);
-    }
-
-    @Override
-    public void setDisabledByAdmin(@Nullable RestrictedLockUtils.EnforcedAdmin admin) {
-        if (mHelper.setDisabledByAdmin(admin)) {
-            notifyChanged();
-        }
-    }
-
-    @Override
-    public boolean isDisabledByAdmin() {
-        return mHelper.isDisabledByAdmin();
-    }
-
-    @Override
-    public boolean isDisabledByEcm() {
-        return mHelper.isDisabledByEcm();
-    }
-
-    @Override
-    public int getUid() {
-        return mHelper != null ? mHelper.uid : Process.INVALID_UID;
-    }
-
-    @Override
-    @Nullable
-    public String getPackageName() {
-        return mHelper != null ? mHelper.packageName : null;
-    }
-
-    @Override
     protected void onAttachedToHierarchy(@NonNull PreferenceManager preferenceManager) {
         mHelper.onAttachedToHierarchy();
         super.onAttachedToHierarchy(preferenceManager);
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
index fffbb54..727dbe1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
@@ -45,8 +45,9 @@
  * Version of SwitchPreferenceCompat that can be disabled by a device admin
  * using a user restriction.
  */
-public class RestrictedSwitchPreference extends SwitchPreferenceCompat {
-    RestrictedPreferenceHelper mHelper;
+public class RestrictedSwitchPreference extends SwitchPreferenceCompat implements
+        RestrictedPreferenceHelperProvider {
+    private final RestrictedPreferenceHelper mHelper;
     AppOpsManager mAppOpsManager;
     boolean mUseAdditionalSummary = false;
     CharSequence mRestrictedSwitchSummary;
@@ -98,6 +99,11 @@
         this(context, null);
     }
 
+    @Override
+    public @NonNull RestrictedPreferenceHelper getRestrictedPreferenceHelper() {
+        return mHelper;
+    }
+
     @VisibleForTesting
     public void setAppOps(AppOpsManager appOps) {
         mAppOpsManager = appOps;
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedTopLevelPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedTopLevelPreference.java
index 0096015..34e4d8f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedTopLevelPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedTopLevelPreference.java
@@ -22,14 +22,16 @@
 import android.os.UserHandle;
 import android.util.AttributeSet;
 
+import androidx.annotation.NonNull;
 import androidx.core.content.res.TypedArrayUtils;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceManager;
 import androidx.preference.PreferenceViewHolder;
 
 /** Top level preference that can be disabled by a device admin using a user restriction. */
-public class RestrictedTopLevelPreference extends Preference {
-    private RestrictedPreferenceHelper mHelper;
+public class RestrictedTopLevelPreference extends Preference implements
+        RestrictedPreferenceHelperProvider {
+    private final RestrictedPreferenceHelper mHelper;
 
     public RestrictedTopLevelPreference(Context context, AttributeSet attrs,
             int defStyleAttr, int defStyleRes) {
@@ -51,6 +53,11 @@
     }
 
     @Override
+    public @NonNull RestrictedPreferenceHelper getRestrictedPreferenceHelper() {
+        return mHelper;
+    }
+
+    @Override
     public void onBindViewHolder(PreferenceViewHolder holder) {
         super.onBindViewHolder(holder);
         mHelper.onBindViewHolder(holder);
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiver.kt b/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiver.kt
index c3b1a7c..aeea8cb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiver.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiver.kt
@@ -24,6 +24,7 @@
 import android.util.Log
 import com.android.settingslib.volume.shared.model.AudioManagerEvent
 import com.android.settingslib.volume.shared.model.AudioStream
+import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.SharedFlow
@@ -31,6 +32,7 @@
 import kotlinx.coroutines.flow.callbackFlow
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.mapNotNull
 import kotlinx.coroutines.flow.shareIn
 import kotlinx.coroutines.launch
@@ -44,6 +46,7 @@
 class AudioManagerEventsReceiverImpl(
     private val context: Context,
     coroutineScope: CoroutineScope,
+    backgroundCoroutineContext: CoroutineContext,
 ) : AudioManagerEventsReceiver {
 
     private val allActions: Collection<String>
@@ -79,6 +82,7 @@
             .filterNotNull()
             .filter { intent -> allActions.contains(intent.action) }
             .mapNotNull { it.toAudioManagerEvent() }
+            .flowOn(backgroundCoroutineContext)
             .shareIn(coroutineScope, SharingStarted.WhileSubscribed())
 
     private fun Intent.toAudioManagerEvent(): AudioManagerEvent? {
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiverTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiverTest.kt
index 35ee828..58a09fb 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiverTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiverTest.kt
@@ -60,7 +60,12 @@
     fun setup() {
         MockitoAnnotations.initMocks(this)
 
-        underTest = AudioManagerEventsReceiverImpl(context, testScope.backgroundScope)
+        underTest =
+            AudioManagerEventsReceiverImpl(
+                context,
+                testScope.backgroundScope,
+                testScope.testScheduler,
+            )
     }
 
     @Test
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 1919572..2c8c261 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -715,6 +715,9 @@
     <uses-permission android:name="android.permission.UWB_PRIVILEGED" />
     <uses-permission android:name="android.permission.UWB_RANGING" />
 
+    <!-- Permission required for CTS test - CtsRangingTestCases -->
+    <uses-permission android:name="android.permission.RANGING" />
+
     <!-- Permission required for CTS test - CtsAlarmManagerTestCases -->
     <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
 
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index a18b6c1..bffda8b 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -536,6 +536,8 @@
         "androidx.room_room-runtime",
         "androidx.room_room-ktx",
         "androidx.datastore_datastore-preferences",
+        "androidx.media3.media3-common",
+        "androidx.media3.media3-session",
         "com.google.android.material_material",
         "device_state_flags_lib",
         "kotlinx_coroutines_android",
@@ -703,6 +705,8 @@
         "androidx.room_room-testing",
         "androidx.room_room-ktx",
         "androidx.datastore_datastore-preferences",
+        "androidx.media3.media3-common",
+        "androidx.media3.media3-session",
         "device_state_flags_lib",
         "kotlinx-coroutines-android",
         "kotlinx-coroutines-core",
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index f0e1b43..e011736 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -1013,6 +1013,7 @@
             android:autoRemoveFromRecents="true"
             android:launchMode="singleTop"
             android:showForAllUsers="true"
+            android:turnScreenOn="true"
             android:exported="false">
         </activity>
 
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 5c5edb1..3bf3e24 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -26,6 +26,16 @@
 }
 
 flag {
+   name: "user_encrypted_source"
+   namespace: "systemui"
+   description: "Get rid of the local cache and rely on UserManager.isUserUnlocked directly to determine whether user CE storage is encrypted."
+   bug: "333656491"
+   metadata {
+        purpose: PURPOSE_BUGFIX
+   }
+}
+
+flag {
    name: "modes_ui_dialog_paging"
    namespace: "systemui"
    description: "Add pagination to the Modes dialog in quick settings."
@@ -468,6 +478,15 @@
 }
 
 flag {
+    name: "status_bar_notification_chips_test"
+    namespace: "systemui"
+    description: "Flag to enable certain features that let us test the status bar notification "
+        "chips with teamfooders. This flag should *never* be released to trunkfood or nextfood."
+    bug: "361346412"
+}
+
+
+flag {
     name: "compose_bouncer"
     namespace: "systemui"
     description: "Use the new compose bouncer in SystemUI"
@@ -1515,6 +1534,16 @@
 }
 
 flag {
+  namespace: "systemui"
+  name: "user_aware_settings_repositories"
+  description: "Provide user-aware versions of SecureSettingsRepository and SystemSettingsRepository in SystemUI modules (see doc linked from b/356099784)."
+  bug: "356099784"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
     name: "notify_password_text_view_user_activity_in_background"
     namespace: "systemui"
     description: "Decide whether to notify the user activity in password text view, to power manager in the background thread."
@@ -1559,6 +1588,13 @@
 }
 
 flag {
+   name: "show_clipboard_indication"
+   namespace: "systemui"
+   description: "Show indication text under the clipboard overlay when copied something"
+   bug: "361199935"
+}
+
+flag {
    name: "media_projection_dialog_behind_lockscreen"
    namespace: "systemui"
    description: "Ensure MediaProjection Dialog appears behind the lockscreen"
@@ -1707,4 +1743,3 @@
     description: "An implementation of shortcut customizations through shortcut helper."
     bug: "365064144"
 }
-
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
index 4cf2642..fdb4871 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
@@ -20,6 +20,7 @@
 import android.animation.AnimatorListenerAdapter
 import android.animation.ValueAnimator
 import android.content.Context
+import android.graphics.PointF
 import android.graphics.PorterDuff
 import android.graphics.PorterDuffXfermode
 import android.graphics.drawable.GradientDrawable
@@ -33,13 +34,13 @@
 import android.view.animation.Interpolator
 import android.window.WindowAnimationState
 import com.android.app.animation.Interpolators.LINEAR
-import com.android.app.animation.MathUtils.max
 import com.android.internal.annotations.VisibleForTesting
 import com.android.internal.dynamicanimation.animation.SpringAnimation
 import com.android.internal.dynamicanimation.animation.SpringForce
 import com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary
 import java.util.concurrent.Executor
 import kotlin.math.abs
+import kotlin.math.max
 import kotlin.math.min
 import kotlin.math.roundToInt
 
@@ -91,6 +92,14 @@
             )
         }
 
+        /**
+         * Similar to [getProgress] above, bug the delay and duration are expressed as percentages
+         * of the animation duration (between 0f and 1f).
+         */
+        internal fun getProgress(linearProgress: Float, delay: Float, duration: Float): Float {
+            return getProgressInternal(totalDuration = 1f, linearProgress, delay, duration)
+        }
+
         private fun getProgressInternal(
             totalDuration: Float,
             linearProgress: Float,
@@ -262,10 +271,10 @@
         var centerY: Float,
         var scale: Float = 0f,
 
-        // Cached values.
-        var previousCenterX: Float = -1f,
-        var previousCenterY: Float = -1f,
-        var previousScale: Float = -1f,
+        // Update flags (used to decide whether it's time to update the transition state).
+        var isCenterXUpdated: Boolean = false,
+        var isCenterYUpdated: Boolean = false,
+        var isScaleUpdated: Boolean = false,
 
         // Completion flags.
         var isCenterXDone: Boolean = false,
@@ -286,6 +295,7 @@
 
             override fun setValue(state: SpringState, value: Float) {
                 state.centerX = value
+                state.isCenterXUpdated = true
             }
         },
         CENTER_Y {
@@ -295,6 +305,7 @@
 
             override fun setValue(state: SpringState, value: Float) {
                 state.centerY = value
+                state.isCenterYUpdated = true
             }
         },
         SCALE {
@@ -304,6 +315,7 @@
 
             override fun setValue(state: SpringState, value: Float) {
                 state.scale = value
+                state.isScaleUpdated = true
             }
         };
 
@@ -444,8 +456,8 @@
      * punching a hole in the [transition container][Controller.transitionContainer]) iff [drawHole]
      * is true.
      *
-     * If [useSpring] is true, a multi-spring animation will be used instead of the default
-     * interpolators.
+     * If [startVelocity] (expressed in pixels per second) is not null, a multi-spring animation
+     * using it for the initial momentum will be used instead of the default interpolators.
      */
     fun startAnimation(
         controller: Controller,
@@ -453,9 +465,9 @@
         windowBackgroundColor: Int,
         fadeWindowBackgroundLayer: Boolean = true,
         drawHole: Boolean = false,
-        useSpring: Boolean = false,
+        startVelocity: PointF? = null,
     ): Animation {
-        if (!controller.isLaunching || useSpring) checkReturnAnimationFrameworkFlag()
+        if (!controller.isLaunching || startVelocity != null) checkReturnAnimationFrameworkFlag()
 
         // We add an extra layer with the same color as the dialog/app splash screen background
         // color, which is usually the same color of the app background. We first fade in this layer
@@ -474,7 +486,7 @@
                 windowBackgroundLayer,
                 fadeWindowBackgroundLayer,
                 drawHole,
-                useSpring,
+                startVelocity,
             )
             .apply { start() }
     }
@@ -487,7 +499,7 @@
         windowBackgroundLayer: GradientDrawable,
         fadeWindowBackgroundLayer: Boolean = true,
         drawHole: Boolean = false,
-        useSpring: Boolean = false,
+        startVelocity: PointF? = null,
     ): Animation {
         val transitionContainer = controller.transitionContainer
         val transitionContainerOverlay = transitionContainer.overlay
@@ -504,11 +516,12 @@
             openingWindowSyncView != null &&
                 openingWindowSyncView.viewRootImpl != controller.transitionContainer.viewRootImpl
 
-        return if (useSpring && springTimings != null && springInterpolators != null) {
+        return if (startVelocity != null && springTimings != null && springInterpolators != null) {
             createSpringAnimation(
                 controller,
                 startState,
                 endState,
+                startVelocity,
                 windowBackgroundLayer,
                 transitionContainer,
                 transitionContainerOverlay,
@@ -693,6 +706,7 @@
         controller: Controller,
         startState: State,
         endState: State,
+        startVelocity: PointF,
         windowBackgroundLayer: GradientDrawable,
         transitionContainer: View,
         transitionContainerOverlay: ViewGroupOverlay,
@@ -721,19 +735,20 @@
 
         fun updateProgress(state: SpringState) {
             if (
-                (!state.isCenterXDone && state.centerX == state.previousCenterX) ||
-                    (!state.isCenterYDone && state.centerY == state.previousCenterY) ||
-                    (!state.isScaleDone && state.scale == state.previousScale)
+                !(state.isCenterXUpdated || state.isCenterXDone) ||
+                    !(state.isCenterYUpdated || state.isCenterYDone) ||
+                    !(state.isScaleUpdated || state.isScaleDone)
             ) {
                 // Because all three springs use the same update method, we only actually update
-                // when all values have changed, avoiding two redundant calls per frame.
+                // when all properties have received their new value (which could be unchanged from
+                // the previous one), avoiding two redundant calls per frame.
                 return
             }
 
-            // Update the latest values for the check above.
-            state.previousCenterX = state.centerX
-            state.previousCenterY = state.centerY
-            state.previousScale = state.scale
+            // Reset the update flags.
+            state.isCenterXUpdated = false
+            state.isCenterYUpdated = false
+            state.isScaleUpdated = false
 
             // Current scale-based values, that will be used to find the new animation bounds.
             val width =
@@ -829,6 +844,7 @@
                         }
 
                     setStartValue(startState.centerX)
+                    setStartVelocity(startVelocity.x)
                     setMinValue(min(startState.centerX, endState.centerX))
                     setMaxValue(max(startState.centerX, endState.centerX))
 
@@ -850,6 +866,7 @@
                         }
 
                     setStartValue(startState.centerY)
+                    setStartVelocity(startVelocity.y)
                     setMinValue(min(startState.centerY, endState.centerY))
                     setMaxValue(max(startState.centerY, endState.centerY))
 
@@ -1057,15 +1074,13 @@
             interpolators = springInterpolators!!
             val timings = springTimings!!
             fadeInProgress =
-                getProgressInternal(
-                    totalDuration = 1f,
+                getProgress(
                     linearProgress,
                     timings.contentBeforeFadeOutDelay,
                     timings.contentBeforeFadeOutDuration,
                 )
             fadeOutProgress =
-                getProgressInternal(
-                    totalDuration = 1f,
+                getProgress(
                     linearProgress,
                     timings.contentAfterFadeInDelay,
                     timings.contentAfterFadeInDuration,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index 6fc51e4..e78862e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -53,6 +53,7 @@
 import com.android.systemui.notifications.ui.composable.SnoozeableHeadsUpNotificationSpace
 import com.android.systemui.res.R
 import com.android.systemui.shade.LargeScreenHeaderHelper
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker
@@ -84,7 +85,7 @@
     stackScrollLayout: NotificationStackScrollLayout,
     sharedNotificationContainerBinder: SharedNotificationContainerBinder,
     private val keyguardRootViewModel: KeyguardRootViewModel,
-    private val configurationState: ConfigurationState,
+    @ShadeDisplayAware private val configurationState: ConfigurationState,
     private val iconBindingFailureTracker: StatusBarIconViewBindingFailureTracker,
     private val nicAodViewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
     private val nicAodIconViewStore: AlwaysOnDisplayNotificationIconViewStore,
@@ -127,7 +128,7 @@
         }
         val burnIn = rememberBurnIn(clockInteractor)
         AnimatedVisibility(
-            visibleState  = transitionState,
+            visibleState = transitionState,
             enter = fadeIn(),
             exit = fadeOut(),
             modifier =
@@ -150,7 +151,7 @@
                             )
                         }
                     }
-                },
+                }
             )
         }
     }
@@ -172,7 +173,7 @@
         areNotificationsVisible: Boolean,
         isShadeLayoutWide: Boolean,
         burnInParams: BurnInParameters?,
-        modifier: Modifier = Modifier
+        modifier: Modifier = Modifier,
     ) {
         if (!areNotificationsVisible) {
             return
@@ -192,10 +193,7 @@
                         if (burnInParams == null) {
                             it
                         } else {
-                            it.burnInAware(
-                                viewModel = aodBurnInViewModel,
-                                params = burnInParams,
-                            )
+                            it.burnInAware(viewModel = aodBurnInViewModel, params = burnInParams)
                         }
                     },
         )
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
index 2a91bd8..26c827a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
@@ -43,6 +43,7 @@
 import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.lifecycle.rememberViewModel
+import com.android.systemui.notifications.ui.composable.SnoozeableHeadsUpNotificationSpace
 import com.android.systemui.qs.composefragment.ui.GridAnchor
 import com.android.systemui.qs.panels.ui.compose.EditMode
 import com.android.systemui.qs.panels.ui.compose.TileGrid
@@ -53,8 +54,11 @@
 import com.android.systemui.scene.ui.composable.Overlay
 import com.android.systemui.shade.ui.composable.ExpandedShadeHeader
 import com.android.systemui.shade.ui.composable.OverlayShade
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
 import com.android.systemui.statusbar.phone.ui.StatusBarIconController
 import com.android.systemui.statusbar.phone.ui.TintedIconManager
+import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 
@@ -67,6 +71,8 @@
     private val tintedIconManagerFactory: TintedIconManager.Factory,
     private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
     private val statusBarIconController: StatusBarIconController,
+    private val notificationStackScrollView: Lazy<NotificationScrollView>,
+    private val notificationsPlaceholderViewModelFactory: NotificationsPlaceholderViewModel.Factory,
 ) : Overlay {
 
     override val key = Overlays.QuickSettingsShade
@@ -98,6 +104,14 @@
 
                 ShadeBody(viewModel = viewModel.quickSettingsContainerViewModel)
             }
+
+            SnoozeableHeadsUpNotificationSpace(
+                stackScrollView = notificationStackScrollView.get(),
+                viewModel =
+                    rememberViewModel("QuickSettingsShadeOverlay") {
+                        notificationsPlaceholderViewModelFactory.create()
+                    },
+            )
         }
     }
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index 7872ffa..041cd15 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -36,12 +36,11 @@
 
 internal interface DraggableHandler {
     /**
-     * Start a drag in the given [startedPosition], with the given [overSlop] and number of
-     * [pointersDown].
+     * Start a drag with the given [pointersInfo] and [overSlop].
      *
      * The returned [DragController] should be used to continue or stop the drag.
      */
-    fun onDragStarted(startedPosition: Offset?, overSlop: Float, pointersDown: Int): DragController
+    fun onDragStarted(pointersInfo: PointersInfo?, overSlop: Float): DragController
 }
 
 /**
@@ -96,7 +95,7 @@
      * Note: if this returns true, then [onDragStarted] will be called with overSlop equal to 0f,
      * indicating that the transition should be intercepted.
      */
-    internal fun shouldImmediatelyIntercept(startedPosition: Offset?): Boolean {
+    internal fun shouldImmediatelyIntercept(pointersInfo: PointersInfo?): Boolean {
         // We don't intercept the touch if we are not currently driving the transition.
         val dragController = dragController
         if (dragController?.isDrivingTransition != true) {
@@ -107,7 +106,7 @@
 
         // Only intercept the current transition if one of the 2 swipes results is also a transition
         // between the same pair of contents.
-        val swipes = computeSwipes(startedPosition, pointersDown = 1)
+        val swipes = computeSwipes(pointersInfo)
         val fromContent = layoutImpl.content(swipeAnimation.currentContent)
         val (upOrLeft, downOrRight) = swipes.computeSwipesResults(fromContent)
         val currentScene = layoutImpl.state.currentScene
@@ -124,11 +123,7 @@
                 ))
     }
 
-    override fun onDragStarted(
-        startedPosition: Offset?,
-        overSlop: Float,
-        pointersDown: Int,
-    ): DragController {
+    override fun onDragStarted(pointersInfo: PointersInfo?, overSlop: Float): DragController {
         if (overSlop == 0f) {
             val oldDragController = dragController
             check(oldDragController != null && oldDragController.isDrivingTransition) {
@@ -153,7 +148,7 @@
             return updateDragController(swipes, swipeAnimation)
         }
 
-        val swipes = computeSwipes(startedPosition, pointersDown)
+        val swipes = computeSwipes(pointersInfo)
         val fromContent = layoutImpl.contentForUserActions()
 
         swipes.updateSwipesResults(fromContent)
@@ -190,8 +185,7 @@
         return createSwipeAnimation(layoutImpl, result, isUpOrLeft, orientation)
     }
 
-    internal fun resolveSwipeSource(startedPosition: Offset?): SwipeSource.Resolved? {
-        if (startedPosition == null) return null
+    internal fun resolveSwipeSource(startedPosition: Offset): SwipeSource.Resolved? {
         return layoutImpl.swipeSourceDetector.source(
             layoutSize = layoutImpl.lastSize,
             position = startedPosition.round(),
@@ -200,57 +194,44 @@
         )
     }
 
-    internal fun resolveSwipe(
-        pointersDown: Int,
-        fromSource: SwipeSource.Resolved?,
-        isUpOrLeft: Boolean,
-    ): Swipe.Resolved {
-        return Swipe.Resolved(
-            direction =
-                when (orientation) {
-                    Orientation.Horizontal ->
-                        if (isUpOrLeft) {
-                            SwipeDirection.Resolved.Left
-                        } else {
-                            SwipeDirection.Resolved.Right
-                        }
-
-                    Orientation.Vertical ->
-                        if (isUpOrLeft) {
-                            SwipeDirection.Resolved.Up
-                        } else {
-                            SwipeDirection.Resolved.Down
-                        }
-                },
-            pointerCount = pointersDown,
-            fromSource = fromSource,
+    private fun computeSwipes(pointersInfo: PointersInfo?): Swipes {
+        val fromSource = pointersInfo?.let { resolveSwipeSource(it.startedPosition) }
+        return Swipes(
+            upOrLeft = resolveSwipe(orientation, isUpOrLeft = true, pointersInfo, fromSource),
+            downOrRight = resolveSwipe(orientation, isUpOrLeft = false, pointersInfo, fromSource),
         )
     }
+}
 
-    private fun computeSwipes(startedPosition: Offset?, pointersDown: Int): Swipes {
-        val fromSource = resolveSwipeSource(startedPosition)
-        val upOrLeft = resolveSwipe(pointersDown, fromSource, isUpOrLeft = true)
-        val downOrRight = resolveSwipe(pointersDown, fromSource, isUpOrLeft = false)
-        return if (fromSource == null) {
-            Swipes(
-                upOrLeft = null,
-                downOrRight = null,
-                upOrLeftNoSource = upOrLeft,
-                downOrRightNoSource = downOrRight,
-            )
-        } else {
-            Swipes(
-                upOrLeft = upOrLeft,
-                downOrRight = downOrRight,
-                upOrLeftNoSource = upOrLeft.copy(fromSource = null),
-                downOrRightNoSource = downOrRight.copy(fromSource = null),
-            )
-        }
-    }
+private fun resolveSwipe(
+    orientation: Orientation,
+    isUpOrLeft: Boolean,
+    pointersInfo: PointersInfo?,
+    fromSource: SwipeSource.Resolved?,
+): Swipe.Resolved {
+    return Swipe.Resolved(
+        direction =
+            when (orientation) {
+                Orientation.Horizontal ->
+                    if (isUpOrLeft) {
+                        SwipeDirection.Resolved.Left
+                    } else {
+                        SwipeDirection.Resolved.Right
+                    }
 
-    companion object {
-        private const val TAG = "DraggableHandlerImpl"
-    }
+                Orientation.Vertical ->
+                    if (isUpOrLeft) {
+                        SwipeDirection.Resolved.Up
+                    } else {
+                        SwipeDirection.Resolved.Down
+                    }
+            },
+        // If the number of pointers is not specified, 1 is assumed.
+        pointerCount = pointersInfo?.pointersDown ?: 1,
+        // Resolves the pointer type only if all pointers are of the same type.
+        pointersType = pointersInfo?.pointersDownByType?.keys?.singleOrNull(),
+        fromSource = fromSource,
+    )
 }
 
 /** @param swipes The [Swipes] associated to the current gesture. */
@@ -498,24 +479,14 @@
 }
 
 /** The [Swipe] associated to a given fromScene, startedPosition and pointersDown. */
-internal class Swipes(
-    val upOrLeft: Swipe.Resolved?,
-    val downOrRight: Swipe.Resolved?,
-    val upOrLeftNoSource: Swipe.Resolved?,
-    val downOrRightNoSource: Swipe.Resolved?,
-) {
+internal class Swipes(val upOrLeft: Swipe.Resolved, val downOrRight: Swipe.Resolved) {
     /** The [UserActionResult] associated to up and down swipes. */
     var upOrLeftResult: UserActionResult? = null
     var downOrRightResult: UserActionResult? = null
 
     fun computeSwipesResults(fromContent: Content): Pair<UserActionResult?, UserActionResult?> {
-        val userActions = fromContent.userActions
-        fun result(swipe: Swipe.Resolved?): UserActionResult? {
-            return userActions[swipe ?: return null]
-        }
-
-        val upOrLeftResult = result(upOrLeft) ?: result(upOrLeftNoSource)
-        val downOrRightResult = result(downOrRight) ?: result(downOrRightNoSource)
+        val upOrLeftResult = fromContent.findActionResultBestMatch(swipe = upOrLeft)
+        val downOrRightResult = fromContent.findActionResultBestMatch(swipe = downOrRight)
         return upOrLeftResult to downOrRightResult
     }
 
@@ -569,11 +540,13 @@
 
     val connection: PriorityNestedScrollConnection = nestedScrollConnection()
 
-    private fun PointersInfo.resolveSwipe(isUpOrLeft: Boolean): Swipe.Resolved {
-        return draggableHandler.resolveSwipe(
-            pointersDown = pointersDown,
-            fromSource = draggableHandler.resolveSwipeSource(startedPosition),
+    private fun resolveSwipe(isUpOrLeft: Boolean, pointersInfo: PointersInfo?): Swipe.Resolved {
+        return resolveSwipe(
+            orientation = draggableHandler.orientation,
             isUpOrLeft = isUpOrLeft,
+            pointersInfo = pointersInfo,
+            fromSource =
+                pointersInfo?.let { draggableHandler.resolveSwipeSource(it.startedPosition) },
         )
     }
 
@@ -582,12 +555,7 @@
         // moving on to the next scene.
         var canChangeScene = false
 
-        var _lastPointersInfo: PointersInfo? = null
-        fun pointersInfo(): PointersInfo {
-            return checkNotNull(_lastPointersInfo) {
-                "PointersInfo should be initialized before the transition begins."
-            }
-        }
+        var lastPointersInfo: PointersInfo? = null
 
         fun hasNextScene(amount: Float): Boolean {
             val transitionState = layoutState.transitionState
@@ -595,17 +563,11 @@
             val fromScene = layoutImpl.scene(scene)
             val resolvedSwipe =
                 when {
-                    amount < 0f -> pointersInfo().resolveSwipe(isUpOrLeft = true)
-                    amount > 0f -> pointersInfo().resolveSwipe(isUpOrLeft = false)
+                    amount < 0f -> resolveSwipe(isUpOrLeft = true, lastPointersInfo)
+                    amount > 0f -> resolveSwipe(isUpOrLeft = false, lastPointersInfo)
                     else -> null
                 }
-            val nextScene =
-                resolvedSwipe?.let {
-                    fromScene.userActions[it]
-                        ?: if (it.fromSource != null) {
-                            fromScene.userActions[it.copy(fromSource = null)]
-                        } else null
-                }
+            val nextScene = resolvedSwipe?.let { fromScene.findActionResultBestMatch(it) }
             if (nextScene != null) return true
 
             if (transitionState !is TransitionState.Idle) return false
@@ -619,13 +581,14 @@
         return PriorityNestedScrollConnection(
             orientation = orientation,
             canStartPreScroll = { offsetAvailable, offsetBeforeStart, _ ->
+                val pointersInfo = pointersInfoOwner.pointersInfo()
                 canChangeScene =
                     if (isExternalOverscrollGesture()) false else offsetBeforeStart == 0f
 
                 val canInterceptSwipeTransition =
                     canChangeScene &&
                         offsetAvailable != 0f &&
-                        draggableHandler.shouldImmediatelyIntercept(startedPosition = null)
+                        draggableHandler.shouldImmediatelyIntercept(pointersInfo)
                 if (!canInterceptSwipeTransition) return@PriorityNestedScrollConnection false
 
                 val threshold = layoutImpl.transitionInterceptionThreshold
@@ -636,13 +599,11 @@
                     return@PriorityNestedScrollConnection false
                 }
 
-                val pointersInfo = pointersInfoOwner.pointersInfo()
-
-                if (pointersInfo.isMouseWheel) {
+                if (pointersInfo?.isMouseWheel == true) {
                     // Do not support mouse wheel interactions
                     return@PriorityNestedScrollConnection false
                 }
-                _lastPointersInfo = pointersInfo
+                lastPointersInfo = pointersInfo
 
                 // If the current swipe transition is *not* closed to 0f or 1f, then we want the
                 // scroll events to intercept the current transition to continue the scene
@@ -662,11 +623,11 @@
                     if (isExternalOverscrollGesture()) false else offsetBeforeStart == 0f
 
                 val pointersInfo = pointersInfoOwner.pointersInfo()
-                if (pointersInfo.isMouseWheel) {
+                if (pointersInfo?.isMouseWheel == true) {
                     // Do not support mouse wheel interactions
                     return@PriorityNestedScrollConnection false
                 }
-                _lastPointersInfo = pointersInfo
+                lastPointersInfo = pointersInfo
 
                 val canStart =
                     when (behavior) {
@@ -704,11 +665,11 @@
                 canChangeScene = false
 
                 val pointersInfo = pointersInfoOwner.pointersInfo()
-                if (pointersInfo.isMouseWheel) {
+                if (pointersInfo?.isMouseWheel == true) {
                     // Do not support mouse wheel interactions
                     return@PriorityNestedScrollConnection false
                 }
-                _lastPointersInfo = pointersInfo
+                lastPointersInfo = pointersInfo
 
                 val canStart = behavior.canStartOnPostFling && hasNextScene(velocityAvailable)
                 if (canStart) {
@@ -718,12 +679,11 @@
                 canStart
             },
             onStart = { firstScroll ->
-                val pointersInfo = pointersInfo()
+                val pointersInfo = lastPointersInfo
                 scrollController(
                     dragController =
                         draggableHandler.onDragStarted(
-                            pointersDown = pointersInfo.pointersDown,
-                            startedPosition = pointersInfo.startedPosition,
+                            pointersInfo = pointersInfo,
                             overSlop = if (isIntercepting) 0f else firstScroll,
                         ),
                     canChangeScene = canChangeScene,
@@ -742,7 +702,7 @@
     return object : ScrollController {
         override fun onScroll(deltaScroll: Float, source: NestedScrollSource): Float {
             val pointersInfo = pointersInfoOwner.pointersInfo()
-            if (pointersInfo.isMouseWheel) {
+            if (pointersInfo?.isMouseWheel == true) {
                 // Do not support mouse wheel interactions
                 return 0f
             }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index 63c5d7a..e7b66c5 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -52,6 +52,7 @@
 import com.android.compose.animation.scene.content.state.TransitionState
 import com.android.compose.animation.scene.transformation.PropertyTransformation
 import com.android.compose.animation.scene.transformation.SharedElementTransformation
+import com.android.compose.animation.scene.transformation.TransformationWithRange
 import com.android.compose.modifiers.thenIf
 import com.android.compose.ui.graphics.drawInContainer
 import com.android.compose.ui.util.lerp
@@ -187,6 +188,7 @@
                 state.transformationSpec
                     .transformations(key, content.key)
                     .shared
+                    ?.transformation
                     ?.elevateInContent == content.key &&
                 isSharedElement(stateByContent, state) &&
                 isSharedElementEnabled(key, state) &&
@@ -901,7 +903,7 @@
     }
 
     val sharedTransformation = sharedElementTransformation(element.key, transition)
-    if (sharedTransformation?.enabled == false) {
+    if (sharedTransformation?.transformation?.enabled == false) {
         return true
     }
 
@@ -954,13 +956,13 @@
     element: ElementKey,
     transition: TransitionState.Transition,
 ): Boolean {
-    return sharedElementTransformation(element, transition)?.enabled ?: true
+    return sharedElementTransformation(element, transition)?.transformation?.enabled ?: true
 }
 
 internal fun sharedElementTransformation(
     element: ElementKey,
     transition: TransitionState.Transition,
-): SharedElementTransformation? {
+): TransformationWithRange<SharedElementTransformation>? {
     val transformationSpec = transition.transformationSpec
     val sharedInFromContent =
         transformationSpec.transformations(element, transition.fromContent).shared
@@ -1244,7 +1246,7 @@
     element: Element,
     transition: TransitionState.Transition?,
     contentValue: (Element.State) -> T,
-    transformation: (ElementTransformations) -> PropertyTransformation<T>?,
+    transformation: (ElementTransformations) -> TransformationWithRange<PropertyTransformation<T>>?,
     currentValue: () -> T,
     isSpecified: (T) -> Boolean,
     lerp: (T, T, Float) -> T,
@@ -1280,7 +1282,7 @@
                 checkNotNull(if (currentContent == toContent) toState else fromState)
             val idleValue = contentValue(overscrollState)
             val targetValue =
-                with(propertySpec) {
+                with(propertySpec.transformation) {
                     layoutImpl.propertyTransformationScope.transform(
                         currentContent,
                         element.key,
@@ -1375,7 +1377,7 @@
         val idleValue = contentValue(contentState)
         val isEntering = content == toContent
         val previewTargetValue =
-            with(previewTransformation) {
+            with(previewTransformation.transformation) {
                 layoutImpl.propertyTransformationScope.transform(
                     content,
                     element.key,
@@ -1386,7 +1388,7 @@
 
         val targetValueOrNull =
             transformation?.let { transformation ->
-                with(transformation) {
+                with(transformation.transformation) {
                     layoutImpl.propertyTransformationScope.transform(
                         content,
                         element.key,
@@ -1461,7 +1463,7 @@
 
     val idleValue = contentValue(contentState)
     val targetValue =
-        with(transformation) {
+        with(transformation.transformation) {
             layoutImpl.propertyTransformationScope.transform(
                 content,
                 element.key,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index 8613f6d..ab2324a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -33,6 +33,7 @@
 import androidx.compose.ui.input.pointer.PointerId
 import androidx.compose.ui.input.pointer.PointerInputChange
 import androidx.compose.ui.input.pointer.PointerInputScope
+import androidx.compose.ui.input.pointer.PointerType
 import androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNode
 import androidx.compose.ui.input.pointer.changedToDown
 import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed
@@ -52,6 +53,7 @@
 import androidx.compose.ui.util.fastAny
 import androidx.compose.ui.util.fastFilter
 import androidx.compose.ui.util.fastFirstOrNull
+import androidx.compose.ui.util.fastForEach
 import androidx.compose.ui.util.fastSumBy
 import com.android.compose.ui.util.SpaceVectorConverter
 import kotlin.coroutines.cancellation.CancellationException
@@ -78,8 +80,8 @@
 @Stable
 internal fun Modifier.multiPointerDraggable(
     orientation: Orientation,
-    startDragImmediately: (startedPosition: Offset) -> Boolean,
-    onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
+    startDragImmediately: (pointersInfo: PointersInfo) -> Boolean,
+    onDragStarted: (pointersInfo: PointersInfo, overSlop: Float) -> DragController,
     onFirstPointerDown: () -> Unit = {},
     swipeDetector: SwipeDetector = DefaultSwipeDetector,
     dispatcher: NestedScrollDispatcher,
@@ -97,9 +99,8 @@
 
 private data class MultiPointerDraggableElement(
     private val orientation: Orientation,
-    private val startDragImmediately: (startedPosition: Offset) -> Boolean,
-    private val onDragStarted:
-        (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
+    private val startDragImmediately: (pointersInfo: PointersInfo) -> Boolean,
+    private val onDragStarted: (pointersInfo: PointersInfo, overSlop: Float) -> DragController,
     private val onFirstPointerDown: () -> Unit,
     private val swipeDetector: SwipeDetector,
     private val dispatcher: NestedScrollDispatcher,
@@ -125,9 +126,8 @@
 
 internal class MultiPointerDraggableNode(
     orientation: Orientation,
-    var startDragImmediately: (startedPosition: Offset) -> Boolean,
-    var onDragStarted:
-        (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
+    var startDragImmediately: (pointersInfo: PointersInfo) -> Boolean,
+    var onDragStarted: (pointersInfo: PointersInfo, overSlop: Float) -> DragController,
     var onFirstPointerDown: () -> Unit,
     swipeDetector: SwipeDetector = DefaultSwipeDetector,
     private val dispatcher: NestedScrollDispatcher,
@@ -183,17 +183,22 @@
         pointerInput.onPointerEvent(pointerEvent, pass, bounds)
     }
 
+    private var lastPointerEvent: PointerEvent? = null
     private var startedPosition: Offset? = null
     private var pointersDown: Int = 0
-    private var isMouseWheel: Boolean = false
 
-    internal fun pointersInfo(): PointersInfo {
-        return PointersInfo(
+    internal fun pointersInfo(): PointersInfo? {
+        val startedPosition = startedPosition
+        val lastPointerEvent = lastPointerEvent
+        if (startedPosition == null || lastPointerEvent == null) {
             // This may be null, i.e. when the user uses TalkBack
+            return null
+        }
+
+        return PointersInfo(
             startedPosition = startedPosition,
-            // We could have 0 pointers during fling or for other reasons.
-            pointersDown = pointersDown.coerceAtLeast(1),
-            isMouseWheel = isMouseWheel,
+            pointersDown = pointersDown,
+            lastPointerEvent = lastPointerEvent,
         )
     }
 
@@ -212,8 +217,8 @@
                 if (pointerEvent.type == PointerEventType.Enter) continue
 
                 val changes = pointerEvent.changes
+                lastPointerEvent = pointerEvent
                 pointersDown = changes.countDown()
-                isMouseWheel = pointerEvent.type == PointerEventType.Scroll
 
                 when {
                     // There are no more pointers down.
@@ -285,8 +290,8 @@
                     detectDragGestures(
                         orientation = orientation,
                         startDragImmediately = startDragImmediately,
-                        onDragStart = { startedPosition, overSlop, pointersDown ->
-                            onDragStarted(startedPosition, overSlop, pointersDown)
+                        onDragStart = { pointersInfo, overSlop ->
+                            onDragStarted(pointersInfo, overSlop)
                         },
                         onDrag = { controller, amount ->
                             dispatchScrollEvents(
@@ -435,9 +440,8 @@
      */
     private suspend fun AwaitPointerEventScope.detectDragGestures(
         orientation: Orientation,
-        startDragImmediately: (startedPosition: Offset) -> Boolean,
-        onDragStart:
-            (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
+        startDragImmediately: (pointersInfo: PointersInfo) -> Boolean,
+        onDragStart: (pointersInfo: PointersInfo, overSlop: Float) -> DragController,
         onDrag: (controller: DragController, dragAmount: Float) -> Unit,
         onDragEnd: (controller: DragController) -> Unit,
         onDragCancel: (controller: DragController) -> Unit,
@@ -462,8 +466,13 @@
                 .first()
 
         var overSlop = 0f
+        var lastPointersInfo =
+            checkNotNull(pointersInfo()) {
+                "We should have pointers down, last event: $currentEvent"
+            }
+
         val drag =
-            if (startDragImmediately(consumablePointer.position)) {
+            if (startDragImmediately(lastPointersInfo)) {
                 consumablePointer.consume()
                 consumablePointer
             } else {
@@ -488,14 +497,18 @@
                                 consumablePointer.id,
                                 onSlopReached,
                             )
-                    }
+                    } ?: return
 
+                lastPointersInfo =
+                    checkNotNull(pointersInfo()) {
+                        "We should have pointers down, last event: $currentEvent"
+                    }
                 // Make sure that overSlop is not 0f. This can happen when the user drags by exactly
                 // the touch slop. However, the overSlop we pass to onDragStarted() is used to
                 // compute the direction we are dragging in, so overSlop should never be 0f unless
                 // we intercept an ongoing swipe transition (i.e. startDragImmediately() returned
                 // true).
-                if (drag != null && overSlop == 0f) {
+                if (overSlop == 0f) {
                     val delta = (drag.position - consumablePointer.position).toFloat()
                     check(delta != 0f) { "delta is equal to 0" }
                     overSlop = delta.sign
@@ -503,49 +516,38 @@
                 drag
             }
 
-        if (drag != null) {
-            val controller =
-                onDragStart(
-                    // The startedPosition is the starting position when a gesture begins (when the
-                    // first pointer touches the screen), not the point where we begin dragging.
-                    // For example, this could be different if one of our children intercepts the
-                    // gesture first and then we do.
-                    requireNotNull(startedPosition),
-                    overSlop,
-                    pointersDown,
+        val controller = onDragStart(lastPointersInfo, overSlop)
+
+        val successful: Boolean
+        try {
+            onDrag(controller, overSlop)
+
+            successful =
+                drag(
+                    initialPointerId = drag.id,
+                    hasDragged = { it.positionChangeIgnoreConsumed().toFloat() != 0f },
+                    onDrag = {
+                        onDrag(controller, it.positionChange().toFloat())
+                        it.consume()
+                    },
+                    onIgnoredEvent = {
+                        // We are still dragging an object, but this event is not of interest to the
+                        // caller.
+                        // This event will not trigger the onDrag event, but we will consume the
+                        // event to prevent another pointerInput from interrupting the current
+                        // gesture just because the event was ignored.
+                        it.consume()
+                    },
                 )
+        } catch (t: Throwable) {
+            onDragCancel(controller)
+            throw t
+        }
 
-            val successful: Boolean
-            try {
-                onDrag(controller, overSlop)
-
-                successful =
-                    drag(
-                        initialPointerId = drag.id,
-                        hasDragged = { it.positionChangeIgnoreConsumed().toFloat() != 0f },
-                        onDrag = {
-                            onDrag(controller, it.positionChange().toFloat())
-                            it.consume()
-                        },
-                        onIgnoredEvent = {
-                            // We are still dragging an object, but this event is not of interest to
-                            // the caller.
-                            // This event will not trigger the onDrag event, but we will consume the
-                            // event to prevent another pointerInput from interrupting the current
-                            // gesture just because the event was ignored.
-                            it.consume()
-                        },
-                    )
-            } catch (t: Throwable) {
-                onDragCancel(controller)
-                throw t
-            }
-
-            if (successful) {
-                onDragEnd(controller)
-            } else {
-                onDragCancel(controller)
-            }
+        if (successful) {
+            onDragEnd(controller)
+        } else {
+            onDragCancel(controller)
         }
     }
 
@@ -655,11 +657,57 @@
 }
 
 internal fun interface PointersInfoOwner {
-    fun pointersInfo(): PointersInfo
+    /**
+     * Provides information about the pointers interacting with this composable.
+     *
+     * @return A [PointersInfo] object containing details about the pointers, including the starting
+     *   position and the number of pointers down, or `null` if there are no pointers down.
+     */
+    fun pointersInfo(): PointersInfo?
 }
 
+/**
+ * Holds information about pointer interactions within a composable.
+ *
+ * This class stores details such as the starting position of a gesture, the number of pointers
+ * down, and whether the last pointer event was a mouse wheel scroll.
+ *
+ * @param startedPosition The starting position of the gesture. This is the position where the first
+ *   pointer touched the screen, not necessarily the point where dragging begins. This may be
+ *   different from the initial touch position if a child composable intercepts the gesture before
+ *   this one.
+ * @param pointersDown The number of pointers currently down.
+ * @param isMouseWheel Indicates whether the last pointer event was a mouse wheel scroll.
+ * @param pointersDownByType Provide a map of pointer types to the count of pointers of that type
+ *   currently down/pressed.
+ */
 internal data class PointersInfo(
-    val startedPosition: Offset?,
+    val startedPosition: Offset,
     val pointersDown: Int,
     val isMouseWheel: Boolean,
-)
+    val pointersDownByType: Map<PointerType, Int>,
+) {
+    init {
+        check(pointersDown > 0) { "We should have at least 1 pointer down, $pointersDown instead" }
+    }
+}
+
+private fun PointersInfo(
+    startedPosition: Offset,
+    pointersDown: Int,
+    lastPointerEvent: PointerEvent,
+): PointersInfo {
+    return PointersInfo(
+        startedPosition = startedPosition,
+        pointersDown = pointersDown,
+        isMouseWheel = lastPointerEvent.type == PointerEventType.Scroll,
+        pointersDownByType =
+            buildMap {
+                lastPointerEvent.changes.fastForEach { change ->
+                    if (!change.pressed) return@fastForEach
+                    val newValue = (get(change.type) ?: 0) + 1
+                    put(change.type, newValue)
+                }
+            },
+    )
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
index 077927d..5bf77ae 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
@@ -233,6 +233,12 @@
             (to == null || this.toContent == to)
     }
 
+    fun isTransitioningSets(from: Set<ContentKey>? = null, to: Set<ContentKey>? = null): Boolean {
+        return this is Transition &&
+            (from == null || from.contains(this.fromContent)) &&
+            (to == null || to.contains(this.toContent))
+    }
+
     /** Whether we are transitioning from [content] to [other], or from [other] to [content]. */
     fun isTransitioningBetween(content: ContentKey, other: ContentKey): Boolean {
         return isTransitioning(from = content, to = other) ||
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index 5042403..21d87e1 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -27,6 +27,7 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.pointer.PointerType
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.unit.Density
@@ -407,6 +408,7 @@
 data class Swipe(
     val direction: SwipeDirection,
     val pointerCount: Int = 1,
+    val pointersType: PointerType? = null,
     val fromSource: SwipeSource? = null,
 ) : UserAction() {
     companion object {
@@ -422,6 +424,7 @@
         return Resolved(
             direction = direction.resolve(layoutDirection),
             pointerCount = pointerCount,
+            pointersType = pointersType,
             fromSource = fromSource?.resolve(layoutDirection),
         )
     }
@@ -431,6 +434,7 @@
         val direction: SwipeDirection.Resolved,
         val pointerCount: Int,
         val fromSource: SwipeSource.Resolved?,
+        val pointersType: PointerType?,
     ) : UserAction.Resolved()
 }
 
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index e1e2411..61332b6 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -764,7 +764,8 @@
                     return@fastForEach
                 }
 
-                state.transformationSpec.transformations.fastForEach { transformation ->
+                state.transformationSpec.transformations.fastForEach { transformationWithRange ->
+                    val transformation = transformationWithRange.transformation
                     if (
                         transformation is SharedElementTransformation &&
                             transformation.elevateInContent != null
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
index 8866fbf..b083f79 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -33,10 +33,10 @@
 import com.android.compose.animation.scene.transformation.Fade
 import com.android.compose.animation.scene.transformation.OverscrollTranslate
 import com.android.compose.animation.scene.transformation.PropertyTransformation
-import com.android.compose.animation.scene.transformation.RangedPropertyTransformation
 import com.android.compose.animation.scene.transformation.ScaleSize
 import com.android.compose.animation.scene.transformation.SharedElementTransformation
 import com.android.compose.animation.scene.transformation.Transformation
+import com.android.compose.animation.scene.transformation.TransformationWithRange
 import com.android.compose.animation.scene.transformation.Translate
 
 /** The transitions configuration of a [SceneTransitionLayout]. */
@@ -233,7 +233,7 @@
     val distance: UserActionDistance?
 
     /** The list of [Transformation] applied to elements during this transition. */
-    val transformations: List<Transformation>
+    val transformations: List<TransformationWithRange<*>>
 
     companion object {
         internal val Empty =
@@ -325,7 +325,7 @@
     override val progressSpec: AnimationSpec<Float>,
     override val swipeSpec: SpringSpec<Float>?,
     override val distance: UserActionDistance?,
-    override val transformations: List<Transformation>,
+    override val transformations: List<TransformationWithRange<*>>,
 ) : TransformationSpec {
     private val cache = mutableMapOf<ElementKey, MutableMap<ContentKey, ElementTransformations>>()
 
@@ -340,42 +340,14 @@
         element: ElementKey,
         content: ContentKey,
     ): ElementTransformations {
-        var shared: SharedElementTransformation? = null
-        var offset: PropertyTransformation<Offset>? = null
-        var size: PropertyTransformation<IntSize>? = null
-        var drawScale: PropertyTransformation<Scale>? = null
-        var alpha: PropertyTransformation<Float>? = null
+        var shared: TransformationWithRange<SharedElementTransformation>? = null
+        var offset: TransformationWithRange<PropertyTransformation<Offset>>? = null
+        var size: TransformationWithRange<PropertyTransformation<IntSize>>? = null
+        var drawScale: TransformationWithRange<PropertyTransformation<Scale>>? = null
+        var alpha: TransformationWithRange<PropertyTransformation<Float>>? = null
 
-        fun <T> onPropertyTransformation(
-            root: PropertyTransformation<T>,
-            current: PropertyTransformation<T> = root,
-        ) {
-            when (current) {
-                is Translate,
-                is OverscrollTranslate,
-                is EdgeTranslate,
-                is AnchoredTranslate -> {
-                    throwIfNotNull(offset, element, name = "offset")
-                    offset = root as PropertyTransformation<Offset>
-                }
-                is ScaleSize,
-                is AnchoredSize -> {
-                    throwIfNotNull(size, element, name = "size")
-                    size = root as PropertyTransformation<IntSize>
-                }
-                is DrawScale -> {
-                    throwIfNotNull(drawScale, element, name = "drawScale")
-                    drawScale = root as PropertyTransformation<Scale>
-                }
-                is Fade -> {
-                    throwIfNotNull(alpha, element, name = "alpha")
-                    alpha = root as PropertyTransformation<Float>
-                }
-                is RangedPropertyTransformation -> onPropertyTransformation(root, current.delegate)
-            }
-        }
-
-        transformations.fastForEach { transformation ->
+        transformations.fastForEach { transformationWithRange ->
+            val transformation = transformationWithRange.transformation
             if (!transformation.matcher.matches(element, content)) {
                 return@fastForEach
             }
@@ -383,16 +355,50 @@
             when (transformation) {
                 is SharedElementTransformation -> {
                     throwIfNotNull(shared, element, name = "shared")
-                    shared = transformation
+                    shared =
+                        transformationWithRange
+                            as TransformationWithRange<SharedElementTransformation>
                 }
-                is PropertyTransformation<*> -> onPropertyTransformation(transformation)
+                is Translate,
+                is OverscrollTranslate,
+                is EdgeTranslate,
+                is AnchoredTranslate -> {
+                    throwIfNotNull(offset, element, name = "offset")
+                    offset =
+                        transformationWithRange
+                            as TransformationWithRange<PropertyTransformation<Offset>>
+                }
+                is ScaleSize,
+                is AnchoredSize -> {
+                    throwIfNotNull(size, element, name = "size")
+                    size =
+                        transformationWithRange
+                            as TransformationWithRange<PropertyTransformation<IntSize>>
+                }
+                is DrawScale -> {
+                    throwIfNotNull(drawScale, element, name = "drawScale")
+                    drawScale =
+                        transformationWithRange
+                            as TransformationWithRange<PropertyTransformation<Scale>>
+                }
+                is Fade -> {
+                    throwIfNotNull(alpha, element, name = "alpha")
+                    alpha =
+                        transformationWithRange
+                            as TransformationWithRange<PropertyTransformation<Float>>
+                }
+                else -> error("Unknown transformation: $transformation")
             }
         }
 
         return ElementTransformations(shared, offset, size, drawScale, alpha)
     }
 
-    private fun throwIfNotNull(previous: Transformation?, element: ElementKey, name: String) {
+    private fun throwIfNotNull(
+        previous: TransformationWithRange<*>?,
+        element: ElementKey,
+        name: String,
+    ) {
         if (previous != null) {
             error("$element has multiple $name transformations")
         }
@@ -401,9 +407,9 @@
 
 /** The transformations of an element during a transition. */
 internal class ElementTransformations(
-    val shared: SharedElementTransformation?,
-    val offset: PropertyTransformation<Offset>?,
-    val size: PropertyTransformation<IntSize>?,
-    val drawScale: PropertyTransformation<Scale>?,
-    val alpha: PropertyTransformation<Float>?,
+    val shared: TransformationWithRange<SharedElementTransformation>?,
+    val offset: TransformationWithRange<PropertyTransformation<Offset>>?,
+    val size: TransformationWithRange<PropertyTransformation<IntSize>>?,
+    val drawScale: TransformationWithRange<PropertyTransformation<Scale>>?,
+    val alpha: TransformationWithRange<PropertyTransformation<Float>>?,
 )
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index fdf01cc..ba5f414 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -19,7 +19,6 @@
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.runtime.Stable
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.input.nestedscroll.NestedScrollDispatcher
 import androidx.compose.ui.input.nestedscroll.nestedScrollModifierNode
 import androidx.compose.ui.input.pointer.PointerEvent
@@ -65,6 +64,52 @@
     return userActions.keys.any { it is Swipe.Resolved && it.direction.orientation == orientation }
 }
 
+/**
+ * Finds the best matching [UserActionResult] for the given [swipe] within this [Content].
+ * Prioritizes actions with matching [Swipe.Resolved.fromSource].
+ *
+ * @param swipe The swipe to match against.
+ * @return The best matching [UserActionResult], or `null` if no match is found.
+ */
+internal fun Content.findActionResultBestMatch(swipe: Swipe.Resolved): UserActionResult? {
+    var bestPoints = Int.MIN_VALUE
+    var bestMatch: UserActionResult? = null
+    userActions.forEach { (actionSwipe, actionResult) ->
+        if (
+            actionSwipe !is Swipe.Resolved ||
+                // The direction must match.
+                actionSwipe.direction != swipe.direction ||
+                // The number of pointers down must match.
+                actionSwipe.pointerCount != swipe.pointerCount ||
+                // The action requires a specific fromSource.
+                (actionSwipe.fromSource != null && actionSwipe.fromSource != swipe.fromSource) ||
+                // The action requires a specific pointerType.
+                (actionSwipe.pointersType != null && actionSwipe.pointersType != swipe.pointersType)
+        ) {
+            // This action is not eligible.
+            return@forEach
+        }
+
+        val sameFromSource = actionSwipe.fromSource == swipe.fromSource
+        val samePointerType = actionSwipe.pointersType == swipe.pointersType
+        // Prioritize actions with a perfect match.
+        if (sameFromSource && samePointerType) {
+            return actionResult
+        }
+
+        var points = 0
+        if (sameFromSource) points++
+        if (samePointerType) points++
+
+        // Otherwise, keep track of the best eligible action.
+        if (points > bestPoints) {
+            bestPoints = points
+            bestMatch = actionResult
+        }
+    }
+    return bestMatch
+}
+
 private data class SwipeToSceneElement(
     val draggableHandler: DraggableHandlerImpl,
     val swipeDetector: SwipeDetector,
@@ -155,10 +200,10 @@
 
     override fun onCancelPointerInput() = multiPointerDraggableNode.onCancelPointerInput()
 
-    private fun startDragImmediately(startedPosition: Offset): Boolean {
+    private fun startDragImmediately(pointersInfo: PointersInfo): Boolean {
         // Immediately start the drag if the user can't swipe in the other direction and the gesture
         // handler can intercept it.
-        return !canOppositeSwipe() && draggableHandler.shouldImmediatelyIntercept(startedPosition)
+        return !canOppositeSwipe() && draggableHandler.shouldImmediatelyIntercept(pointersInfo)
     }
 
     private fun canOppositeSwipe(): Boolean {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index 269d91b0..e461f9c 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -34,12 +34,11 @@
 import com.android.compose.animation.scene.transformation.EdgeTranslate
 import com.android.compose.animation.scene.transformation.Fade
 import com.android.compose.animation.scene.transformation.OverscrollTranslate
-import com.android.compose.animation.scene.transformation.PropertyTransformation
-import com.android.compose.animation.scene.transformation.RangedPropertyTransformation
 import com.android.compose.animation.scene.transformation.ScaleSize
 import com.android.compose.animation.scene.transformation.SharedElementTransformation
 import com.android.compose.animation.scene.transformation.Transformation
 import com.android.compose.animation.scene.transformation.TransformationRange
+import com.android.compose.animation.scene.transformation.TransformationWithRange
 import com.android.compose.animation.scene.transformation.Translate
 
 internal fun transitionsImpl(builder: SceneTransitionsBuilder.() -> Unit): SceneTransitions {
@@ -158,7 +157,7 @@
 }
 
 internal abstract class BaseTransitionBuilderImpl : BaseTransitionBuilder {
-    val transformations = mutableListOf<Transformation>()
+    val transformations = mutableListOf<TransformationWithRange<*>>()
     private var range: TransformationRange? = null
     protected var reversed = false
     override var distance: UserActionDistance? = null
@@ -174,19 +173,13 @@
         range = null
     }
 
-    protected fun transformation(transformation: PropertyTransformation<*>) {
-        val transformation =
-            if (range != null) {
-                RangedPropertyTransformation(transformation, range!!)
-            } else {
-                transformation
-            }
-
+    protected fun transformation(transformation: Transformation) {
+        val transformationWithRange = TransformationWithRange(transformation, range)
         transformations.add(
             if (reversed) {
-                transformation.reversed()
+                transformationWithRange.reversed()
             } else {
-                transformation
+                transformationWithRange
             }
         )
     }
@@ -264,7 +257,7 @@
                 "(${transition.toContent.debugName})"
         }
 
-        transformations.add(SharedElementTransformation(matcher, enabled, elevateInContent))
+        transformation(SharedElementTransformation(matcher, enabled, elevateInContent))
     }
 
     override fun timestampRange(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
index 5936d25..0ddeb7c 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
@@ -33,7 +33,7 @@
         content: ContentKey,
         element: ElementKey,
         transition: TransitionState.Transition,
-        value: IntSize,
+        idleValue: IntSize,
     ): IntSize {
         fun anchorSizeIn(content: ContentKey): IntSize {
             val size =
@@ -45,8 +45,8 @@
                     )
 
             return IntSize(
-                width = if (anchorWidth) size.width else value.width,
-                height = if (anchorHeight) size.height else value.height,
+                width = if (anchorWidth) size.width else idleValue.width,
+                height = if (anchorHeight) size.height else idleValue.height,
             )
         }
 
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
index 0a59dfe..47508b4 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
@@ -31,7 +31,7 @@
         content: ContentKey,
         element: ElementKey,
         transition: TransitionState.Transition,
-        value: Offset,
+        idleValue: Offset,
     ): Offset {
         fun throwException(content: ContentKey?): Nothing {
             throwMissingAnchorException(
@@ -51,9 +51,9 @@
         val offset = anchorToOffset - anchorFromOffset
 
         return if (content == transition.toContent) {
-            Offset(value.x - offset.x, value.y - offset.y)
+            Offset(idleValue.x - offset.x, idleValue.y - offset.y)
         } else {
-            Offset(value.x + offset.x, value.y + offset.y)
+            Offset(idleValue.x + offset.x, idleValue.y + offset.y)
         }
     }
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt
index 7223dad..8488ae5 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt
@@ -33,12 +33,11 @@
     private val scaleY: Float,
     private val pivot: Offset = Offset.Unspecified,
 ) : PropertyTransformation<Scale> {
-
     override fun PropertyTransformationScope.transform(
         content: ContentKey,
         element: ElementKey,
         transition: TransitionState.Transition,
-        value: Scale,
+        idleValue: Scale,
     ): Scale {
         return Scale(scaleX, scaleY, pivot)
     }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
index 4ae07c5..884aae4b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
@@ -33,37 +33,37 @@
         content: ContentKey,
         element: ElementKey,
         transition: TransitionState.Transition,
-        value: Offset,
+        idleValue: Offset,
     ): Offset {
         val sceneSize =
             content.targetSize()
                 ?: error("Content ${content.debugName} does not have a target size")
-        val elementSize = element.targetSize(content) ?: return value
+        val elementSize = element.targetSize(content) ?: return idleValue
 
         return when (edge.resolve(layoutDirection)) {
             Edge.Resolved.Top ->
                 if (startsOutsideLayoutBounds) {
-                    Offset(value.x, -elementSize.height.toFloat())
+                    Offset(idleValue.x, -elementSize.height.toFloat())
                 } else {
-                    Offset(value.x, 0f)
+                    Offset(idleValue.x, 0f)
                 }
             Edge.Resolved.Left ->
                 if (startsOutsideLayoutBounds) {
-                    Offset(-elementSize.width.toFloat(), value.y)
+                    Offset(-elementSize.width.toFloat(), idleValue.y)
                 } else {
-                    Offset(0f, value.y)
+                    Offset(0f, idleValue.y)
                 }
             Edge.Resolved.Bottom ->
                 if (startsOutsideLayoutBounds) {
-                    Offset(value.x, sceneSize.height.toFloat())
+                    Offset(idleValue.x, sceneSize.height.toFloat())
                 } else {
-                    Offset(value.x, (sceneSize.height - elementSize.height).toFloat())
+                    Offset(idleValue.x, (sceneSize.height - elementSize.height).toFloat())
                 }
             Edge.Resolved.Right ->
                 if (startsOutsideLayoutBounds) {
-                    Offset(sceneSize.width.toFloat(), value.y)
+                    Offset(sceneSize.width.toFloat(), idleValue.y)
                 } else {
-                    Offset((sceneSize.width - elementSize.width).toFloat(), value.y)
+                    Offset((sceneSize.width - elementSize.width).toFloat(), idleValue.y)
                 }
         }
     }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt
index c11ec97..ef769e7 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt
@@ -27,7 +27,7 @@
         content: ContentKey,
         element: ElementKey,
         transition: TransitionState.Transition,
-        value: Float,
+        idleValue: Float,
     ): Float {
         // Return the alpha value of [element] either when it starts fading in or when it finished
         // fading out, which is `0` in both cases.
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
index a159a5b..ef3654b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
@@ -36,11 +36,11 @@
         content: ContentKey,
         element: ElementKey,
         transition: TransitionState.Transition,
-        value: IntSize,
+        idleValue: IntSize,
     ): IntSize {
         return IntSize(
-            width = (value.width * width).roundToInt(),
-            height = (value.height * height).roundToInt(),
+            width = (idleValue.width * width).roundToInt(),
+            height = (idleValue.height * height).roundToInt(),
         )
     }
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
index d38067d..74a3ead 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
@@ -36,14 +36,6 @@
      */
     val matcher: ElementMatcher
 
-    /**
-     * The range during which the transformation is applied. If it is `null`, then the
-     * transformation will be applied throughout the whole scene transition.
-     */
-    // TODO(b/240432457): Move this back to PropertyTransformation.
-    val range: TransformationRange?
-        get() = null
-
     /*
      * Reverse this transformation. This is called when we use Transition(from = A, to = B) when
      * animating from B to A and there is no Transition(from = B, to = A) defined.
@@ -66,14 +58,14 @@
      * - the value at progress = 100% for elements that are leaving the layout (i.e. elements in the
      *   content we are transitioning from).
      *
-     * The returned value will be interpolated using the [transition] progress and [value], the
+     * The returned value will be interpolated using the [transition] progress and [idleValue], the
      * value of the property when we are idle.
      */
     fun PropertyTransformationScope.transform(
         content: ContentKey,
         element: ElementKey,
         transition: TransitionState.Transition,
-        value: T,
+        idleValue: T,
     ): T
 }
 
@@ -82,20 +74,15 @@
     val layoutDirection: LayoutDirection
 }
 
-/**
- * A [PropertyTransformation] associated to a range. This is a helper class so that normal
- * implementations of [PropertyTransformation] don't have to take care of reversing their range when
- * they are reversed.
- */
-internal class RangedPropertyTransformation<T>(
-    val delegate: PropertyTransformation<T>,
-    override val range: TransformationRange,
-) : PropertyTransformation<T> by delegate {
-    override fun reversed(): Transformation {
-        return RangedPropertyTransformation(
-            delegate.reversed() as PropertyTransformation<T>,
-            range.reversed(),
-        )
+/** A pair consisting of a [transformation] and optional [range]. */
+class TransformationWithRange<out T : Transformation>(
+    val transformation: T,
+    val range: TransformationRange?,
+) {
+    fun reversed(): TransformationWithRange<T> {
+        if (range == null) return this
+
+        return TransformationWithRange(transformation = transformation, range = range.reversed())
     }
 }
 
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
index af0a6ed..356ed99 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
@@ -35,9 +35,9 @@
         content: ContentKey,
         element: ElementKey,
         transition: TransitionState.Transition,
-        value: Offset,
+        idleValue: Offset,
     ): Offset {
-        return Offset(value.x + x.toPx(), value.y + y.toPx())
+        return Offset(idleValue.x + x.toPx(), idleValue.y + y.toPx())
     }
 }
 
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
index f24d93f..5dad0d7 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
@@ -23,6 +23,7 @@
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource.Companion.UserInput
+import androidx.compose.ui.input.pointer.PointerType
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.LayoutDirection
@@ -51,6 +52,20 @@
 private const val SCREEN_SIZE = 100f
 private val LAYOUT_SIZE = IntSize(SCREEN_SIZE.toInt(), SCREEN_SIZE.toInt())
 
+private fun pointersInfo(
+    startedPosition: Offset = Offset.Zero,
+    pointersDown: Int = 1,
+    isMouseWheel: Boolean = false,
+    pointersDownByType: Map<PointerType, Int> = mapOf(PointerType.Touch to pointersDown),
+): PointersInfo {
+    return PointersInfo(
+        startedPosition = startedPosition,
+        pointersDown = pointersDown,
+        isMouseWheel = isMouseWheel,
+        pointersDownByType = pointersDownByType,
+    )
+}
+
 @RunWith(AndroidJUnit4::class)
 class DraggableHandlerTest {
     private class TestGestureScope(val testScope: MonotonicClockTestScope) {
@@ -126,9 +141,7 @@
         val draggableHandler = layoutImpl.draggableHandler(Orientation.Vertical)
         val horizontalDraggableHandler = layoutImpl.draggableHandler(Orientation.Horizontal)
 
-        var pointerInfoOwner: () -> PointersInfo = {
-            PointersInfo(startedPosition = Offset.Zero, pointersDown = 1, isMouseWheel = false)
-        }
+        var pointerInfoOwner: () -> PointersInfo = { pointersInfo() }
 
         fun nestedScrollConnection(
             nestedScrollBehavior: NestedScrollBehavior,
@@ -211,42 +224,32 @@
         }
 
         fun onDragStarted(
-            startedPosition: Offset = Offset.Zero,
+            pointersInfo: PointersInfo = pointersInfo(),
             overSlop: Float,
-            pointersDown: Int = 1,
             expectedConsumedOverSlop: Float = overSlop,
         ): DragController {
             // overSlop should be 0f only if the drag gesture starts with startDragImmediately
             if (overSlop == 0f) error("Consider using onDragStartedImmediately()")
             return onDragStarted(
                 draggableHandler = draggableHandler,
-                startedPosition = startedPosition,
+                pointersInfo = pointersInfo,
                 overSlop = overSlop,
-                pointersDown = pointersDown,
                 expectedConsumedOverSlop = expectedConsumedOverSlop,
             )
         }
 
-        fun onDragStartedImmediately(
-            startedPosition: Offset = Offset.Zero,
-            pointersDown: Int = 1,
-        ): DragController {
-            return onDragStarted(draggableHandler, startedPosition, overSlop = 0f, pointersDown)
+        fun onDragStartedImmediately(pointersInfo: PointersInfo = pointersInfo()): DragController {
+            return onDragStarted(draggableHandler, pointersInfo, overSlop = 0f)
         }
 
         fun onDragStarted(
             draggableHandler: DraggableHandler,
-            startedPosition: Offset = Offset.Zero,
+            pointersInfo: PointersInfo = pointersInfo(),
             overSlop: Float = 0f,
-            pointersDown: Int = 1,
             expectedConsumedOverSlop: Float = overSlop,
         ): DragController {
             val dragController =
-                draggableHandler.onDragStarted(
-                    startedPosition = startedPosition,
-                    overSlop = overSlop,
-                    pointersDown = pointersDown,
-                )
+                draggableHandler.onDragStarted(pointersInfo = pointersInfo, overSlop = overSlop)
 
             // MultiPointerDraggable will always call onDelta with the initial overSlop right after
             dragController.onDragDelta(pixels = overSlop, expectedConsumedOverSlop)
@@ -528,7 +531,8 @@
             mapOf(Swipe.Up to UserActionResult(SceneB, isIrreversible = true), Swipe.Down to SceneC)
         val dragController =
             onDragStarted(
-                startedPosition = Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE * 0.5f),
+                pointersInfo =
+                    pointersInfo(startedPosition = Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE * 0.5f)),
                 overSlop = up(fractionOfScreen = 0.2f),
             )
         assertTransition(
@@ -554,7 +558,7 @@
 
         // Start dragging from the bottom
         onDragStarted(
-            startedPosition = Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE),
+            pointersInfo = pointersInfo(startedPosition = Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE)),
             overSlop = up(fractionOfScreen = 0.1f),
         )
         assertTransition(
@@ -1051,8 +1055,8 @@
         navigateToSceneC()
 
         // Swipe up from the middle to transition to scene B.
-        val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
-        onDragStarted(startedPosition = middle, overSlop = up(0.1f))
+        val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
+        onDragStarted(pointersInfo = middle, overSlop = up(0.1f))
         assertTransition(
             currentScene = SceneC,
             fromScene = SceneC,
@@ -1067,7 +1071,7 @@
         // should intercept it. Because it is intercepted, the overSlop passed to onDragStarted()
         // should be 0f.
         assertThat(draggableHandler.shouldImmediatelyIntercept(middle)).isTrue()
-        onDragStartedImmediately(startedPosition = middle)
+        onDragStartedImmediately(pointersInfo = middle)
 
         // We should have intercepted the transition, so the transition should be the same object.
         assertTransition(
@@ -1083,9 +1087,9 @@
         // Start a new gesture from the bottom of the screen. Because swiping up from the bottom of
         // C leads to scene A (and not B), the previous transitions is *not* intercepted and we
         // instead animate from C to A.
-        val bottom = Offset(SCREEN_SIZE / 2, SCREEN_SIZE)
+        val bottom = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2, SCREEN_SIZE))
         assertThat(draggableHandler.shouldImmediatelyIntercept(bottom)).isFalse()
-        onDragStarted(startedPosition = bottom, overSlop = up(0.1f))
+        onDragStarted(pointersInfo = bottom, overSlop = up(0.1f))
 
         assertTransition(
             currentScene = SceneC,
@@ -1102,8 +1106,8 @@
         navigateToSceneC()
 
         // Swipe up from the middle to transition to scene B.
-        val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
-        onDragStarted(startedPosition = middle, overSlop = up(0.1f))
+        val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
+        onDragStarted(pointersInfo = middle, overSlop = up(0.1f))
         assertTransition(fromScene = SceneC, toScene = SceneB, isUserInputOngoing = true)
 
         // The current transition can be intercepted.
@@ -1119,15 +1123,15 @@
 
     @Test
     fun interruptedTransitionCanNotBeImmediatelyIntercepted() = runGestureTest {
-        assertThat(draggableHandler.shouldImmediatelyIntercept(startedPosition = null)).isFalse()
+        assertThat(draggableHandler.shouldImmediatelyIntercept(pointersInfo = null)).isFalse()
         onDragStarted(overSlop = up(0.1f))
-        assertThat(draggableHandler.shouldImmediatelyIntercept(startedPosition = null)).isTrue()
+        assertThat(draggableHandler.shouldImmediatelyIntercept(pointersInfo = null)).isTrue()
 
         layoutState.startTransitionImmediately(
             animationScope = testScope.backgroundScope,
             transition(SceneA, SceneB),
         )
-        assertThat(draggableHandler.shouldImmediatelyIntercept(startedPosition = null)).isFalse()
+        assertThat(draggableHandler.shouldImmediatelyIntercept(pointersInfo = null)).isFalse()
     }
 
     @Test
@@ -1159,7 +1163,7 @@
         assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
 
         // Intercept the transition and swipe down back to scene A.
-        assertThat(draggableHandler.shouldImmediatelyIntercept(startedPosition = null)).isTrue()
+        assertThat(draggableHandler.shouldImmediatelyIntercept(pointersInfo = null)).isTrue()
         val dragController2 = onDragStartedImmediately()
 
         // Block the transition when the user release their finger.
@@ -1203,9 +1207,7 @@
         val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways)
 
         // Drag from the **top** of the screen
-        pointerInfoOwner = {
-            PointersInfo(startedPosition = Offset(0f, 0f), pointersDown = 1, isMouseWheel = false)
-        }
+        pointerInfoOwner = { pointersInfo() }
         assertIdle(currentScene = SceneC)
 
         nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f))
@@ -1222,13 +1224,7 @@
         advanceUntilIdle()
 
         // Drag from the **bottom** of the screen
-        pointerInfoOwner = {
-            PointersInfo(
-                startedPosition = Offset(0f, SCREEN_SIZE),
-                pointersDown = 1,
-                isMouseWheel = false,
-            )
-        }
+        pointerInfoOwner = { pointersInfo(startedPosition = Offset(0f, SCREEN_SIZE)) }
         assertIdle(currentScene = SceneC)
 
         nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f))
@@ -1248,9 +1244,7 @@
         val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways)
 
         // Use mouse wheel
-        pointerInfoOwner = {
-            PointersInfo(startedPosition = Offset(0f, 0f), pointersDown = 1, isMouseWheel = true)
-        }
+        pointerInfoOwner = { pointersInfo(isMouseWheel = true) }
         assertIdle(currentScene = SceneC)
 
         nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f))
@@ -1260,8 +1254,8 @@
     @Test
     fun transitionIsImmediatelyUpdatedWhenReleasingFinger() = runGestureTest {
         // Swipe up from the middle to transition to scene B.
-        val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
-        val dragController = onDragStarted(startedPosition = middle, overSlop = up(0.1f))
+        val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
+        val dragController = onDragStarted(pointersInfo = middle, overSlop = up(0.1f))
         assertTransition(fromScene = SceneA, toScene = SceneB, isUserInputOngoing = true)
 
         dragController.onDragStoppedAnimateLater(velocity = 0f)
@@ -1274,10 +1268,10 @@
         layoutState.transitions = transitions { overscrollDisabled(SceneB, Orientation.Vertical) }
 
         // Swipe up to scene B at progress = 200%.
-        val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
+        val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
         val dragController =
             onDragStarted(
-                startedPosition = middle,
+                pointersInfo = middle,
                 overSlop = up(2f),
                 // Overscroll is disabled, it will scroll up to 100%
                 expectedConsumedOverSlop = up(1f),
@@ -1305,8 +1299,8 @@
         layoutState.transitions = transitions { overscrollDisabled(SceneB, Orientation.Vertical) }
 
         // Swipe up to scene B at progress = 200%.
-        val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
-        val dragController = onDragStarted(startedPosition = middle, overSlop = up(0.99f))
+        val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
+        val dragController = onDragStarted(pointersInfo = middle, overSlop = up(0.99f))
         assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.99f)
 
         // Release the finger.
@@ -1351,9 +1345,9 @@
             overscroll(SceneB, Orientation.Vertical) { fade(TestElements.Foo) }
         }
 
-        val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
+        val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
 
-        val dragController = onDragStarted(startedPosition = middle, overSlop = up(0.5f))
+        val dragController = onDragStarted(pointersInfo = middle, overSlop = up(0.5f))
         val transition = assertThat(transitionState).isSceneTransition()
         assertThat(transition).hasFromScene(SceneA)
         assertThat(transition).hasToScene(SceneB)
@@ -1383,9 +1377,9 @@
             overscroll(SceneC, Orientation.Vertical) { fade(TestElements.Foo) }
         }
 
-        val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
+        val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
 
-        val dragController = onDragStarted(startedPosition = middle, overSlop = down(0.5f))
+        val dragController = onDragStarted(pointersInfo = middle, overSlop = down(0.5f))
         val transition = assertThat(transitionState).isSceneTransition()
         assertThat(transition).hasFromScene(SceneA)
         assertThat(transition).hasToScene(SceneC)
@@ -1414,9 +1408,9 @@
             overscroll(SceneB, Orientation.Vertical) { fade(TestElements.Foo) }
         }
 
-        val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
+        val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
 
-        val dragController = onDragStarted(startedPosition = middle, overSlop = up(1.5f))
+        val dragController = onDragStarted(pointersInfo = middle, overSlop = up(1.5f))
         val transition = assertThat(transitionState).isSceneTransition()
         assertThat(transition).hasFromScene(SceneA)
         assertThat(transition).hasToScene(SceneB)
@@ -1446,9 +1440,9 @@
             overscroll(SceneC, Orientation.Vertical) { fade(TestElements.Foo) }
         }
 
-        val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
+        val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
 
-        val dragController = onDragStarted(startedPosition = middle, overSlop = down(1.5f))
+        val dragController = onDragStarted(pointersInfo = middle, overSlop = down(1.5f))
         val transition = assertThat(transitionState).isSceneTransition()
         assertThat(transition).hasFromScene(SceneA)
         assertThat(transition).hasToScene(SceneC)
@@ -1480,8 +1474,8 @@
 
         mutableUserActionsA = mapOf(Swipe.Up to UserActionResult(SceneB))
 
-        val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
-        val dragController = onDragStarted(startedPosition = middle, overSlop = down(1f))
+        val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
+        val dragController = onDragStarted(pointersInfo = middle, overSlop = down(1f))
         val transition = assertThat(transitionState).isSceneTransition()
         assertThat(transition).hasFromScene(SceneA)
         assertThat(transition).hasToScene(SceneB)
@@ -1513,8 +1507,8 @@
 
         mutableUserActionsA = mapOf(Swipe.Down to UserActionResult(SceneC))
 
-        val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
-        val dragController = onDragStarted(startedPosition = middle, overSlop = up(1f))
+        val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
+        val dragController = onDragStarted(pointersInfo = middle, overSlop = up(1f))
         val transition = assertThat(transitionState).isSceneTransition()
         assertThat(transition).hasFromScene(SceneA)
         assertThat(transition).hasToScene(SceneC)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
index 3df6087..5ec74f8 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
@@ -98,7 +98,7 @@
                         Modifier.multiPointerDraggable(
                             orientation = Orientation.Vertical,
                             startDragImmediately = { false },
-                            onDragStarted = { _, _, _ ->
+                            onDragStarted = { _, _ ->
                                 started = true
                                 SimpleDragController(
                                     onDrag = { dragged = true },
@@ -167,7 +167,7 @@
                         orientation = Orientation.Vertical,
                         // We want to start a drag gesture immediately
                         startDragImmediately = { true },
-                        onDragStarted = { _, _, _ ->
+                        onDragStarted = { _, _ ->
                             started = true
                             SimpleDragController(
                                 onDrag = { dragged = true },
@@ -239,7 +239,7 @@
                     .multiPointerDraggable(
                         orientation = Orientation.Vertical,
                         startDragImmediately = { false },
-                        onDragStarted = { _, _, _ ->
+                        onDragStarted = { _, _ ->
                             started = true
                             SimpleDragController(
                                 onDrag = { dragged = true },
@@ -358,7 +358,7 @@
                     .multiPointerDraggable(
                         orientation = Orientation.Vertical,
                         startDragImmediately = { false },
-                        onDragStarted = { _, _, _ ->
+                        onDragStarted = { _, _ ->
                             started = true
                             SimpleDragController(
                                 onDrag = { dragged = true },
@@ -463,7 +463,7 @@
                     .multiPointerDraggable(
                         orientation = Orientation.Vertical,
                         startDragImmediately = { false },
-                        onDragStarted = { _, _, _ ->
+                        onDragStarted = { _, _ ->
                             verticalStarted = true
                             SimpleDragController(
                                 onDrag = { verticalDragged = true },
@@ -475,7 +475,7 @@
                     .multiPointerDraggable(
                         orientation = Orientation.Horizontal,
                         startDragImmediately = { false },
-                        onDragStarted = { _, _, _ ->
+                        onDragStarted = { _, _ ->
                             horizontalStarted = true
                             SimpleDragController(
                                 onDrag = { horizontalDragged = true },
@@ -574,7 +574,7 @@
                                     return swipeConsume
                                 }
                             },
-                        onDragStarted = { _, _, _ ->
+                        onDragStarted = { _, _ ->
                             started = true
                             SimpleDragController(
                                 onDrag = { /* do nothing */ },
@@ -668,7 +668,7 @@
                     .multiPointerDraggable(
                         orientation = Orientation.Vertical,
                         startDragImmediately = { false },
-                        onDragStarted = { _, _, _ ->
+                        onDragStarted = { _, _ ->
                             SimpleDragController(
                                 onDrag = { consumedOnDrag = it },
                                 onStop = { consumedOnDragStop = it },
@@ -739,7 +739,7 @@
                     .multiPointerDraggable(
                         orientation = Orientation.Vertical,
                         startDragImmediately = { false },
-                        onDragStarted = { _, _, _ ->
+                        onDragStarted = { _, _ ->
                             SimpleDragController(
                                 onDrag = { /* do nothing */ },
                                 onStop = {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
index 2bc9b38..aaeaba9 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
@@ -38,6 +38,7 @@
 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource
 import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.input.pointer.PointerType
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.platform.LocalViewConfiguration
 import androidx.compose.ui.platform.testTag
@@ -61,6 +62,7 @@
 import com.android.compose.animation.scene.TestScenes.SceneA
 import com.android.compose.animation.scene.TestScenes.SceneB
 import com.android.compose.animation.scene.TestScenes.SceneC
+import com.android.compose.animation.scene.TestScenes.SceneD
 import com.android.compose.animation.scene.subjects.assertThat
 import com.google.common.truth.Truth.assertThat
 import org.junit.Rule
@@ -127,6 +129,7 @@
                         mapOf(
                             Swipe.Down to SceneA,
                             Swipe(SwipeDirection.Down, pointerCount = 2) to SceneB,
+                            Swipe(SwipeDirection.Down, pointersType = PointerType.Mouse) to SceneD,
                             Swipe(SwipeDirection.Right, fromSource = Edge.Left) to SceneB,
                             Swipe(SwipeDirection.Down, fromSource = Edge.Top) to SceneB,
                         )
@@ -134,6 +137,12 @@
             ) {
                 Box(Modifier.fillMaxSize())
             }
+            scene(
+                key = SceneD,
+                userActions = if (swipesEnabled()) mapOf(Swipe.Up to SceneC) else emptyMap(),
+            ) {
+                Box(Modifier.fillMaxSize())
+            }
         }
     }
 
@@ -502,6 +511,45 @@
     }
 
     @Test
+    fun mousePointerSwipe() {
+        // Start at scene C.
+        val layoutState = layoutState(SceneC)
+
+        // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
+        // detected as a drag event.
+        var touchSlop = 0f
+        rule.setContent {
+            touchSlop = LocalViewConfiguration.current.touchSlop
+            TestContent(layoutState)
+        }
+
+        assertThat(layoutState.transitionState).isIdle()
+        assertThat(layoutState.transitionState).hasCurrentScene(SceneC)
+
+        rule.onRoot().performMouseInput {
+            enter(middle)
+            press()
+            moveBy(Offset(0f, touchSlop + 10.dp.toPx()), 1_000)
+        }
+
+        // We are transitioning to D because we are moving the mouse while the primary button is
+        // pressed.
+        val transition = assertThat(layoutState.transitionState).isSceneTransition()
+        assertThat(transition).hasFromScene(SceneC)
+        assertThat(transition).hasToScene(SceneD)
+
+        rule.onRoot().performMouseInput {
+            release()
+            exit(middle)
+        }
+        // Release the mouse primary button and wait for the animation to end. We are back to C
+        // because we only swiped 10dp.
+        rule.waitForIdle()
+        assertThat(layoutState.transitionState).isIdle()
+        assertThat(layoutState.transitionState).hasCurrentScene(SceneC)
+    }
+
+    @Test
     fun mouseWheel_pointerInputApi_ignoredByStl() {
         val layoutState = layoutState()
         var touchSlop = 0f
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
index d66d6b3..d317114 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
@@ -28,8 +28,8 @@
 import com.android.compose.animation.scene.TestScenes.SceneC
 import com.android.compose.animation.scene.content.state.TransitionState
 import com.android.compose.animation.scene.transformation.OverscrollTranslate
-import com.android.compose.animation.scene.transformation.Transformation
 import com.android.compose.animation.scene.transformation.TransformationRange
+import com.android.compose.animation.scene.transformation.TransformationWithRange
 import com.android.compose.test.transition
 import com.google.common.truth.Correspondence
 import com.google.common.truth.Truth.assertThat
@@ -310,7 +310,8 @@
         }
 
         val overscrollSpec = transitions.overscrollSpecs.single()
-        val transformation = overscrollSpec.transformationSpec.transformations.single()
+        val transformation =
+            overscrollSpec.transformationSpec.transformations.single().transformation
         assertThat(transformation).isInstanceOf(OverscrollTranslate::class.java)
     }
 
@@ -344,7 +345,7 @@
 
     companion object {
         private val TRANSFORMATION_RANGE =
-            Correspondence.transforming<Transformation, TransformationRange?>(
+            Correspondence.transforming<TransformationWithRange<*>, TransformationRange?>(
                 { it?.range },
                 "has range equal to",
             )
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestValues.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestValues.kt
index f39dd67..95ef2ce 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestValues.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestValues.kt
@@ -65,4 +65,6 @@
     }
 
     from(TestScenes.SceneC, to = TestScenes.SceneA) { spec = snap() }
+
+    from(TestScenes.SceneC, to = TestScenes.SceneD) { spec = snap() }
 }
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
index 0b55a6e..d86c0d6 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
@@ -39,7 +39,7 @@
     val digitLeftTopMap = mutableMapOf<Int, Point>()
     var maxSingleDigitSize = Point(-1, -1)
     val lockscreenTranslate = Point(0, 0)
-    val aodTranslate = Point(0, 0)
+    var aodTranslate = Point(0, 0)
 
     init {
         setWillNotDraw(false)
@@ -64,8 +64,7 @@
             maxSingleDigitSize.y = max(maxSingleDigitSize.y, textView.measuredHeight)
         }
         val textView = digitalClockTextViewMap[R.id.HOUR_FIRST_DIGIT]!!
-        aodTranslate.x = -(maxSingleDigitSize.x * AOD_HORIZONTAL_TRANSLATE_RATIO).toInt()
-        aodTranslate.y = (maxSingleDigitSize.y * AOD_VERTICAL_TRANSLATE_RATIO).toInt()
+        aodTranslate = Point(0, 0)
         return Point(
             ((maxSingleDigitSize.x + abs(aodTranslate.x)) * 2),
             ((maxSingleDigitSize.y + abs(aodTranslate.y)) * 2),
@@ -162,9 +161,6 @@
         val AOD_TRANSITION_DURATION = 750L
         val CHARGING_TRANSITION_DURATION = 300L
 
-        val AOD_HORIZONTAL_TRANSLATE_RATIO = 0.15F
-        val AOD_VERTICAL_TRANSLATE_RATIO = 0.075F
-
         // Use the sign of targetTranslation to control the direction of digit translation
         fun updateDirectionalTargetTranslate(id: Int, targetTranslation: Point): Point {
             val outPoint = Point(targetTranslation)
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
index c0899e3..5c84f2d 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
@@ -148,7 +148,11 @@
         lsFontVariation = ClockFontAxisSetting.toFVar(axes + OPTICAL_SIZE_AXIS)
         lockScreenPaint.typeface = typefaceCache.getTypefaceForVariant(lsFontVariation)
         typeface = lockScreenPaint.typeface
-        textAnimator.setTextStyle(fvar = lsFontVariation, animate = true)
+
+        lockScreenPaint.getTextBounds(text, 0, text.length, textBounds)
+        targetTextBounds.set(textBounds)
+
+        textAnimator.setTextStyle(fvar = lsFontVariation, animate = false)
         measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)
         recomputeMaxSingleDigitSizes()
         requestLayout()
@@ -201,7 +205,7 @@
                     } else {
                         textBounds.height() + 2 * lockScreenPaint.strokeWidth.toInt()
                     },
-                    MeasureSpec.getMode(measuredHeight),
+                    MeasureSpec.getMode(measuredHeightAndState),
                 )
         }
 
@@ -215,10 +219,10 @@
                     } else {
                         max(
                             textBounds.width() + 2 * lockScreenPaint.strokeWidth.toInt(),
-                            MeasureSpec.getSize(measuredWidth),
+                            MeasureSpec.getSize(measuredWidthAndState),
                         )
                     },
-                    MeasureSpec.getMode(measuredWidth),
+                    MeasureSpec.getMode(measuredWidthAndState),
                 )
         }
 
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
index 2a87452..ae18aac 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
@@ -16,102 +16,19 @@
 
 package com.android.systemui.shared.settings.data.repository
 
-import android.content.ContentResolver
-import android.database.ContentObserver
 import android.provider.Settings
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.withContext
 
-/**
- * Defines interface for classes that can provide access to data from [Settings.Secure].
- * This repository doesn't guarantee to provide value across different users. For that
- * see: [UserAwareSecureSettingsRepository]
- */
+/** Defines interface for classes that can provide access to data from [Settings.Secure]. */
 interface SecureSettingsRepository {
 
     /** Returns a [Flow] tracking the value of a setting as an [Int]. */
-    fun intSetting(
-        name: String,
-        defaultValue: Int = 0,
-    ): Flow<Int>
+    fun intSetting(name: String, defaultValue: Int = 0): Flow<Int>
 
     /** Updates the value of the setting with the given name. */
-    suspend fun setInt(
-        name: String,
-        value: Int,
-    )
+    suspend fun setInt(name: String, value: Int)
 
-    suspend fun getInt(
-        name: String,
-        defaultValue: Int = 0,
-    ): Int
+    suspend fun getInt(name: String, defaultValue: Int = 0): Int
 
     suspend fun getString(name: String): String?
 }
-
-class SecureSettingsRepositoryImpl(
-    private val contentResolver: ContentResolver,
-    private val backgroundDispatcher: CoroutineDispatcher,
-) : SecureSettingsRepository {
-
-    override fun intSetting(
-        name: String,
-        defaultValue: Int,
-    ): Flow<Int> {
-        return callbackFlow {
-                val observer =
-                    object : ContentObserver(null) {
-                        override fun onChange(selfChange: Boolean) {
-                            trySend(Unit)
-                        }
-                    }
-
-                contentResolver.registerContentObserver(
-                    Settings.Secure.getUriFor(name),
-                    /* notifyForDescendants= */ false,
-                    observer,
-                )
-                send(Unit)
-
-                awaitClose { contentResolver.unregisterContentObserver(observer) }
-            }
-            .map { Settings.Secure.getInt(contentResolver, name, defaultValue) }
-            // The above work is done on the background thread (which is important for accessing
-            // settings through the content resolver).
-            .flowOn(backgroundDispatcher)
-    }
-
-    override suspend fun setInt(name: String, value: Int) {
-        withContext(backgroundDispatcher) {
-            Settings.Secure.putInt(
-                contentResolver,
-                name,
-                value,
-            )
-        }
-    }
-
-    override suspend fun getInt(name: String, defaultValue: Int): Int {
-        return withContext(backgroundDispatcher) {
-            Settings.Secure.getInt(
-                contentResolver,
-                name,
-                defaultValue,
-            )
-        }
-    }
-
-    override suspend fun getString(name: String): String? {
-        return withContext(backgroundDispatcher) {
-            Settings.Secure.getString(
-                contentResolver,
-                name,
-            )
-        }
-    }
-}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepositoryImpl.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepositoryImpl.kt
new file mode 100644
index 0000000..8b9fcb4
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepositoryImpl.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.shared.settings.data.repository
+
+import android.content.ContentResolver
+import android.database.ContentObserver
+import android.provider.Settings
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.withContext
+
+/**
+ * Simple implementation of [SecureSettingsRepository].
+ *
+ * This repository doesn't guarantee to provide value across different users, and therefore
+ * shouldn't be used in SystemUI. For that see: [UserAwareSecureSettingsRepository]
+ */
+// TODO: b/377244768 - Move to Theme/WallpaperPicker, away from SystemUI.
+class SecureSettingsRepositoryImpl(
+    private val contentResolver: ContentResolver,
+    private val backgroundDispatcher: CoroutineDispatcher,
+) : SecureSettingsRepository {
+
+    override fun intSetting(name: String, defaultValue: Int): Flow<Int> {
+        return callbackFlow {
+                val observer =
+                    object : ContentObserver(null) {
+                        override fun onChange(selfChange: Boolean) {
+                            trySend(Unit)
+                        }
+                    }
+
+                contentResolver.registerContentObserver(
+                    Settings.Secure.getUriFor(name),
+                    /* notifyForDescendants= */ false,
+                    observer,
+                )
+                send(Unit)
+
+                awaitClose { contentResolver.unregisterContentObserver(observer) }
+            }
+            .map { Settings.Secure.getInt(contentResolver, name, defaultValue) }
+            // The above work is done on the background thread (which is important for accessing
+            // settings through the content resolver).
+            .flowOn(backgroundDispatcher)
+    }
+
+    override suspend fun setInt(name: String, value: Int) {
+        withContext(backgroundDispatcher) { Settings.Secure.putInt(contentResolver, name, value) }
+    }
+
+    override suspend fun getInt(name: String, defaultValue: Int): Int {
+        return withContext(backgroundDispatcher) {
+            Settings.Secure.getInt(contentResolver, name, defaultValue)
+        }
+    }
+
+    override suspend fun getString(name: String): String? {
+        return withContext(backgroundDispatcher) {
+            Settings.Secure.getString(contentResolver, name)
+        }
+    }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepository.kt
index afe82fb..8cda9b3 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepository.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepository.kt
@@ -16,102 +16,19 @@
 
 package com.android.systemui.shared.settings.data.repository
 
-import android.content.ContentResolver
-import android.database.ContentObserver
 import android.provider.Settings
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.withContext
 
-/**
- * Defines interface for classes that can provide access to data from [Settings.System]. This
- * repository doesn't guarantee to provide value across different users. For that see:
- * [UserAwareSecureSettingsRepository] which does that for secure settings.
- */
+/** Interface for classes that can provide access to data from [Settings.System]. */
 interface SystemSettingsRepository {
 
     /** Returns a [Flow] tracking the value of a setting as an [Int]. */
-    fun intSetting(
-        name: String,
-        defaultValue: Int = 0,
-    ): Flow<Int>
+    fun intSetting(name: String, defaultValue: Int = 0): Flow<Int>
 
     /** Updates the value of the setting with the given name. */
-    suspend fun setInt(
-        name: String,
-        value: Int,
-    )
+    suspend fun setInt(name: String, value: Int)
 
-    suspend fun getInt(
-        name: String,
-        defaultValue: Int = 0,
-    ): Int
+    suspend fun getInt(name: String, defaultValue: Int = 0): Int
 
     suspend fun getString(name: String): String?
 }
-
-class SystemSettingsRepositoryImpl(
-    private val contentResolver: ContentResolver,
-    private val backgroundDispatcher: CoroutineDispatcher,
-) : SystemSettingsRepository {
-
-    override fun intSetting(
-        name: String,
-        defaultValue: Int,
-    ): Flow<Int> {
-        return callbackFlow {
-                val observer =
-                    object : ContentObserver(null) {
-                        override fun onChange(selfChange: Boolean) {
-                            trySend(Unit)
-                        }
-                    }
-
-                contentResolver.registerContentObserver(
-                    Settings.System.getUriFor(name),
-                    /* notifyForDescendants= */ false,
-                    observer,
-                )
-                send(Unit)
-
-                awaitClose { contentResolver.unregisterContentObserver(observer) }
-            }
-            .map { Settings.System.getInt(contentResolver, name, defaultValue) }
-            // The above work is done on the background thread (which is important for accessing
-            // settings through the content resolver).
-            .flowOn(backgroundDispatcher)
-    }
-
-    override suspend fun setInt(name: String, value: Int) {
-        withContext(backgroundDispatcher) {
-            Settings.System.putInt(
-                contentResolver,
-                name,
-                value,
-            )
-        }
-    }
-
-    override suspend fun getInt(name: String, defaultValue: Int): Int {
-        return withContext(backgroundDispatcher) {
-            Settings.System.getInt(
-                contentResolver,
-                name,
-                defaultValue,
-            )
-        }
-    }
-
-    override suspend fun getString(name: String): String? {
-        return withContext(backgroundDispatcher) {
-            Settings.System.getString(
-                contentResolver,
-                name,
-            )
-        }
-    }
-}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepositoryImpl.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepositoryImpl.kt
new file mode 100644
index 0000000..b039a32
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepositoryImpl.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 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.shared.settings.data.repository
+
+import android.content.ContentResolver
+import android.database.ContentObserver
+import android.provider.Settings
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.withContext
+
+/**
+ * Defines interface for classes that can provide access to data from [Settings.System].
+ *
+ * This repository doesn't guarantee to provide value across different users. For that see:
+ * [UserAwareSystemSettingsRepository].
+ */
+// TODO: b/377244768 - Move to Theme/WallpaperPicker, away from SystemUI.
+class SystemSettingsRepositoryImpl(
+    private val contentResolver: ContentResolver,
+    private val backgroundDispatcher: CoroutineDispatcher,
+) : SystemSettingsRepository {
+
+    override fun intSetting(name: String, defaultValue: Int): Flow<Int> {
+        return callbackFlow {
+                val observer =
+                    object : ContentObserver(null) {
+                        override fun onChange(selfChange: Boolean) {
+                            trySend(Unit)
+                        }
+                    }
+
+                contentResolver.registerContentObserver(
+                    Settings.System.getUriFor(name),
+                    /* notifyForDescendants= */ false,
+                    observer,
+                )
+                send(Unit)
+
+                awaitClose { contentResolver.unregisterContentObserver(observer) }
+            }
+            .map { Settings.System.getInt(contentResolver, name, defaultValue) }
+            // The above work is done on the background thread (which is important for accessing
+            // settings through the content resolver).
+            .flowOn(backgroundDispatcher)
+    }
+
+    override suspend fun setInt(name: String, value: Int) {
+        withContext(backgroundDispatcher) { Settings.System.putInt(contentResolver, name, value) }
+    }
+
+    override suspend fun getInt(name: String, defaultValue: Int): Int {
+        return withContext(backgroundDispatcher) {
+            Settings.System.getInt(contentResolver, name, defaultValue)
+        }
+    }
+
+    override suspend fun getString(name: String): String? {
+        return withContext(backgroundDispatcher) {
+            Settings.System.getString(contentResolver, name)
+        }
+    }
+}
diff --git a/packages/SystemUI/docs/demo_mode.md b/packages/SystemUI/docs/demo_mode.md
index ade5171..c18d1f1 100644
--- a/packages/SystemUI/docs/demo_mode.md
+++ b/packages/SystemUI/docs/demo_mode.md
@@ -35,6 +35,7 @@
 |                      | ```fully```                |                  | Sets MCS state to fully connected (```true```, ```false```)
 |                      | ```wifi```                 |                  | ```show``` to show icon, any other value to hide
 |                      |                            | ```level```      | Sets wifi level (null or 0-4)
+|                      |                            | ```hotspot```    | Sets the wifi to be from an Instant Hotspot. Values: ```none```, ```unknown```, ```phone```, ```tablet```, ```laptop```, ```watch```, ```auto```. (See `DemoModeWifiDataSource.kt`.)
 |                      | ```mobile```               |                  | ```show``` to show icon, any other value to hide
 |                      |                            | ```datatype```   | Values: ```1x```, ```3g```, ```4g```, ```e```, ```g```, ```h```, ```lte```, ```roam```, any other value to hide
 |                      |                            | ```level```      | Sets mobile signal strength level (null or 0-4)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/OnTeardownRuleTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/OnTeardownRuleTest.kt
new file mode 100644
index 0000000..8635bb0
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/OnTeardownRuleTest.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2024 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
+
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.fail
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.JUnitCore
+
+@Suppress("JUnitMalformedDeclaration")
+@SmallTest
+class OnTeardownRuleTest : SysuiTestCase() {
+    // None of these inner classes should be run except as part of this utilities-testing test
+    class HasTeardown {
+        @get:Rule val teardownRule = OnTeardownRule()
+
+        @Before
+        fun setUp() {
+            teardownWasRun = false
+            teardownRule.onTeardown { teardownWasRun = true }
+        }
+
+        @Test fun doTest() {}
+
+        companion object {
+            var teardownWasRun = false
+        }
+    }
+
+    @Test
+    fun teardownRuns() {
+        val result = JUnitCore().run(HasTeardown::class.java)
+        assertThat(result.failures).isEmpty()
+        assertThat(HasTeardown.teardownWasRun).isTrue()
+    }
+
+    class FirstTeardownFails {
+        @get:Rule val teardownRule = OnTeardownRule()
+
+        @Before
+        fun setUp() {
+            teardownWasRun = false
+            teardownRule.onTeardown { fail("One fails") }
+            teardownRule.onTeardown { teardownWasRun = true }
+        }
+
+        @Test fun doTest() {}
+
+        companion object {
+            var teardownWasRun = false
+        }
+    }
+
+    @Test
+    fun allTeardownsRun() {
+        val result = JUnitCore().run(FirstTeardownFails::class.java)
+        assertThat(result.failures.map { it.message }).isEqualTo(listOf("One fails"))
+        assertThat(FirstTeardownFails.teardownWasRun).isTrue()
+    }
+
+    class ThreeTeardowns {
+        @get:Rule val teardownRule = OnTeardownRule()
+
+        @Before
+        fun setUp() {
+            messages.clear()
+        }
+
+        @Test
+        fun doTest() {
+            teardownRule.onTeardown { messages.add("A") }
+            teardownRule.onTeardown { messages.add("B") }
+            teardownRule.onTeardown { messages.add("C") }
+        }
+
+        companion object {
+            val messages = mutableListOf<String>()
+        }
+    }
+
+    @Test
+    fun reverseOrder() {
+        val result = JUnitCore().run(ThreeTeardowns::class.java)
+        assertThat(result.failures).isEmpty()
+        assertThat(ThreeTeardowns.messages).isEqualTo(listOf("C", "B", "A"))
+    }
+
+    class TryToDoABadThing {
+        @get:Rule val teardownRule = OnTeardownRule()
+
+        @Test
+        fun doTest() {
+            teardownRule.onTeardown {
+                teardownRule.onTeardown {
+                    // do nothing
+                }
+            }
+        }
+    }
+
+    @Test
+    fun prohibitTeardownDuringTeardown() {
+        val result = JUnitCore().run(TryToDoABadThing::class.java)
+        assertThat(result.failures.map { it.message })
+            .isEqualTo(listOf("Cannot add new teardown routines after test complete."))
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
index c74d340..b087ecf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
@@ -19,7 +19,6 @@
 import static com.android.systemui.accessibility.MagnificationImpl.DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
@@ -190,8 +189,7 @@
 
     @Test
     public void showMagnificationButton_delayedShowButton() throws RemoteException {
-        // magnification settings panel should not be showing
-        assertFalse(mMagnification.isMagnificationSettingsPanelShowing(TEST_DISPLAY));
+        when(mMagnificationSettingsController.isMagnificationSettingsShowing()).thenReturn(false);
 
         mIMagnificationConnection.showMagnificationButton(TEST_DISPLAY,
                 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
@@ -237,8 +235,7 @@
     @Test
     public void removeMagnificationButton_delayingShowButton_doNotShowButtonAfterTimeout()
             throws RemoteException {
-        // magnification settings panel should not be showing
-        assertFalse(mMagnification.isMagnificationSettingsPanelShowing(TEST_DISPLAY));
+        when(mMagnificationSettingsController.isMagnificationSettingsShowing()).thenReturn(false);
 
         mIMagnificationConnection.showMagnificationButton(TEST_DISPLAY,
                 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/OWNERS b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/OWNERS
new file mode 100644
index 0000000..a2001e6
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/view/accessibility/OWNERS
\ No newline at end of file
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index 25696bf..f41d5c8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2024 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.
@@ -20,7 +20,6 @@
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
-import static android.view.Choreographer.FrameCallback;
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_UP;
 import static android.view.WindowInsets.Type.systemGestures;
@@ -28,14 +27,8 @@
 
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP;
 
-import static org.hamcrest.Matchers.containsString;
-import static org.hamcrest.Matchers.hasItems;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.AdditionalAnswers.returnsSecondArg;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyFloat;
@@ -45,13 +38,17 @@
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import static java.util.Arrays.asList;
+
 import android.animation.ValueAnimator;
 import android.annotation.IdRes;
 import android.annotation.Nullable;
@@ -63,38 +60,36 @@
 import android.graphics.Insets;
 import android.graphics.PointF;
 import android.graphics.Rect;
-import android.graphics.Region;
-import android.graphics.RegionIterator;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.os.SystemClock;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.provider.Settings;
 import android.testing.TestableLooper;
 import android.testing.TestableResources;
-import android.text.TextUtils;
 import android.util.Size;
+import android.view.AttachedSurfaceControl;
 import android.view.Display;
-import android.view.IWindowSession;
+import android.view.Gravity;
 import android.view.MotionEvent;
 import android.view.Surface;
 import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
 import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewRootImpl;
 import android.view.WindowInsets;
 import android.view.WindowManager;
-import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.IRemoteMagnificationAnimationCallback;
+import android.widget.FrameLayout;
+import android.window.InputTransferToken;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.LargeTest;
 
-import com.android.app.viewcapture.ViewCapture;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
-import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.AnimatorTestRule;
@@ -109,8 +104,6 @@
 
 import com.google.common.util.concurrent.AtomicDouble;
 
-import kotlin.Lazy;
-
 import org.junit.After;
 import org.junit.Assume;
 import org.junit.Before;
@@ -118,31 +111,27 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Answers;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Supplier;
 
 @LargeTest
 @TestableLooper.RunWithLooper
 @RunWith(AndroidJUnit4.class)
-@RequiresFlagsDisabled(Flags.FLAG_CREATE_WINDOWLESS_WINDOW_MAGNIFIER)
 public class WindowMagnificationControllerTest extends SysuiTestCase {
 
     @Rule
     // NOTE: pass 'null' to allow this test advances time on the main thread.
-    public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(null);
-    @Rule
-    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+    public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(/* test= */ null);
 
     private static final int LAYOUT_CHANGE_TIMEOUT_MS = 5000;
     @Mock
-    private SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
-    @Mock
     private MirrorWindowControl mMirrorWindowControl;
     @Mock
     private WindowMagnifierCallback mWindowMagnifierCallback;
@@ -150,12 +139,10 @@
     IRemoteMagnificationAnimationCallback mAnimationCallback;
     @Mock
     IRemoteMagnificationAnimationCallback mAnimationCallback2;
-    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
-    private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+
+    private SurfaceControl.Transaction mTransaction;
     @Mock
     private SecureSettings mSecureSettings;
-    @Mock
-    private Lazy<ViewCapture> mLazyViewCapture;
 
     private long mWaitAnimationDuration;
     private long mWaitBounceEffectDuration;
@@ -170,11 +157,17 @@
     private final ValueAnimator mValueAnimator = ValueAnimator.ofFloat(0, 1.0f).setDuration(0);
     private final FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
 
-    private IWindowSession mWindowSessionSpy;
-
     private View mSpyView;
     private View.OnTouchListener mTouchListener;
+
     private MotionEventHelper mMotionEventHelper = new MotionEventHelper();
+
+    // This list contains all SurfaceControlViewHosts created during a given test. If the
+    // magnification window is recreated during a test, the list will contain more than a single
+    // element.
+    private List<SurfaceControlViewHost> mSurfaceControlViewHosts = new ArrayList<>();
+    // The most recently created SurfaceControlViewHost.
+    private SurfaceControlViewHost mSurfaceControlViewHost;
     private KosmosJavaAdapter mKosmos;
     private FakeSharedPreferences mSharedPreferences;
 
@@ -196,15 +189,7 @@
         final WindowManager wm = mContext.getSystemService(WindowManager.class);
         mWindowManager = spy(new TestableWindowManager(wm));
 
-        mWindowSessionSpy = spy(WindowManagerGlobal.getWindowSession());
-
         mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
-        doAnswer(invocation -> {
-            FrameCallback callback = invocation.getArgument(0);
-            callback.doFrame(0);
-            return null;
-        }).when(mSfVsyncFrameProvider).postFrameCallback(
-                any(FrameCallback.class));
         mSysUiState = new SysUiState(mDisplayTracker, mKosmos.getSceneContainerPlugin());
         mSysUiState.addCallback(Mockito.mock(SysUiState.SysUiStateCallback.class));
         when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).then(
@@ -228,13 +213,20 @@
 
         mWindowMagnificationAnimationController = new WindowMagnificationAnimationController(
                 mContext, mValueAnimator);
+        Supplier<SurfaceControlViewHost> scvhSupplier = () -> {
+            mSurfaceControlViewHost = spy(new SurfaceControlViewHost(
+                    mContext, mContext.getDisplay(), new InputTransferToken(),
+                    "WindowMagnification"));
+            ViewRootImpl viewRoot = mock(ViewRootImpl.class);
+            when(mSurfaceControlViewHost.getRootSurfaceControl()).thenReturn(viewRoot);
+            mSurfaceControlViewHosts.add(mSurfaceControlViewHost);
+            return mSurfaceControlViewHost;
+        };
+        mTransaction = spy(new SurfaceControl.Transaction());
         mSharedPreferences = new FakeSharedPreferences();
         when(mContext.getSharedPreferences(
                 eq("window_magnification_preferences"), anyInt()))
                 .thenReturn(mSharedPreferences);
-        ViewCaptureAwareWindowManager viewCaptureAwareWindowManager = new
-                ViewCaptureAwareWindowManager(mWindowManager, mLazyViewCapture,
-                /* isViewCaptureEnabled= */ false);
         mWindowMagnificationController =
                 new WindowMagnificationController(
                         mContext,
@@ -245,10 +237,7 @@
                         mWindowMagnifierCallback,
                         mSysUiState,
                         mSecureSettings,
-                        /* scvhSupplier= */ () -> null,
-                        mSfVsyncFrameProvider,
-                        /* globalWindowSessionSupplier= */ () -> mWindowSessionSpy,
-                        viewCaptureAwareWindowManager);
+                        scvhSupplier);
 
         verify(mMirrorWindowControl).setWindowDelegate(
                 any(MirrorWindowControl.MirrorWindowDelegate.class));
@@ -277,7 +266,7 @@
         verify(mSecureSettings).getIntForUser(
                 eq(Settings.Secure.ACCESSIBILITY_ALLOW_DIAGONAL_SCROLLING),
                 /* def */ eq(1), /* userHandle= */ anyInt());
-        assertTrue(mWindowMagnificationController.isDiagonalScrollingEnabled());
+        assertThat(mWindowMagnificationController.isDiagonalScrollingEnabled()).isTrue();
     }
 
     @Test
@@ -325,7 +314,8 @@
         });
         advanceTimeBy(LAYOUT_CHANGE_TIMEOUT_MS);
 
-        verify(mSfVsyncFrameProvider, atLeast(2)).postFrameCallback(any());
+        verify(mTransaction, atLeastOnce()).setGeometry(any(), any(), any(),
+                eq(Surface.ROTATION_0));
     }
 
     @Test
@@ -342,10 +332,10 @@
         final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class);
         verify(mWindowMagnifierCallback, atLeast(2)).onSourceBoundsChanged(
                 (eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
-        assertEquals(mWindowMagnificationController.getCenterX(),
-                sourceBoundsCaptor.getValue().exactCenterX(), 0);
-        assertEquals(mWindowMagnificationController.getCenterY(),
-                sourceBoundsCaptor.getValue().exactCenterY(), 0);
+        assertThat(mWindowMagnificationController.getCenterX())
+                .isEqualTo(sourceBoundsCaptor.getValue().exactCenterX());
+        assertThat(mWindowMagnificationController.getCenterY())
+                .isEqualTo(sourceBoundsCaptor.getValue().exactCenterY());
     }
 
     @Test
@@ -357,8 +347,8 @@
         // Wait for Rects updated.
         waitForIdleSync();
 
-        List<Rect> rects = mWindowManager.getAttachedView().getSystemGestureExclusionRects();
-        assertFalse(rects.isEmpty());
+        List<Rect> rects = mSurfaceControlViewHost.getView().getSystemGestureExclusionRects();
+        assertThat(rects).isNotEmpty();
     }
 
     @Ignore("The default window size should be constrained after fixing b/288056772")
@@ -373,11 +363,11 @@
         });
 
         final int halfScreenSize = screenSize / 2;
-        WindowManager.LayoutParams params = mWindowManager.getLayoutParamsFromAttachedView();
+        ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams();
         // The frame size should be the half of smaller value of window height/width unless it
         //exceed the max frame size.
-        assertTrue(params.width < halfScreenSize);
-        assertTrue(params.height < halfScreenSize);
+        assertThat(params.width).isLessThan(halfScreenSize);
+        assertThat(params.height).isLessThan(halfScreenSize);
     }
 
     @Test
@@ -411,7 +401,7 @@
         });
 
         verify(mMirrorWindowControl).destroyControl();
-        assertFalse(hasMagnificationOverlapFlag());
+        assertThat(hasMagnificationOverlapFlag()).isFalse();
     }
 
     @Test
@@ -435,10 +425,14 @@
         mInstrumentation.runOnMainSync(() -> {
             mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
-            mWindowMagnificationController.moveWindowMagnifier(100f, 100f);
         });
 
-        verify(mSfVsyncFrameProvider, atLeastOnce()).postFrameCallback(any());
+        waitForIdleSync();
+        mInstrumentation.runOnMainSync(
+                () -> mWindowMagnificationController.moveWindowMagnifier(100f, 100f));
+
+        verify(mTransaction, atLeastOnce()).setGeometry(any(), any(), any(),
+                eq(Surface.ROTATION_0));
     }
 
     @Test
@@ -455,6 +449,7 @@
         final float targetCenterX = sourceBoundsCaptor.getValue().exactCenterX() + 10;
         final float targetCenterY = sourceBoundsCaptor.getValue().exactCenterY() + 10;
 
+        reset(mWindowMagnifierCallback);
         mInstrumentation.runOnMainSync(() -> {
             mWindowMagnificationController.moveWindowMagnifierToPosition(
                     targetCenterX, targetCenterY, mAnimationCallback);
@@ -465,12 +460,12 @@
         verify(mAnimationCallback, never()).onResult(eq(false));
         verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
                 .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
-        assertEquals(mWindowMagnificationController.getCenterX(),
-                sourceBoundsCaptor.getValue().exactCenterX(), 0);
-        assertEquals(mWindowMagnificationController.getCenterY(),
-                sourceBoundsCaptor.getValue().exactCenterY(), 0);
-        assertEquals(mWindowMagnificationController.getCenterX(), targetCenterX, 0);
-        assertEquals(mWindowMagnificationController.getCenterY(), targetCenterY, 0);
+        assertThat(mWindowMagnificationController.getCenterX())
+                .isEqualTo(sourceBoundsCaptor.getValue().exactCenterX());
+        assertThat(mWindowMagnificationController.getCenterY())
+                .isEqualTo(sourceBoundsCaptor.getValue().exactCenterY());
+        assertThat(mWindowMagnificationController.getCenterX()).isEqualTo(targetCenterX);
+        assertThat(mWindowMagnificationController.getCenterY()).isEqualTo(targetCenterY);
     }
 
     @Test
@@ -487,6 +482,7 @@
         final float centerX = sourceBoundsCaptor.getValue().exactCenterX();
         final float centerY = sourceBoundsCaptor.getValue().exactCenterY();
 
+        reset(mWindowMagnifierCallback);
         mInstrumentation.runOnMainSync(() -> {
             mWindowMagnificationController.moveWindowMagnifierToPosition(
                     centerX + 10, centerY + 10, mAnimationCallback);
@@ -505,12 +501,12 @@
         verify(mAnimationCallback, times(3)).onResult(eq(false));
         verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
                 .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
-        assertEquals(mWindowMagnificationController.getCenterX(),
-                sourceBoundsCaptor.getValue().exactCenterX(), 0);
-        assertEquals(mWindowMagnificationController.getCenterY(),
-                sourceBoundsCaptor.getValue().exactCenterY(), 0);
-        assertEquals(mWindowMagnificationController.getCenterX(), centerX + 40, 0);
-        assertEquals(mWindowMagnificationController.getCenterY(), centerY + 40, 0);
+        assertThat(mWindowMagnificationController.getCenterX())
+                .isEqualTo(sourceBoundsCaptor.getValue().exactCenterX());
+        assertThat(mWindowMagnificationController.getCenterY())
+                .isEqualTo(sourceBoundsCaptor.getValue().exactCenterY());
+        assertThat(mWindowMagnificationController.getCenterX()).isEqualTo(centerX + 40);
+        assertThat(mWindowMagnificationController.getCenterY()).isEqualTo(centerY + 40);
     }
 
     @Test
@@ -521,10 +517,10 @@
 
         mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.setScale(3.0f));
 
-        assertEquals(3.0f, mWindowMagnificationController.getScale(), 0);
-        final View mirrorView = mWindowManager.getAttachedView();
-        assertNotNull(mirrorView);
-        assertThat(mirrorView.getStateDescription().toString(), containsString("300"));
+        assertThat(mWindowMagnificationController.getScale()).isEqualTo(3.0f);
+        final View mirrorView = mSurfaceControlViewHost.getView();
+        assertThat(mirrorView).isNotNull();
+        assertThat(mirrorView.getStateDescription().toString()).contains("300");
     }
 
     @Test
@@ -563,12 +559,12 @@
         mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.onConfigurationChanged(
                 ActivityInfo.CONFIG_ORIENTATION));
 
-        assertEquals(newRotation, mWindowMagnificationController.mRotation);
+        assertThat(mWindowMagnificationController.mRotation).isEqualTo(newRotation);
         final PointF expectedCenter = new PointF(magnifiedCenter.y,
                 displayWidth - magnifiedCenter.x);
         final PointF actualCenter = new PointF(mWindowMagnificationController.getCenterX(),
                 mWindowMagnificationController.getCenterY());
-        assertEquals(expectedCenter, actualCenter);
+        assertThat(actualCenter).isEqualTo(expectedCenter);
     }
 
     @Test
@@ -583,7 +579,7 @@
         mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.onConfigurationChanged(
                 ActivityInfo.CONFIG_ORIENTATION));
 
-        assertEquals(newRotation, mWindowMagnificationController.mRotation);
+        assertThat(mWindowMagnificationController.mRotation).isEqualTo(newRotation);
     }
 
     @Test
@@ -610,14 +606,13 @@
         });
 
         // The ratio of center to window size should be the same.
-        assertEquals(expectedRatio,
-                mWindowMagnificationController.getCenterX() / testWindowBounds.width(),
-                0);
-        assertEquals(expectedRatio,
-                mWindowMagnificationController.getCenterY() / testWindowBounds.height(),
-                0);
+        assertThat(mWindowMagnificationController.getCenterX() / testWindowBounds.width())
+                .isEqualTo(expectedRatio);
+        assertThat(mWindowMagnificationController.getCenterY() / testWindowBounds.height())
+                .isEqualTo(expectedRatio);
     }
 
+    @DisableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS)
     @Test
     public void onScreenSizeAndDensityChanged_enabled_restoreSavedMagnifierWindow() {
         int newSmallestScreenWidthDp =
@@ -635,7 +630,7 @@
                     Float.NaN);
         });
 
-        // Change screen density and size to trigger restoring the preferred window size
+        // Screen density and size change
         mContext.getResources().getConfiguration().smallestScreenWidthDp = newSmallestScreenWidthDp;
         final Rect testWindowBounds = new Rect(
                 mWindowManager.getCurrentWindowMetrics().getBounds());
@@ -648,12 +643,56 @@
 
         // wait for rect update
         waitForIdleSync();
-        WindowManager.LayoutParams params = mWindowManager.getLayoutParamsFromAttachedView();
+        ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams();
         final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
                 R.dimen.magnification_mirror_surface_margin);
         // The width and height of the view include the magnification frame and the margins.
-        assertTrue(params.width == (windowFrameSize + 2 * mirrorSurfaceMargin));
-        assertTrue(params.height == (windowFrameSize + 2 * mirrorSurfaceMargin));
+        assertThat(params.width).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin);
+        assertThat(params.height).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin);
+    }
+
+    @EnableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS)
+    @Test
+    public void onScreenSizeAndDensityChanged_enabled_restoreSavedMagnifierIndexAndWindow() {
+        int newSmallestScreenWidthDp =
+                mContext.getResources().getConfiguration().smallestScreenWidthDp * 2;
+        int windowFrameSize = mResources.getDimensionPixelSize(
+                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
+        Size preferredWindowSize = new Size(windowFrameSize, windowFrameSize);
+        mSharedPreferences
+                .edit()
+                .putString(String.valueOf(newSmallestScreenWidthDp),
+                        WindowMagnificationFrameSpec.serialize(
+                                WindowMagnificationSettings.MagnificationSize.CUSTOM,
+                                preferredWindowSize))
+                .commit();
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
+                    Float.NaN);
+        });
+
+        // Screen density and size change
+        mContext.getResources().getConfiguration().smallestScreenWidthDp = newSmallestScreenWidthDp;
+        final Rect testWindowBounds = new Rect(
+                mWindowManager.getCurrentWindowMetrics().getBounds());
+        testWindowBounds.set(testWindowBounds.left, testWindowBounds.top,
+                testWindowBounds.right + 100, testWindowBounds.bottom + 100);
+        mWindowManager.setWindowBounds(testWindowBounds);
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE);
+        });
+
+        // wait for rect update
+        waitForIdleSync();
+        verify(mWindowMagnifierCallback).onWindowMagnifierBoundsRestored(
+                eq(mContext.getDisplayId()),
+                eq(WindowMagnificationSettings.MagnificationSize.CUSTOM));
+        ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams();
+        final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
+                R.dimen.magnification_mirror_surface_margin);
+        // The width and height of the view include the magnification frame and the margins.
+        assertThat(params.width).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin);
+        assertThat(params.height).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin);
     }
 
     @Test
@@ -675,10 +714,10 @@
         final int defaultWindowSize =
                 mWindowMagnificationController.getMagnificationWindowSizeFromIndex(
                         WindowMagnificationSettings.MagnificationSize.MEDIUM);
-        WindowManager.LayoutParams params = mWindowManager.getLayoutParamsFromAttachedView();
+        ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams();
 
-        assertTrue(params.width == defaultWindowSize);
-        assertTrue(params.height == defaultWindowSize);
+        assertThat(params.width).isEqualTo(defaultWindowSize);
+        assertThat(params.height).isEqualTo(defaultWindowSize);
     }
 
     @Test
@@ -695,9 +734,9 @@
         });
 
         verify(mResources, atLeastOnce()).getDimensionPixelSize(anyInt());
-        verify(mWindowManager).removeView(any());
+        verify(mSurfaceControlViewHosts.get(0)).release();
         verify(mMirrorWindowControl).destroyControl();
-        verify(mWindowManager).addView(any(), any());
+        verify(mSurfaceControlViewHosts.get(1)).setView(any(), any());
         verify(mMirrorWindowControl).showControl();
     }
 
@@ -716,21 +755,30 @@
             mWindowMagnificationController.updateWindowMagnificationInternal(2.5f, Float.NaN,
                     Float.NaN);
         });
-        final View mirrorView = mWindowManager.getAttachedView();
-        assertNotNull(mirrorView);
+        final View mirrorView = mSurfaceControlViewHost.getView();
+        assertThat(mirrorView).isNotNull();
         final AccessibilityNodeInfo nodeInfo = new AccessibilityNodeInfo();
 
         mirrorView.onInitializeAccessibilityNodeInfo(nodeInfo);
 
-        assertNotNull(nodeInfo.getContentDescription());
-        assertThat(nodeInfo.getStateDescription().toString(), containsString("250"));
-        assertThat(nodeInfo.getActionList(),
-                hasItems(new AccessibilityAction(R.id.accessibility_action_zoom_in, null),
-                        new AccessibilityAction(R.id.accessibility_action_zoom_out, null),
-                        new AccessibilityAction(R.id.accessibility_action_move_right, null),
-                        new AccessibilityAction(R.id.accessibility_action_move_left, null),
-                        new AccessibilityAction(R.id.accessibility_action_move_down, null),
-                        new AccessibilityAction(R.id.accessibility_action_move_up, null)));
+        assertThat(nodeInfo.getContentDescription()).isNotNull();
+        assertThat(nodeInfo.getStateDescription().toString()).contains("250");
+        assertThat(nodeInfo.getActionList()).containsExactlyElementsIn(asList(
+                new AccessibilityAction(AccessibilityAction.ACTION_CLICK.getId(),
+                        mContext.getResources().getString(
+                                R.string.magnification_open_settings_click_label)),
+                new AccessibilityAction(R.id.accessibility_action_zoom_in,
+                        mContext.getString(R.string.accessibility_control_zoom_in)),
+                new AccessibilityAction(R.id.accessibility_action_zoom_out,
+                        mContext.getString(R.string.accessibility_control_zoom_out)),
+                new AccessibilityAction(R.id.accessibility_action_move_right,
+                        mContext.getString(R.string.accessibility_control_move_right)),
+                new AccessibilityAction(R.id.accessibility_action_move_left,
+                        mContext.getString(R.string.accessibility_control_move_left)),
+                new AccessibilityAction(R.id.accessibility_action_move_down,
+                        mContext.getString(R.string.accessibility_control_move_down)),
+                new AccessibilityAction(R.id.accessibility_action_move_up,
+                        mContext.getString(R.string.accessibility_control_move_up))));
     }
 
     @Test
@@ -741,29 +789,34 @@
                     Float.NaN);
         });
 
-        final View mirrorView = mWindowManager.getAttachedView();
-        assertTrue(
-                mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_out, null));
+        final View mirrorView = mSurfaceControlViewHost.getView();
+        assertThat(mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_out, null))
+                .isTrue();
         // Minimum scale is 1.0.
         verify(mWindowMagnifierCallback).onPerformScaleAction(
                 eq(displayId), /* scale= */ eq(1.0f), /* updatePersistence= */ eq(true));
 
-        assertTrue(mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_in, null));
+        assertThat(mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_in, null))
+                .isTrue();
         verify(mWindowMagnifierCallback).onPerformScaleAction(
                 eq(displayId), /* scale= */ eq(2.5f), /* updatePersistence= */ eq(true));
 
         // TODO: Verify the final state when the mirror surface is visible.
-        assertTrue(mirrorView.performAccessibilityAction(R.id.accessibility_action_move_up, null));
-        assertTrue(
-                mirrorView.performAccessibilityAction(R.id.accessibility_action_move_down, null));
-        assertTrue(
-                mirrorView.performAccessibilityAction(R.id.accessibility_action_move_right, null));
-        assertTrue(
-                mirrorView.performAccessibilityAction(R.id.accessibility_action_move_left, null));
+        assertThat(mirrorView.performAccessibilityAction(R.id.accessibility_action_move_up, null))
+                .isTrue();
+        assertThat(
+                mirrorView.performAccessibilityAction(R.id.accessibility_action_move_down, null))
+                .isTrue();
+        assertThat(
+                mirrorView.performAccessibilityAction(R.id.accessibility_action_move_right, null))
+                .isTrue();
+        assertThat(
+                mirrorView.performAccessibilityAction(R.id.accessibility_action_move_left, null))
+                .isTrue();
         verify(mWindowMagnifierCallback, times(4)).onMove(eq(displayId));
 
-        assertTrue(mirrorView.performAccessibilityAction(
-                AccessibilityAction.ACTION_CLICK.getId(), null));
+        assertThat(mirrorView.performAccessibilityAction(
+                AccessibilityAction.ACTION_CLICK.getId(), null)).isTrue();
         verify(mWindowMagnifierCallback).onClickSettingsButton(eq(displayId));
     }
 
@@ -775,7 +828,7 @@
                     Float.NaN);
         });
 
-        final View mirrorView = mWindowManager.getAttachedView();
+        final View mirrorView = mSurfaceControlViewHost.getView();
         mirrorView.performAccessibilityAction(R.id.accessibility_action_move_up, null);
 
         verify(mWindowMagnifierCallback).onAccessibilityActionPerformed(eq(displayId));
@@ -795,20 +848,22 @@
         View topRightCorner = getInternalView(R.id.top_right_corner);
         View topLeftCorner = getInternalView(R.id.top_left_corner);
 
-        assertEquals(View.VISIBLE, closeButton.getVisibility());
-        assertEquals(View.VISIBLE, bottomRightCorner.getVisibility());
-        assertEquals(View.VISIBLE, bottomLeftCorner.getVisibility());
-        assertEquals(View.VISIBLE, topRightCorner.getVisibility());
-        assertEquals(View.VISIBLE, topLeftCorner.getVisibility());
+        assertThat(closeButton.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(bottomRightCorner.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(bottomLeftCorner.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(topRightCorner.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(topLeftCorner.getVisibility()).isEqualTo(View.VISIBLE);
 
-        final View mirrorView = mWindowManager.getAttachedView();
-        mirrorView.performAccessibilityAction(AccessibilityAction.ACTION_CLICK.getId(), null);
+        final View mirrorView = mSurfaceControlViewHost.getView();
+        mInstrumentation.runOnMainSync(() ->
+                mirrorView.performAccessibilityAction(AccessibilityAction.ACTION_CLICK.getId(),
+                        null));
 
-        assertEquals(View.GONE, closeButton.getVisibility());
-        assertEquals(View.GONE, bottomRightCorner.getVisibility());
-        assertEquals(View.GONE, bottomLeftCorner.getVisibility());
-        assertEquals(View.GONE, topRightCorner.getVisibility());
-        assertEquals(View.GONE, topLeftCorner.getVisibility());
+        assertThat(closeButton.getVisibility()).isEqualTo(View.GONE);
+        assertThat(bottomRightCorner.getVisibility()).isEqualTo(View.GONE);
+        assertThat(bottomLeftCorner.getVisibility()).isEqualTo(View.GONE);
+        assertThat(topRightCorner.getVisibility()).isEqualTo(View.GONE);
+        assertThat(topLeftCorner.getVisibility()).isEqualTo(View.GONE);
     }
 
     @Test
@@ -828,7 +883,7 @@
             mWindowMagnificationController.setEditMagnifierSizeMode(true);
         });
 
-        final View mirrorView = mWindowManager.getAttachedView();
+        final View mirrorView = mSurfaceControlViewHost.getView();
         final AtomicInteger actualWindowHeight = new AtomicInteger();
         final AtomicInteger actualWindowWidth = new AtomicInteger();
 
@@ -836,8 +891,10 @@
                 () -> {
                     mirrorView.performAccessibilityAction(
                             R.id.accessibility_action_increase_window_width, null);
-                    actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
-                    actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
+                    actualWindowHeight.set(
+                            mSurfaceControlViewHost.getView().getLayoutParams().height);
+                    actualWindowWidth.set(
+                            mSurfaceControlViewHost.getView().getLayoutParams().width);
                 });
 
         final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
@@ -847,8 +904,8 @@
         int newWindowWidth =
                 (int) ((startingWidth - 2 * mirrorSurfaceMargin) * (1 + changeWindowSizeAmount))
                         + 2 * mirrorSurfaceMargin;
-        assertEquals(newWindowWidth, actualWindowWidth.get());
-        assertEquals(startingHeight, actualWindowHeight.get());
+        assertThat(actualWindowWidth.get()).isEqualTo(newWindowWidth);
+        assertThat(actualWindowHeight.get()).isEqualTo(startingHeight);
     }
 
     @Test
@@ -868,7 +925,7 @@
             mWindowMagnificationController.setEditMagnifierSizeMode(true);
         });
 
-        final View mirrorView = mWindowManager.getAttachedView();
+        final View mirrorView = mSurfaceControlViewHost.getView();
         final AtomicInteger actualWindowHeight = new AtomicInteger();
         final AtomicInteger actualWindowWidth = new AtomicInteger();
 
@@ -876,8 +933,10 @@
                 () -> {
                     mirrorView.performAccessibilityAction(
                             R.id.accessibility_action_increase_window_height, null);
-                    actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
-                    actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
+                    actualWindowHeight.set(
+                            mSurfaceControlViewHost.getView().getLayoutParams().height);
+                    actualWindowWidth.set(
+                            mSurfaceControlViewHost.getView().getLayoutParams().width);
                 });
 
         final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
@@ -887,8 +946,8 @@
         int newWindowHeight =
                 (int) ((startingHeight - 2 * mirrorSurfaceMargin) * (1 + changeWindowSizeAmount))
                         + 2 * mirrorSurfaceMargin;
-        assertEquals(startingWidth, actualWindowWidth.get());
-        assertEquals(newWindowHeight, actualWindowHeight.get());
+        assertThat(actualWindowWidth.get()).isEqualTo(startingWidth);
+        assertThat(actualWindowHeight.get()).isEqualTo(newWindowHeight);
     }
 
     @Test
@@ -904,11 +963,14 @@
             mWindowMagnificationController.setEditMagnifierSizeMode(true);
         });
 
-        final View mirrorView = mWindowManager.getAttachedView();
+        final View mirrorView = mSurfaceControlViewHost.getView();
         final AccessibilityNodeInfo accessibilityNodeInfo =
                 mirrorView.createAccessibilityNodeInfo();
-        assertFalse(accessibilityNodeInfo.getActionList().contains(
-                new AccessibilityAction(R.id.accessibility_action_increase_window_width, null)));
+        assertThat(accessibilityNodeInfo.getActionList()).doesNotContain(
+                new AccessibilityAction(
+                        R.id.accessibility_action_increase_window_width,
+                        mContext.getString(
+                                R.string.accessibility_control_increase_window_width)));
     }
 
     @Test
@@ -924,11 +986,12 @@
             mWindowMagnificationController.setEditMagnifierSizeMode(true);
         });
 
-        final View mirrorView = mWindowManager.getAttachedView();
+        final View mirrorView = mSurfaceControlViewHost.getView();
         final AccessibilityNodeInfo accessibilityNodeInfo =
                 mirrorView.createAccessibilityNodeInfo();
-        assertFalse(accessibilityNodeInfo.getActionList().contains(
-                new AccessibilityAction(R.id.accessibility_action_increase_window_height, null)));
+        assertThat(accessibilityNodeInfo.getActionList()).doesNotContain(
+                new AccessibilityAction(
+                        R.id.accessibility_action_increase_window_height, null));
     }
 
     @Test
@@ -947,7 +1010,7 @@
             mWindowMagnificationController.setEditMagnifierSizeMode(true);
         });
 
-        final View mirrorView = mWindowManager.getAttachedView();
+        final View mirrorView = mSurfaceControlViewHost.getView();
         final AtomicInteger actualWindowHeight = new AtomicInteger();
         final AtomicInteger actualWindowWidth = new AtomicInteger();
 
@@ -955,8 +1018,10 @@
                 () -> {
                     mirrorView.performAccessibilityAction(
                             R.id.accessibility_action_decrease_window_width, null);
-                    actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
-                    actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
+                    actualWindowHeight.set(
+                            mSurfaceControlViewHost.getView().getLayoutParams().height);
+                    actualWindowWidth.set(
+                            mSurfaceControlViewHost.getView().getLayoutParams().width);
                 });
 
         final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
@@ -966,8 +1031,8 @@
         int newWindowWidth =
                 (int) ((startingSize - 2 * mirrorSurfaceMargin) * (1 - changeWindowSizeAmount))
                         + 2 * mirrorSurfaceMargin;
-        assertEquals(newWindowWidth, actualWindowWidth.get());
-        assertEquals(startingSize, actualWindowHeight.get());
+        assertThat(actualWindowWidth.get()).isEqualTo(newWindowWidth);
+        assertThat(actualWindowHeight.get()).isEqualTo(startingSize);
     }
 
     @Test
@@ -987,7 +1052,7 @@
             mWindowMagnificationController.setEditMagnifierSizeMode(true);
         });
 
-        final View mirrorView = mWindowManager.getAttachedView();
+        final View mirrorView = mSurfaceControlViewHost.getView();
         final AtomicInteger actualWindowHeight = new AtomicInteger();
         final AtomicInteger actualWindowWidth = new AtomicInteger();
 
@@ -995,8 +1060,10 @@
                 () -> {
                     mirrorView.performAccessibilityAction(
                             R.id.accessibility_action_decrease_window_height, null);
-                    actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
-                    actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
+                    actualWindowHeight.set(
+                            mSurfaceControlViewHost.getView().getLayoutParams().height);
+                    actualWindowWidth.set(
+                            mSurfaceControlViewHost.getView().getLayoutParams().width);
                 });
 
         final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
@@ -1006,8 +1073,8 @@
         int newWindowHeight =
                 (int) ((startingSize - 2 * mirrorSurfaceMargin) * (1 - changeWindowSizeAmount))
                         + 2 * mirrorSurfaceMargin;
-        assertEquals(startingSize, actualWindowWidth.get());
-        assertEquals(newWindowHeight, actualWindowHeight.get());
+        assertThat(actualWindowWidth.get()).isEqualTo(startingSize);
+        assertThat(actualWindowHeight.get()).isEqualTo(newWindowHeight);
     }
 
     @Test
@@ -1023,15 +1090,16 @@
             mWindowMagnificationController.setEditMagnifierSizeMode(true);
         });
 
-        final View mirrorView = mWindowManager.getAttachedView();
+        final View mirrorView = mSurfaceControlViewHost.getView();
         final AccessibilityNodeInfo accessibilityNodeInfo =
                 mirrorView.createAccessibilityNodeInfo();
-        assertFalse(accessibilityNodeInfo.getActionList().contains(
-                new AccessibilityAction(R.id.accessibility_action_decrease_window_width, null)));
+        assertThat(accessibilityNodeInfo.getActionList()).doesNotContain(
+                new AccessibilityAction(
+                        R.id.accessibility_action_decrease_window_width, null));
     }
 
     @Test
-    public void windowHeightIsMin_noDecreaseWindowHeightA11yAcyion() {
+    public void windowHeightIsMin_noDecreaseWindowHeightA11yAction() {
         int mMinWindowSize = mResources.getDimensionPixelSize(
                 com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
         final int startingSize = mMinWindowSize;
@@ -1043,11 +1111,12 @@
             mWindowMagnificationController.setEditMagnifierSizeMode(true);
         });
 
-        final View mirrorView = mWindowManager.getAttachedView();
+        final View mirrorView = mSurfaceControlViewHost.getView();
         final AccessibilityNodeInfo accessibilityNodeInfo =
                 mirrorView.createAccessibilityNodeInfo();
-        assertFalse(accessibilityNodeInfo.getActionList().contains(
-                new AccessibilityAction(R.id.accessibility_action_decrease_window_height, null)));
+        assertThat(accessibilityNodeInfo.getActionList()).doesNotContain(
+                new AccessibilityAction(
+                        R.id.accessibility_action_decrease_window_height, null));
     }
 
     @Test
@@ -1057,8 +1126,8 @@
                     Float.NaN);
         });
 
-        assertEquals(getContext().getResources().getString(
-                com.android.internal.R.string.android_system_label), getAccessibilityWindowTitle());
+        assertThat(getAccessibilityWindowTitle()).isEqualTo(getContext().getResources().getString(
+                com.android.internal.R.string.android_system_label));
     }
 
     @Test
@@ -1073,14 +1142,14 @@
                     Float.NaN);
         });
 
-        assertEquals(Float.NaN, mWindowMagnificationController.getScale(), 0);
+        assertThat(mWindowMagnificationController.getScale()).isEqualTo(Float.NaN);
     }
 
     @Test
     public void enableWindowMagnification_rotationIsChanged_updateRotationValue() {
         // the config orientation should not be undefined, since it would cause config.diff
         // returning 0 and thus the orientation changed would not be detected
-        assertNotEquals(ORIENTATION_UNDEFINED, mResources.getConfiguration().orientation);
+        assertThat(mResources.getConfiguration().orientation).isNotEqualTo(ORIENTATION_UNDEFINED);
 
         final Configuration config = mResources.getConfiguration();
         config.orientation = config.orientation == ORIENTATION_LANDSCAPE ? ORIENTATION_PORTRAIT
@@ -1091,7 +1160,7 @@
                 () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
                         Float.NaN, Float.NaN));
 
-        assertEquals(newRotation, mWindowMagnificationController.mRotation);
+        assertThat(mWindowMagnificationController.mRotation).isEqualTo(newRotation);
     }
 
     @Test
@@ -1119,7 +1188,7 @@
             mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_LOCALE);
         });
 
-        assertTrue(TextUtils.equals(newA11yWindowTitle, getAccessibilityWindowTitle()));
+        assertThat(getAccessibilityWindowTitle()).isEqualTo(newA11yWindowTitle);
     }
 
     @Ignore("it's flaky in presubmit but works in abtd, filter for now. b/305654925")
@@ -1134,7 +1203,7 @@
             mWindowMagnificationController.onSingleTap(mSpyView);
         });
 
-        final View mirrorView = mWindowManager.getAttachedView();
+        final View mirrorView = mSurfaceControlViewHost.getView();
 
         final AtomicDouble maxScaleX = new AtomicDouble();
         advanceTimeBy(mWaitBounceEffectDuration, /* runnableOnEachRefresh= */ () -> {
@@ -1142,10 +1211,10 @@
             // maxScaleX.getAndAccumulate(mirrorView.getScaleX(), Math::max);
             final double oldMax = maxScaleX.get();
             final double newMax = Math.max(mirrorView.getScaleX(), oldMax);
-            assertTrue(maxScaleX.compareAndSet(oldMax, newMax));
+            assertThat(maxScaleX.compareAndSet(oldMax, newMax)).isTrue();
         });
 
-        assertTrue(maxScaleX.get() > 1.0);
+        assertThat(maxScaleX.get()).isGreaterThan(1.0);
     }
 
     @Test
@@ -1174,30 +1243,23 @@
                     mWindowMagnificationController.updateWindowMagnificationInternal(
                             Float.NaN, Float.NaN, Float.NaN);
                 });
+        // Wait for Region updated.
+        waitForIdleSync();
 
         mInstrumentation.runOnMainSync(
                 () -> {
                     mWindowMagnificationController.moveWindowMagnifier(bounds.width(), 0);
                 });
-
         // Wait for Region updated.
         waitForIdleSync();
 
-        final ArgumentCaptor<Region> tapExcludeRegionCapturer =
-                ArgumentCaptor.forClass(Region.class);
-        verify(mWindowSessionSpy, times(2))
-                .updateTapExcludeRegion(any(), tapExcludeRegionCapturer.capture());
-        Region tapExcludeRegion = tapExcludeRegionCapturer.getValue();
-        RegionIterator iterator = new RegionIterator(tapExcludeRegion);
+        AttachedSurfaceControl viewRoot = mSurfaceControlViewHost.getRootSurfaceControl();
+        // Verifying two times in: (1) enable window magnification (2) reposition drag handle
+        verify(viewRoot, times(2)).setTouchableRegion(any());
 
-        final Rect topRect = new Rect();
-        final Rect bottomRect = new Rect();
-        assertTrue(iterator.next(topRect));
-        assertTrue(iterator.next(bottomRect));
-        assertFalse(iterator.next(new Rect()));
-
-        assertEquals(topRect.right, bottomRect.right);
-        assertNotEquals(topRect.left, bottomRect.left);
+        View dragButton = getInternalView(R.id.drag_handle);
+        FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) dragButton.getLayoutParams();
+        assertThat(params.gravity).isEqualTo(Gravity.BOTTOM | Gravity.LEFT);
     }
 
     @Test
@@ -1210,29 +1272,23 @@
                     mWindowMagnificationController.updateWindowMagnificationInternal(
                             Float.NaN, Float.NaN, Float.NaN);
                 });
+        // Wait for Region updated.
+        waitForIdleSync();
 
         mInstrumentation.runOnMainSync(
                 () -> {
                     mWindowMagnificationController.moveWindowMagnifier(-bounds.width(), 0);
                 });
-
         // Wait for Region updated.
         waitForIdleSync();
 
-        final ArgumentCaptor<Region> tapExcludeRegionCapturer =
-                ArgumentCaptor.forClass(Region.class);
-        verify(mWindowSessionSpy).updateTapExcludeRegion(any(), tapExcludeRegionCapturer.capture());
-        Region tapExcludeRegion = tapExcludeRegionCapturer.getValue();
-        RegionIterator iterator = new RegionIterator(tapExcludeRegion);
+        AttachedSurfaceControl viewRoot = mSurfaceControlViewHost.getRootSurfaceControl();
+        // Verifying one times in: (1) enable window magnification
+        verify(viewRoot).setTouchableRegion(any());
 
-        final Rect topRect = new Rect();
-        final Rect bottomRect = new Rect();
-        assertTrue(iterator.next(topRect));
-        assertTrue(iterator.next(bottomRect));
-        assertFalse(iterator.next(new Rect()));
-
-        assertEquals(topRect.left, bottomRect.left);
-        assertNotEquals(topRect.right, bottomRect.right);
+        View dragButton = getInternalView(R.id.drag_handle);
+        FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) dragButton.getLayoutParams();
+        assertThat(params.gravity).isEqualTo(Gravity.BOTTOM | Gravity.RIGHT);
     }
 
     @Test
@@ -1249,13 +1305,13 @@
         final AtomicInteger actualWindowWidth = new AtomicInteger();
         mInstrumentation.runOnMainSync(() -> {
             mWindowMagnificationController.setWindowSize(expectedWindowWidth, expectedWindowHeight);
-            actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
-            actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
+            actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height);
+            actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width);
 
         });
 
-        assertEquals(expectedWindowHeight, actualWindowHeight.get());
-        assertEquals(expectedWindowWidth, actualWindowWidth.get());
+        assertThat(actualWindowHeight.get()).isEqualTo(expectedWindowHeight);
+        assertThat(actualWindowWidth.get()).isEqualTo(expectedWindowWidth);
     }
 
     @Test
@@ -1271,12 +1327,12 @@
             mWindowMagnificationController.setWindowSize(expectedWindowWidth, expectedWindowHeight);
             mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
                     Float.NaN, Float.NaN);
-            actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
-            actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
+            actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height);
+            actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width);
         });
 
-        assertEquals(expectedWindowHeight, actualWindowHeight.get());
-        assertEquals(expectedWindowWidth, actualWindowWidth.get());
+        assertThat(actualWindowHeight.get()).isEqualTo(expectedWindowHeight);
+        assertThat(actualWindowWidth.get()).isEqualTo(expectedWindowWidth);
     }
 
     @Test
@@ -1292,12 +1348,12 @@
         mInstrumentation.runOnMainSync(() -> {
             mWindowMagnificationController.setWindowSize(minimumWindowSize - 10,
                     minimumWindowSize - 10);
-            actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
-            actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
+            actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height);
+            actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width);
         });
 
-        assertEquals(minimumWindowSize, actualWindowHeight.get());
-        assertEquals(minimumWindowSize, actualWindowWidth.get());
+        assertThat(actualWindowHeight.get()).isEqualTo(minimumWindowSize);
+        assertThat(actualWindowWidth.get()).isEqualTo(minimumWindowSize);
     }
 
     @Test
@@ -1311,12 +1367,12 @@
         final AtomicInteger actualWindowWidth = new AtomicInteger();
         mInstrumentation.runOnMainSync(() -> {
             mWindowMagnificationController.setWindowSize(bounds.width() + 10, bounds.height() + 10);
-            actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
-            actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
+            actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height);
+            actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width);
         });
 
-        assertEquals(bounds.height(), actualWindowHeight.get());
-        assertEquals(bounds.width(), actualWindowWidth.get());
+        assertThat(actualWindowHeight.get()).isEqualTo(bounds.height());
+        assertThat(actualWindowWidth.get()).isEqualTo(bounds.width());
     }
 
     @Test
@@ -1342,12 +1398,14 @@
                 () -> {
                     mWindowMagnificationController.changeMagnificationSize(
                             WindowMagnificationSettings.MagnificationSize.LARGE);
-                    actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
-                    actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
+                    actualWindowHeight.set(
+                            mSurfaceControlViewHost.getView().getLayoutParams().height);
+                    actualWindowWidth.set(
+                            mSurfaceControlViewHost.getView().getLayoutParams().width);
                 });
 
-        assertEquals(expectedWindowHeight, actualWindowHeight.get());
-        assertEquals(expectedWindowWidth, actualWindowWidth.get());
+        assertThat(actualWindowHeight.get()).isEqualTo(expectedWindowHeight);
+        assertThat(actualWindowWidth.get()).isEqualTo(expectedWindowWidth);
     }
 
     @Test
@@ -1376,12 +1434,14 @@
                 () -> {
                     mWindowMagnificationController
                             .onDrag(getInternalView(R.id.bottom_right_corner), 2f, 1f);
-                    actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
-                    actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
+                    actualWindowHeight.set(
+                            mSurfaceControlViewHost.getView().getLayoutParams().height);
+                    actualWindowWidth.set(
+                            mSurfaceControlViewHost.getView().getLayoutParams().width);
                 });
 
-        assertEquals(startingSize + 1, actualWindowHeight.get());
-        assertEquals(startingSize + 2, actualWindowWidth.get());
+        assertThat(actualWindowHeight.get()).isEqualTo(startingSize + 1);
+        assertThat(actualWindowWidth.get()).isEqualTo(startingSize + 2);
     }
 
     @Test
@@ -1404,11 +1464,13 @@
                     mWindowMagnificationController.setEditMagnifierSizeMode(true);
                     mWindowMagnificationController
                             .onDrag(getInternalView(R.id.bottom_handle), 2f, 1f);
-                    actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
-                    actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
+                    actualWindowHeight.set(
+                            mSurfaceControlViewHost.getView().getLayoutParams().height);
+                    actualWindowWidth.set(
+                            mSurfaceControlViewHost.getView().getLayoutParams().width);
                 });
-        assertEquals(startingSize + 1, actualWindowHeight.get());
-        assertEquals(startingSize, actualWindowWidth.get());
+        assertThat(actualWindowHeight.get()).isEqualTo(startingSize + 1);
+        assertThat(actualWindowWidth.get()).isEqualTo(startingSize);
     }
 
     @Test
@@ -1430,8 +1492,8 @@
             magnificationCenterY.set((int) mWindowMagnificationController.getCenterY());
         });
 
-        assertTrue(magnificationCenterX.get() < bounds.right);
-        assertTrue(magnificationCenterY.get() < bounds.bottom);
+        assertThat(magnificationCenterX.get()).isLessThan(bounds.right);
+        assertThat(magnificationCenterY.get()).isLessThan(bounds.bottom);
     }
 
     @Test
@@ -1451,13 +1513,13 @@
         dragButton.dispatchTouchEvent(
                 obtainMotionEvent(downTime, downTime, ACTION_UP, 100, 100));
 
-        verify(mWindowManager).addView(any(View.class), any());
+        verify(mSurfaceControlViewHost).setView(any(View.class), any());
     }
 
     private <T extends View> T getInternalView(@IdRes int idRes) {
-        View mirrorView = mWindowManager.getAttachedView();
+        View mirrorView = mSurfaceControlViewHost.getView();
         T view = mirrorView.findViewById(idRes);
-        assertNotNull(view);
+        assertThat(view).isNotNull();
         return view;
     }
 
@@ -1466,14 +1528,14 @@
         return mMotionEventHelper.obtainMotionEvent(downTime, eventTime, action, x, y);
     }
 
-    private CharSequence getAccessibilityWindowTitle() {
-        final View mirrorView = mWindowManager.getAttachedView();
+    private String getAccessibilityWindowTitle() {
+        final View mirrorView = mSurfaceControlViewHost.getView();
         if (mirrorView == null) {
             return null;
         }
         WindowManager.LayoutParams layoutParams =
                 (WindowManager.LayoutParams) mirrorView.getLayoutParams();
-        return layoutParams.accessibilityTitle;
+        return layoutParams.accessibilityTitle.toString();
     }
 
     private boolean hasMagnificationOverlapFlag() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt
index 176c3ac..2594472 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt
@@ -22,12 +22,13 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.testKosmos
 import com.android.systemui.util.settings.fakeSettings
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -40,8 +41,6 @@
 @RunWith(AndroidJUnit4::class)
 class AccessibilityQsShortcutsRepositoryImplTest : SysuiTestCase() {
     private val kosmos = testKosmos()
-    private val testDispatcher = kosmos.testDispatcher
-    private val testScope = kosmos.testScope
     private val secureSettings = kosmos.fakeSettings
 
     @Rule @JvmField val mockitoRule: MockitoRule = MockitoJUnit.rule()
@@ -55,8 +54,8 @@
                 return UserA11yQsShortcutsRepository(
                     userId,
                     secureSettings,
-                    testScope.backgroundScope,
-                    testDispatcher,
+                    kosmos.testScope.backgroundScope,
+                    kosmos.testDispatcher,
                 )
             }
         }
@@ -69,13 +68,13 @@
             AccessibilityQsShortcutsRepositoryImpl(
                 a11yManager,
                 userA11yQsShortcutsRepositoryFactory,
-                testDispatcher
+                kosmos.testDispatcher,
             )
     }
 
     @Test
     fun a11yQsShortcutTargetsForCorrectUsers() =
-        testScope.runTest {
+        kosmos.runTest {
             val user0 = 0
             val targetsForUser0 = setOf("a", "b", "c")
             val user1 = 1
@@ -94,7 +93,7 @@
         secureSettings.putStringForUser(
             SETTING_NAME,
             a11yQsTargets.joinToString(separator = ":"),
-            forUser
+            forUser,
         )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
index 8c7cd61..cdda9cc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
@@ -47,8 +47,7 @@
 import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
 import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
-import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.util.concurrency.FakeExecutor
@@ -61,7 +60,6 @@
 import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertTrue
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.runCurrent
 import org.junit.Before
 import org.junit.Rule
@@ -95,9 +93,6 @@
     @Mock private lateinit var onBackInvokedDispatcher: WindowOnBackInvokedDispatcher
     @Mock private lateinit var iStatusBarService: IStatusBarService
     @Mock private lateinit var headsUpManager: HeadsUpManager
-    private val activeNotificationsRepository = ActiveNotificationListRepository()
-    private val activeNotificationsInteractor =
-        ActiveNotificationsInteractor(activeNotificationsRepository, StandardTestDispatcher())
 
     private val keyguardRepository = FakeKeyguardRepository()
     private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor by lazy {
@@ -107,7 +102,7 @@
             keyguardRepository,
             headsUpManager,
             powerInteractor,
-            activeNotificationsInteractor,
+            kosmos.activeNotificationsInteractor,
             kosmos::sceneInteractor,
         )
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt
index 72e0726..5994afa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt
@@ -55,7 +55,7 @@
         testableResources.overrideConfiguration(configuration)
         configurationRepository = FakeConfigurationRepository()
         testScope = TestScope()
-        underTest = ConfigurationInteractor(configurationRepository)
+        underTest = ConfigurationInteractorImpl(configurationRepository)
     }
 
     @Test
@@ -207,7 +207,7 @@
             updateDisplay(
                 width = DISPLAY_HEIGHT,
                 height = DISPLAY_WIDTH,
-                rotation = Surface.ROTATION_90
+                rotation = Surface.ROTATION_90,
             )
             runCurrent()
 
@@ -217,7 +217,7 @@
     private fun updateDisplay(
         width: Int = DISPLAY_WIDTH,
         height: Int = DISPLAY_HEIGHT,
-        @Surface.Rotation rotation: Int = Surface.ROTATION_0
+        @Surface.Rotation rotation: Int = Surface.ROTATION_0,
     ) {
         configuration.windowConfiguration.maxBounds.set(Rect(0, 0, width, height))
         configuration.windowConfiguration.displayRotation = rotation
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerServiceTest.kt
index 44ce085..c3c958c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerServiceTest.kt
@@ -20,6 +20,8 @@
 import android.appwidget.AppWidgetProviderInfo
 import android.content.ComponentName
 import android.content.Intent
+import android.content.IntentSender
+import android.os.Binder
 import android.os.UserHandle
 import android.testing.TestableLooper
 import android.widget.RemoteViews
@@ -29,6 +31,7 @@
 import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
 import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
 import com.android.systemui.communal.shared.model.fakeGlanceableHubMultiUserHelper
+import com.android.systemui.communal.widgets.IGlanceableHubWidgetManagerService.IConfigureWidgetCallback
 import com.android.systemui.communal.widgets.IGlanceableHubWidgetManagerService.IGlanceableHubWidgetsListener
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testScope
@@ -43,11 +46,13 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.kotlin.argumentCaptor
 import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.never
 import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -164,7 +169,7 @@
         }
 
     @Test
-    fun addWidget_getWidgetUpdate() =
+    fun addWidget_noConfigurationCallback_getWidgetUpdate() =
         testScope.runTest {
             setupWidgets()
 
@@ -180,7 +185,7 @@
             assertThat(widgets?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue()
 
             // Add a widget
-            service.addWidget(ComponentName("pkg_4", "cls_4"), UserHandle.of(0), 3)
+            service.addWidget(ComponentName("pkg_4", "cls_4"), UserHandle.of(0), 3, null)
             runCurrent()
 
             // Verify an update pushed with widget 4 added
@@ -192,6 +197,71 @@
         }
 
     @Test
+    fun addWidget_withConfigurationCallback_configurationFails_doNotAddWidget() =
+        testScope.runTest {
+            setupWidgets()
+
+            // Bind service
+            val binder = underTest.onBind(Intent())
+            val service = IGlanceableHubWidgetManagerService.Stub.asInterface(binder)
+
+            // Verify the update is as expected
+            val widgets by collectLastValue(service.listenForWidgetUpdates())
+            assertThat(widgets).hasSize(3)
+            assertThat(widgets?.get(0)?.has(1, "pkg_1/cls_1", 0, 3)).isTrue()
+            assertThat(widgets?.get(1)?.has(2, "pkg_2/cls_2", 1, 3)).isTrue()
+            assertThat(widgets?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue()
+
+            // Add a widget with a configuration callback that fails
+            service.addWidget(
+                ComponentName("pkg_4", "cls_4"),
+                UserHandle.of(0),
+                3,
+                createConfigureWidgetCallback(success = false),
+            )
+            runCurrent()
+
+            // Verify that widget 4 is not added
+            assertThat(widgets).hasSize(3)
+            assertThat(widgets?.get(0)?.has(1, "pkg_1/cls_1", 0, 3)).isTrue()
+            assertThat(widgets?.get(1)?.has(2, "pkg_2/cls_2", 1, 3)).isTrue()
+            assertThat(widgets?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue()
+        }
+
+    @Test
+    fun addWidget_withConfigurationCallback_configurationSucceeds_addWidget() =
+        testScope.runTest {
+            setupWidgets()
+
+            // Bind service
+            val binder = underTest.onBind(Intent())
+            val service = IGlanceableHubWidgetManagerService.Stub.asInterface(binder)
+
+            // Verify the update is as expected
+            val widgets by collectLastValue(service.listenForWidgetUpdates())
+            assertThat(widgets).hasSize(3)
+            assertThat(widgets?.get(0)?.has(1, "pkg_1/cls_1", 0, 3)).isTrue()
+            assertThat(widgets?.get(1)?.has(2, "pkg_2/cls_2", 1, 3)).isTrue()
+            assertThat(widgets?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue()
+
+            // Add a widget with a configuration callback that fails
+            service.addWidget(
+                ComponentName("pkg_4", "cls_4"),
+                UserHandle.of(0),
+                3,
+                createConfigureWidgetCallback(success = true),
+            )
+            runCurrent()
+
+            // Verify that widget 4 is added
+            assertThat(widgets).hasSize(4)
+            assertThat(widgets?.get(0)?.has(1, "pkg_1/cls_1", 0, 3)).isTrue()
+            assertThat(widgets?.get(1)?.has(2, "pkg_2/cls_2", 1, 3)).isTrue()
+            assertThat(widgets?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue()
+            assertThat(widgets?.get(3)?.has(4, "pkg_4/cls_4", 3, 3)).isTrue()
+        }
+
+    @Test
     fun deleteWidget_getWidgetUpdate() =
         testScope.runTest {
             setupWidgets()
@@ -271,6 +341,21 @@
             assertThat(widgets?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue()
         }
 
+    @Test
+    fun getIntentSenderForConfigureActivity() =
+        testScope.runTest {
+            val expected = IntentSender(Binder())
+            whenever(appWidgetHost.getIntentSenderForConfigureActivity(anyInt(), anyInt()))
+                .thenReturn(expected)
+
+            // Bind service
+            val binder = underTest.onBind(Intent())
+            val service = IGlanceableHubWidgetManagerService.Stub.asInterface(binder)
+
+            val actual = service.getIntentSenderForConfigureActivity(1)
+            assertThat(actual).isEqualTo(expected)
+        }
+
     private fun setupWidgets() {
         widgetRepository.addWidget(
             appWidgetId = 1,
@@ -293,7 +378,7 @@
     }
 
     private fun IGlanceableHubWidgetManagerService.listenForWidgetUpdates() =
-        conflatedCallbackFlow<List<CommunalWidgetContentModel>> {
+        conflatedCallbackFlow {
             val listener =
                 object : IGlanceableHubWidgetsListener.Stub() {
                     override fun onWidgetsUpdated(widgets: List<CommunalWidgetContentModel>) {
@@ -316,4 +401,15 @@
             this.rank == rank &&
             this.spanY == spanY
     }
+
+    private fun createConfigureWidgetCallback(success: Boolean): IConfigureWidgetCallback {
+        return object : IConfigureWidgetCallback.Stub() {
+            override fun onConfigureWidget(
+                appWidgetId: Int,
+                resultReceiver: IConfigureWidgetCallback.IResultReceiver?,
+            ) {
+                resultReceiver?.onResult(success)
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetConfigurationControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetConfigurationControllerTest.kt
index 5d4eaf0..e1bdf1c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetConfigurationControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetConfigurationControllerTest.kt
@@ -18,17 +18,18 @@
 
 import android.app.Activity
 import android.content.ActivityNotFoundException
+import android.content.IntentSender
+import android.os.Binder
+import android.os.OutcomeReceiver
 import androidx.activity.ComponentActivity
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.shared.model.fakeGlanceableHubMultiUserHelper
+import com.android.systemui.concurrency.fakeExecutor
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.async
@@ -38,15 +39,22 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class WidgetConfigurationControllerTest : SysuiTestCase() {
-    @Mock private lateinit var appWidgetHost: CommunalAppWidgetHost
-    @Mock private lateinit var ownerActivity: ComponentActivity
+    private val appWidgetHost = mock<CommunalAppWidgetHost>()
+    private val ownerActivity = mock<ComponentActivity>()
+
+    private val outcomeReceiverCaptor = argumentCaptor<OutcomeReceiver<IntentSender?, Throwable>>()
 
     private val kosmos = testKosmos()
 
@@ -54,18 +62,19 @@
 
     @Before
     fun setUp() {
-        MockitoAnnotations.initMocks(this)
         underTest =
             WidgetConfigurationController(
                 ownerActivity,
                 { appWidgetHost },
                 kosmos.testDispatcher,
                 kosmos.fakeGlanceableHubMultiUserHelper,
+                { kosmos.mockGlanceableHubWidgetManager },
+                kosmos.fakeExecutor,
             )
     }
 
     @Test
-    fun configurationFailsWhenActivityNotFound() =
+    fun configureWidget_activityNotFound_returnsFalse() =
         with(kosmos) {
             testScope.runTest {
                 whenever(
@@ -84,7 +93,7 @@
         }
 
     @Test
-    fun configurationFails() =
+    fun configureWidget_configurationFails_returnsFalse() =
         with(kosmos) {
             testScope.runTest {
                 val result = async { underTest.configureWidget(123) }
@@ -100,7 +109,7 @@
         }
 
     @Test
-    fun configurationSuccessful() =
+    fun configureWidget_configurationSucceeds_returnsTrue() =
         with(kosmos) {
             testScope.runTest {
                 val result = async { underTest.configureWidget(123) }
@@ -114,4 +123,116 @@
                 result.cancel()
             }
         }
+
+    @Test
+    fun configureWidget_headlessSystemUser_activityNotFound_returnsFalse() =
+        with(kosmos) {
+            testScope.runTest {
+                fakeGlanceableHubMultiUserHelper.setIsInHeadlessSystemUser(true)
+
+                // Activity not found
+                whenever(
+                        mockGlanceableHubWidgetManager.getIntentSenderForConfigureActivity(
+                            anyInt(),
+                            outcomeReceiverCaptor.capture(),
+                            any(),
+                        )
+                    )
+                    .then { outcomeReceiverCaptor.firstValue.onError(ActivityNotFoundException()) }
+
+                val result = async { underTest.configureWidget(123) }
+                runCurrent()
+
+                assertThat(result.await()).isFalse()
+                result.cancel()
+            }
+        }
+
+    @Test
+    fun configureWidget_headlessSystemUser_intentSenderNull_returnsFalse() =
+        with(kosmos) {
+            testScope.runTest {
+                fakeGlanceableHubMultiUserHelper.setIsInHeadlessSystemUser(true)
+
+                prepareIntentSender(null)
+
+                assertThat(underTest.configureWidget(123)).isFalse()
+            }
+        }
+
+    @Test
+    fun configureWidget_headlessSystemUser_configurationFails_returnsFalse() =
+        with(kosmos) {
+            testScope.runTest {
+                fakeGlanceableHubMultiUserHelper.setIsInHeadlessSystemUser(true)
+
+                val intentSender = IntentSender(Binder())
+                prepareIntentSender(intentSender)
+
+                val result = async { underTest.configureWidget(123) }
+                runCurrent()
+                assertThat(result.isCompleted).isFalse()
+
+                verify(ownerActivity)
+                    .startIntentSenderForResult(
+                        eq(intentSender),
+                        eq(WidgetConfigurationController.REQUEST_CODE),
+                        anyOrNull(),
+                        anyInt(),
+                        anyInt(),
+                        anyInt(),
+                        any(),
+                    )
+
+                underTest.setConfigurationResult(Activity.RESULT_CANCELED)
+                runCurrent()
+
+                assertThat(result.await()).isFalse()
+                result.cancel()
+            }
+        }
+
+    @Test
+    fun configureWidget_headlessSystemUser_configurationSucceeds_returnsTrue() =
+        with(kosmos) {
+            testScope.runTest {
+                fakeGlanceableHubMultiUserHelper.setIsInHeadlessSystemUser(true)
+
+                val intentSender = IntentSender(Binder())
+                prepareIntentSender(intentSender)
+
+                val result = async { underTest.configureWidget(123) }
+                runCurrent()
+                assertThat(result.isCompleted).isFalse()
+
+                verify(ownerActivity)
+                    .startIntentSenderForResult(
+                        eq(intentSender),
+                        eq(WidgetConfigurationController.REQUEST_CODE),
+                        anyOrNull(),
+                        anyInt(),
+                        anyInt(),
+                        anyInt(),
+                        any(),
+                    )
+
+                underTest.setConfigurationResult(Activity.RESULT_OK)
+                runCurrent()
+
+                assertThat(result.await()).isTrue()
+                result.cancel()
+            }
+        }
+
+    private fun prepareIntentSender(intentSender: IntentSender?) =
+        with(kosmos) {
+            whenever(
+                    mockGlanceableHubWidgetManager.getIntentSenderForConfigureActivity(
+                        anyInt(),
+                        outcomeReceiverCaptor.capture(),
+                        any(),
+                    )
+                )
+                .then { outcomeReceiverCaptor.firstValue.onResult(intentSender) }
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteServiceBinderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteServiceBinderTest.kt
index f8a45e8..b343def 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteServiceBinderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteServiceBinderTest.kt
@@ -31,7 +31,8 @@
 import com.android.systemui.controls.panels.selectedComponentRepository
 import com.android.systemui.controls.settings.FakeControlsSettingsRepository
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.dreams.homecontrols.shared.IOnControlsSettingsChangeListener
+import com.android.systemui.dreams.homecontrols.shared.controlsSettings
+import com.android.systemui.dreams.homecontrols.shared.model.HomeControlsComponentInfo
 import com.android.systemui.dreams.homecontrols.system.domain.interactor.controlsComponent
 import com.android.systemui.dreams.homecontrols.system.domain.interactor.controlsListingController
 import com.android.systemui.dreams.homecontrols.system.domain.interactor.homeControlsComponentInteractor
@@ -42,13 +43,10 @@
 import com.android.systemui.settings.fakeUserTracker
 import com.android.systemui.testKosmos
 import com.android.systemui.user.data.repository.fakeUserRepository
-import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.mockito.withArgCaptor
-import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
 import com.google.common.truth.Truth.assertThat
 import java.util.Optional
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
@@ -56,6 +54,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito
+import org.mockito.kotlin.whenever
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -90,13 +89,13 @@
     fun testRegisterSingleListener() =
         testScope.runTest {
             setup()
-            val controlsSettings by collectLastValue(addCallback())
+            val controlsSettings by collectLastValue(underTest.controlsSettings)
             runServicesUpdate()
 
             assertThat(controlsSettings)
                 .isEqualTo(
-                    CallbackArgs(
-                        panelComponent = TEST_COMPONENT,
+                    HomeControlsComponentInfo(
+                        componentName = TEST_COMPONENT,
                         allowTrivialControlsOnLockscreen = false,
                     )
                 )
@@ -106,21 +105,21 @@
     fun testRegisterMultipleListeners() =
         testScope.runTest {
             setup()
-            val controlsSettings1 by collectLastValue(addCallback())
-            val controlsSettings2 by collectLastValue(addCallback())
+            val controlsSettings1 by collectLastValue(underTest.controlsSettings)
+            val controlsSettings2 by collectLastValue(underTest.controlsSettings)
             runServicesUpdate()
 
             assertThat(controlsSettings1)
                 .isEqualTo(
-                    CallbackArgs(
-                        panelComponent = TEST_COMPONENT,
+                    HomeControlsComponentInfo(
+                        componentName = TEST_COMPONENT,
                         allowTrivialControlsOnLockscreen = false,
                     )
                 )
             assertThat(controlsSettings2)
                 .isEqualTo(
-                    CallbackArgs(
-                        panelComponent = TEST_COMPONENT,
+                    HomeControlsComponentInfo(
+                        componentName = TEST_COMPONENT,
                         allowTrivialControlsOnLockscreen = false,
                     )
                 )
@@ -130,13 +129,13 @@
     fun testListenerCalledWhenStateChanges() =
         testScope.runTest {
             setup()
-            val controlsSettings by collectLastValue(addCallback())
+            val controlsSettings by collectLastValue(underTest.controlsSettings)
             runServicesUpdate()
 
             assertThat(controlsSettings)
                 .isEqualTo(
-                    CallbackArgs(
-                        panelComponent = TEST_COMPONENT,
+                    HomeControlsComponentInfo(
+                        componentName = TEST_COMPONENT,
                         allowTrivialControlsOnLockscreen = false,
                     )
                 )
@@ -146,13 +145,47 @@
             // Updated with null component now that we are no longer authorized.
             assertThat(controlsSettings)
                 .isEqualTo(
-                    CallbackArgs(panelComponent = null, allowTrivialControlsOnLockscreen = false)
+                    HomeControlsComponentInfo(
+                        componentName = null,
+                        allowTrivialControlsOnLockscreen = false,
+                    )
                 )
         }
 
+    @Test
+    fun testDestroy() =
+        testScope.runTest {
+            setup()
+            val controlsSettings1 by collectLastValue(underTest.controlsSettings)
+
+            assertThat(controlsSettings1)
+                .isEqualTo(
+                    HomeControlsComponentInfo(
+                        componentName = null,
+                        allowTrivialControlsOnLockscreen = false,
+                    )
+                )
+
+            underTest.onDestroy()
+            runServicesUpdate()
+            fakeControlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(true)
+
+            // Existing callback is not triggered if destroyed.
+            assertThat(controlsSettings1)
+                .isEqualTo(
+                    HomeControlsComponentInfo(
+                        componentName = null,
+                        allowTrivialControlsOnLockscreen = false,
+                    )
+                )
+            // New callbacks cannot be added.
+            val controlsSettings2 by collectLastValue(underTest.controlsSettings)
+            assertThat(controlsSettings2).isNull()
+        }
+
     private fun TestScope.runServicesUpdate() {
         runCurrent()
-        val listings = listOf(ControlsServiceInfo(TEST_COMPONENT, "panel", hasPanel = true))
+        val listings = listOf(buildControlsServiceInfo(TEST_COMPONENT, "panel", hasPanel = true))
         val callback = withArgCaptor {
             Mockito.verify(kosmos.controlsListingController).addCallback(capture())
         }
@@ -160,20 +193,6 @@
         runCurrent()
     }
 
-    private fun addCallback() = conflatedCallbackFlow {
-        val callback =
-            object : IOnControlsSettingsChangeListener.Stub() {
-                override fun onControlsSettingsChanged(
-                    panelComponent: ComponentName?,
-                    allowTrivialControlsOnLockscreen: Boolean,
-                ) {
-                    trySend(CallbackArgs(panelComponent, allowTrivialControlsOnLockscreen))
-                }
-            }
-        underTest.registerListenerForCurrentUser(callback)
-        awaitClose { underTest.unregisterListenerForCurrentUser(callback) }
-    }
-
     private suspend fun TestScope.setup() {
         kosmos.fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
         kosmos.fakeUserTracker.set(listOf(PRIMARY_USER), 0)
@@ -182,12 +201,7 @@
         runCurrent()
     }
 
-    private data class CallbackArgs(
-        val panelComponent: ComponentName?,
-        val allowTrivialControlsOnLockscreen: Boolean,
-    )
-
-    private fun ControlsServiceInfo(
+    private fun buildControlsServiceInfo(
         componentName: ComponentName,
         label: CharSequence,
         hasPanel: Boolean,
@@ -225,7 +239,7 @@
             UserInfo(
                 /* id= */ PRIMARY_USER_ID,
                 /* name= */ "primary user",
-                /* flags= */ UserInfo.FLAG_PRIMARY,
+                /* flags= */ UserInfo.FLAG_MAIN,
             )
 
         private const val TEST_PACKAGE = "pkg"
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dump/DumpManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dump/DumpManagerTest.kt
index f331060..5827c7b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dump/DumpManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dump/DumpManagerTest.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.table.TableLogBuffer
 import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
 import org.junit.Assert.assertThrows
 import org.junit.Before
 import org.junit.Test
@@ -134,6 +135,21 @@
     }
 
     @Test
+    fun registerDumpable_supportsAnonymousDumpables() {
+        val anonDumpable =
+            object : Dumpable {
+                override fun dump(pw: PrintWriter, args: Array<out String>) {
+                    pw.println("AnonDumpable")
+                }
+            }
+
+        // THEN registration with implicit names should succeed
+        dumpManager.registerCriticalDumpable(anonDumpable)
+
+        // No exception thrown
+    }
+
+    @Test
     fun getDumpables_returnsSafeCollection() {
         // GIVEN a variety of registered dumpables
         dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
index 3388a78..20cd860 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
@@ -28,11 +28,13 @@
 import com.android.systemui.Flags.FLAG_SYSUI_TEAMFOOD
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.nullable
 import com.android.systemui.util.mockito.withArgCaptor
 import com.android.systemui.util.settings.GlobalSettings
+import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import java.io.PrintWriter
 import java.io.Serializable
@@ -68,6 +70,7 @@
     @Mock private lateinit var systemProperties: SystemPropertiesHelper
     @Mock private lateinit var resources: Resources
     @Mock private lateinit var restarter: Restarter
+    private lateinit var fakeExecutor: FakeExecutor
     private lateinit var userTracker: FakeUserTracker
     private val flagMap = mutableMapOf<String, Flag<*>>()
     private lateinit var broadcastReceiver: BroadcastReceiver
@@ -83,6 +86,7 @@
         flagMap.put(teamfoodableFlagA.name, teamfoodableFlagA)
         flagMap.put(releasedFlagB.name, releasedFlagB)
 
+        fakeExecutor = FakeExecutor(FakeSystemClock())
         userTracker = FakeUserTracker(onCreateCurrentUserContext = { mockContext })
 
         mFeatureFlagsClassicDebug =
@@ -95,7 +99,8 @@
                 serverFlagReader,
                 flagMap,
                 restarter,
-                userTracker
+                userTracker,
+                fakeExecutor,
             )
         mFeatureFlagsClassicDebug.init()
         verify(flagManager).onSettingsChangedAction = any()
@@ -325,14 +330,14 @@
         // trying to erase an id not in the map does nothing
         broadcastReceiver.onReceive(
             mockContext,
-            Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_NAME, "")
+            Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_NAME, ""),
         )
         verifyNoMoreInteractions(flagManager, globalSettings)
 
         // valid id with no value puts empty string in the setting
         broadcastReceiver.onReceive(
             mockContext,
-            Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_NAME, "1")
+            Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_NAME, "1"),
         )
         verifyPutData("1", "", numReads = 0)
     }
@@ -415,7 +420,7 @@
         serverFlagReader.setFlagValue(
             teamfoodableFlagA.namespace,
             teamfoodableFlagA.name,
-            !teamfoodableFlagA.default
+            !teamfoodableFlagA.default,
         )
         verify(restarter, never()).restartSystemUI(anyString())
     }
@@ -428,7 +433,7 @@
         serverFlagReader.setFlagValue(
             teamfoodableFlagA.namespace,
             teamfoodableFlagA.name,
-            !teamfoodableFlagA.default
+            !teamfoodableFlagA.default,
         )
         verify(restarter).restartSystemUI(anyString())
     }
@@ -441,7 +446,7 @@
         serverFlagReader.setFlagValue(
             teamfoodableFlagA.namespace,
             teamfoodableFlagA.name,
-            teamfoodableFlagA.default
+            teamfoodableFlagA.default,
         )
         verify(restarter, never()).restartSystemUI(anyString())
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModelTest.kt
index 2735d2f..a0bef72 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModelTest.kt
@@ -24,7 +24,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
-import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl
 import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
 import com.android.systemui.keyboard.docking.domain.interactor.KeyboardDockingIndicationInteractor
 import com.google.common.truth.Truth.assertThat
@@ -59,7 +59,7 @@
 
         val keyboardDockingIndicationInteractor =
             KeyboardDockingIndicationInteractor(keyboardRepository)
-        val configurationInteractor = ConfigurationInteractor(configurationRepository)
+        val configurationInteractor = ConfigurationInteractorImpl(configurationRepository)
 
         underTest =
             KeyboardDockingIndicationViewModel(
@@ -67,7 +67,7 @@
                 context,
                 keyboardDockingIndicationInteractor,
                 configurationInteractor,
-                testScope.backgroundScope
+                testScope.backgroundScope,
             )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
index 26ce67d..7ec53df 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
@@ -40,8 +40,8 @@
 import com.android.systemui.user.data.repository.fakeUserRepository
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.settings.data.repository.userAwareSecureSettingsRepository
 import com.android.systemui.util.settings.fakeSettings
-import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepositoryImpl
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestScope
@@ -72,14 +72,13 @@
 
     @Before
     fun setup() {
-        val settingsRepository =
-            UserAwareSecureSettingsRepositoryImpl(secureSettings, userRepository, dispatcher)
+        val settingsRepository = kosmos.userAwareSecureSettingsRepository
         val stickyKeysRepository =
             StickyKeysRepositoryImpl(
                 inputManager,
                 dispatcher,
                 settingsRepository,
-                mock<StickyKeysLogger>()
+                mock<StickyKeysLogger>(),
             )
         setStickyKeySetting(enabled = false)
         viewModel =
@@ -114,7 +113,7 @@
             verify(inputManager)
                 .registerStickyModifierStateListener(
                     any(),
-                    any(InputManager.StickyModifierStateListener::class.java)
+                    any(InputManager.StickyModifierStateListener::class.java),
                 )
         }
     }
@@ -187,11 +186,7 @@
 
             assertThat(stickyKeys)
                 .isEqualTo(
-                    mapOf(
-                        ALT to Locked(false),
-                        META to Locked(false),
-                        SHIFT to Locked(false),
-                    )
+                    mapOf(ALT to Locked(false), META to Locked(false), SHIFT to Locked(false))
                 )
         }
     }
@@ -218,7 +213,7 @@
                 mapOf(
                     META to false,
                     SHIFT to false, // shift is sticky but not locked
-                    CTRL to false
+                    CTRL to false,
                 )
             )
             val previousShiftIndex = stickyKeys?.toList()?.indexOf(SHIFT to Locked(false))
@@ -228,7 +223,7 @@
                     SHIFT to false,
                     SHIFT to true, // shift is now locked
                     META to false,
-                    CTRL to false
+                    CTRL to false,
                 )
             )
             assertThat(stickyKeys?.toList()?.indexOf(SHIFT to Locked(true)))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
index 6e16705..2c12f87 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
@@ -34,6 +34,8 @@
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.data.repository.Idle
+import com.android.systemui.scene.data.repository.Transition
 import com.android.systemui.scene.data.repository.sceneContainerRepository
 import com.android.systemui.scene.data.repository.setSceneTransition
 import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -115,9 +117,9 @@
 
             assertEquals(
                 listOf(
-                    false, // We should start with the surface invisible on LOCKSCREEN.
+                    false // We should start with the surface invisible on LOCKSCREEN.
                 ),
-                values
+                values,
             )
 
             val lockscreenSpecificSurfaceVisibility = true
@@ -134,13 +136,7 @@
 
             // We started a transition from LOCKSCREEN, we should be using the value emitted by the
             // lockscreenSurfaceVisibilityFlow.
-            assertEquals(
-                listOf(
-                    false,
-                    lockscreenSpecificSurfaceVisibility,
-                ),
-                values
-            )
+            assertEquals(listOf(false, lockscreenSpecificSurfaceVisibility), values)
 
             // Go back to LOCKSCREEN, since we won't emit 'true' twice in a row.
             transitionRepository.sendTransitionStep(
@@ -166,7 +162,7 @@
                     lockscreenSpecificSurfaceVisibility,
                     false, // FINISHED (LOCKSCREEN)
                 ),
-                values
+                values,
             )
 
             val bouncerSpecificVisibility = true
@@ -191,13 +187,13 @@
                     false,
                     bouncerSpecificVisibility,
                 ),
-                values
+                values,
             )
         }
 
     @Test
     @EnableSceneContainer
-    fun surfaceBehindVisibility_fromLockscreenToGone_noUserInput_trueThroughout() =
+    fun surfaceBehindVisibility_fromLockscreenToGone_dependsOnDeviceEntry() =
         testScope.runTest {
             val isSurfaceBehindVisible by collectLastValue(underTest.value.surfaceBehindVisibility)
             val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene)
@@ -212,7 +208,7 @@
                 SuccessFingerprintAuthenticationStatus(0, true)
             )
 
-            // Start the transition to Gone, the surface should become immediately visible.
+            // Start the transition to Gone, the surface should remain invisible.
             kosmos.setSceneTransition(
                 ObservableTransitionState.Transition(
                     fromScene = Scenes.Lockscreen,
@@ -224,9 +220,9 @@
                 )
             )
             assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
-            assertThat(isSurfaceBehindVisible).isTrue()
+            assertThat(isSurfaceBehindVisible).isFalse()
 
-            // Towards the end of the transition, the surface should continue to be visible.
+            // Towards the end of the transition, the surface should continue to remain invisible.
             kosmos.setSceneTransition(
                 ObservableTransitionState.Transition(
                     fromScene = Scenes.Lockscreen,
@@ -238,7 +234,7 @@
                 )
             )
             assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
-            assertThat(isSurfaceBehindVisible).isTrue()
+            assertThat(isSurfaceBehindVisible).isFalse()
 
             // After the transition, settles on Gone. Surface behind should stay visible now.
             kosmos.setSceneTransition(ObservableTransitionState.Idle(Scenes.Gone))
@@ -249,43 +245,6 @@
 
     @Test
     @EnableSceneContainer
-    fun surfaceBehindVisibility_fromLockscreenToGone_withUserInput_falseUntilInputStops() =
-        testScope.runTest {
-            val isSurfaceBehindVisible by collectLastValue(underTest.value.surfaceBehindVisibility)
-            val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene)
-
-            // Before the transition, we start on Lockscreen so the surface should start invisible.
-            kosmos.setSceneTransition(ObservableTransitionState.Idle(Scenes.Lockscreen))
-            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
-            assertThat(isSurfaceBehindVisible).isFalse()
-
-            // Unlocked with fingerprint.
-            kosmos.deviceEntryFingerprintAuthRepository.setAuthenticationStatus(
-                SuccessFingerprintAuthenticationStatus(0, true)
-            )
-
-            // Start the transition to Gone, the surface should not be visible while
-            // isUserInputOngoing is true
-            val isUserInputOngoing = MutableStateFlow(true)
-            kosmos.setSceneTransition(
-                ObservableTransitionState.Transition(
-                    fromScene = Scenes.Lockscreen,
-                    toScene = Scenes.Gone,
-                    isInitiatedByUserInput = true,
-                    isUserInputOngoing = isUserInputOngoing,
-                    progress = flowOf(0.51f),
-                    currentScene = flowOf(Scenes.Gone),
-                )
-            )
-            assertThat(isSurfaceBehindVisible).isFalse()
-
-            // When isUserInputOngoing becomes false, then the surface should become visible.
-            isUserInputOngoing.value = false
-            assertThat(isSurfaceBehindVisible).isTrue()
-        }
-
-    @Test
-    @EnableSceneContainer
     fun surfaceBehindVisibility_fromBouncerToGone_becomesTrue() =
         testScope.runTest {
             val isSurfaceBehindVisible by collectLastValue(underTest.value.surfaceBehindVisibility)
@@ -362,20 +321,14 @@
             kosmos.sceneInteractor.changeScene(Scenes.Gone, "")
             assertThat(currentScene).isEqualTo(Scenes.Gone)
 
-            listOf(
-                    Scenes.Shade,
-                    Scenes.QuickSettings,
-                    Scenes.Shade,
-                    Scenes.Gone,
-                )
-                .forEach { scene ->
-                    kosmos.setSceneTransition(ObservableTransitionState.Idle(scene))
-                    kosmos.sceneInteractor.changeScene(scene, "")
-                    assertThat(currentScene).isEqualTo(scene)
-                    assertWithMessage("Unexpected visibility for scene \"${scene.debugName}\"")
-                        .that(isSurfaceBehindVisible)
-                        .isTrue()
-                }
+            listOf(Scenes.Shade, Scenes.QuickSettings, Scenes.Shade, Scenes.Gone).forEach { scene ->
+                kosmos.setSceneTransition(ObservableTransitionState.Idle(scene))
+                kosmos.sceneInteractor.changeScene(scene, "")
+                assertThat(currentScene).isEqualTo(scene)
+                assertWithMessage("Unexpected visibility for scene \"${scene.debugName}\"")
+                    .that(isSurfaceBehindVisible)
+                    .isTrue()
+            }
         }
 
     @Test
@@ -386,19 +339,14 @@
             val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene)
             assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
 
-            listOf(
-                    Scenes.Shade,
-                    Scenes.QuickSettings,
-                    Scenes.Shade,
-                    Scenes.Lockscreen,
-                )
-                .forEach { scene ->
-                    kosmos.setSceneTransition(ObservableTransitionState.Idle(scene))
-                    kosmos.sceneInteractor.changeScene(scene, "")
-                    assertWithMessage("Unexpected visibility for scene \"${scene.debugName}\"")
-                        .that(isSurfaceBehindVisible)
-                        .isFalse()
-                }
+            listOf(Scenes.Shade, Scenes.QuickSettings, Scenes.Shade, Scenes.Lockscreen).forEach {
+                scene ->
+                kosmos.setSceneTransition(ObservableTransitionState.Idle(scene))
+                kosmos.sceneInteractor.changeScene(scene, "")
+                assertWithMessage("Unexpected visibility for scene \"${scene.debugName}\"")
+                    .that(isSurfaceBehindVisible)
+                    .isFalse()
+            }
         }
 
     @Test
@@ -427,9 +375,9 @@
 
             assertEquals(
                 listOf(
-                    false, // Not using the animation when we're just sitting on LOCKSCREEN.
+                    false // Not using the animation when we're just sitting on LOCKSCREEN.
                 ),
-                values
+                values,
             )
 
             surfaceBehindIsAnimatingFlow.emit(true)
@@ -437,7 +385,7 @@
             transitionRepository.sendTransitionSteps(
                 from = KeyguardState.LOCKSCREEN,
                 to = KeyguardState.GONE,
-                testScope
+                testScope,
             )
             runCurrent()
 
@@ -446,7 +394,7 @@
                     false,
                     true, // Still true when we're FINISHED -> GONE, since we're still animating.
                 ),
-                values
+                values,
             )
 
             surfaceBehindIsAnimatingFlow.emit(false)
@@ -458,7 +406,7 @@
                     true,
                     false, // False once the animation ends.
                 ),
-                values
+                values,
             )
         }
 
@@ -488,9 +436,9 @@
 
             assertEquals(
                 listOf(
-                    false, // Not using the animation when we're just sitting on LOCKSCREEN.
+                    false // Not using the animation when we're just sitting on LOCKSCREEN.
                 ),
-                values
+                values,
             )
 
             surfaceBehindIsAnimatingFlow.emit(true)
@@ -509,7 +457,7 @@
                     false,
                     true, // We're happily animating while transitioning to gone.
                 ),
-                values
+                values,
             )
 
             // Oh no, we're still surfaceBehindAnimating=true, but no longer transitioning to GONE.
@@ -536,7 +484,7 @@
                     true,
                     false, // Despite the animator still running, this should be false.
                 ),
-                values
+                values,
             )
 
             surfaceBehindIsAnimatingFlow.emit(false)
@@ -548,7 +496,7 @@
                     true,
                     false, // The animator ending should have no effect.
                 ),
-                values
+                values,
             )
         }
 
@@ -579,10 +527,10 @@
 
             assertEquals(
                 listOf(
-                    true, // Unsurprisingly, we should start with the lockscreen visible on
+                    true // Unsurprisingly, we should start with the lockscreen visible on
                     // LOCKSCREEN.
                 ),
-                values
+                values,
             )
 
             transitionRepository.sendTransitionStep(
@@ -596,9 +544,9 @@
 
             assertEquals(
                 listOf(
-                    true, // Lockscreen remains visible while we're transitioning to GONE.
+                    true // Lockscreen remains visible while we're transitioning to GONE.
                 ),
-                values
+                values,
             )
 
             transitionRepository.sendTransitionStep(
@@ -615,7 +563,7 @@
                     true,
                     false, // Once we're fully GONE, the lockscreen should not be visible.
                 ),
-                values
+                values,
             )
         }
 
@@ -628,7 +576,7 @@
             transitionRepository.sendTransitionSteps(
                 from = KeyguardState.LOCKSCREEN,
                 to = KeyguardState.GONE,
-                testScope
+                testScope,
             )
 
             runCurrent()
@@ -640,7 +588,7 @@
                     // Then, false, since we finish in GONE.
                     false,
                 ),
-                values
+                values,
             )
 
             transitionRepository.sendTransitionStep(
@@ -665,7 +613,7 @@
                     // Should remain false as we transition from GONE.
                     false,
                 ),
-                values
+                values,
             )
 
             transitionRepository.sendTransitionStep(
@@ -693,7 +641,7 @@
                     // visibility of the from state (LS).
                     true,
                 ),
-                values
+                values,
             )
 
             transitionRepository.sendTransitionStep(
@@ -706,14 +654,7 @@
 
             runCurrent()
 
-            assertEquals(
-                listOf(
-                    true,
-                    false,
-                    true,
-                ),
-                values
-            )
+            assertEquals(listOf(true, false, true), values)
         }
 
     /**
@@ -730,7 +671,7 @@
             transitionRepository.sendTransitionSteps(
                 from = KeyguardState.LOCKSCREEN,
                 to = KeyguardState.GONE,
-                testScope
+                testScope,
             )
 
             runCurrent()
@@ -740,7 +681,7 @@
                     // Not visible since we're GONE.
                     false,
                 ),
-                values
+                values,
             )
 
             transitionRepository.sendTransitionStep(
@@ -803,7 +744,7 @@
                     // STARTED to GONE after a CANCELED from GONE.
                     false,
                 ),
-                values
+                values,
             )
 
             transitionRepository.sendTransitionSteps(
@@ -820,7 +761,7 @@
                     // visible again once we're finished in LOCKSCREEN.
                     true,
                 ),
-                values
+                values,
             )
         }
 
@@ -833,7 +774,7 @@
             transitionRepository.sendTransitionSteps(
                 from = KeyguardState.LOCKSCREEN,
                 to = KeyguardState.GONE,
-                testScope
+                testScope,
             )
 
             runCurrent()
@@ -843,7 +784,7 @@
                     // Not visible when finished in GONE.
                     false,
                 ),
-                values
+                values,
             )
 
             transitionRepository.sendTransitionStep(
@@ -869,7 +810,7 @@
                     // Still not visible during GONE -> AOD.
                     false,
                 ),
-                values
+                values,
             )
 
             transitionRepository.sendTransitionStep(
@@ -886,9 +827,9 @@
                     true,
                     false,
                     // Visible now that we're FINISHED in AOD.
-                    true
+                    true,
                 ),
-                values
+                values,
             )
 
             transitionRepository.sendTransitionStep(
@@ -914,9 +855,9 @@
                     true,
                     false,
                     // Remains visible from AOD during transition.
-                    true
+                    true,
                 ),
-                values
+                values,
             )
 
             transitionRepository.sendTransitionStep(
@@ -934,15 +875,15 @@
                     false,
                     true,
                     // Until we're finished in GONE again.
-                    false
+                    false,
                 ),
-                values
+                values,
             )
         }
 
     @Test
     @EnableSceneContainer
-    fun lockscreenVisibility() =
+    fun lockscreenVisibilityWithScenes() =
         testScope.runTest {
             val isDeviceUnlocked by
                 collectLastValue(
@@ -956,32 +897,69 @@
             val lockscreenVisibility by collectLastValue(underTest.value.lockscreenVisibility)
             assertThat(lockscreenVisibility).isTrue()
 
+            kosmos.setSceneTransition(Idle(Scenes.Shade))
+            kosmos.sceneInteractor.changeScene(Scenes.Shade, "")
+            assertThat(currentScene).isEqualTo(Scenes.Shade)
+            assertThat(lockscreenVisibility).isTrue()
+
+            kosmos.setSceneTransition(Transition(from = Scenes.Shade, to = Scenes.QuickSettings))
+            assertThat(lockscreenVisibility).isTrue()
+
+            kosmos.setSceneTransition(Idle(Scenes.QuickSettings))
+            kosmos.sceneInteractor.changeScene(Scenes.QuickSettings, "")
+            assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
+            assertThat(lockscreenVisibility).isTrue()
+
+            kosmos.setSceneTransition(Transition(from = Scenes.QuickSettings, to = Scenes.Shade))
+            assertThat(lockscreenVisibility).isTrue()
+
+            kosmos.setSceneTransition(Idle(Scenes.Shade))
+            kosmos.sceneInteractor.changeScene(Scenes.Shade, "")
+            assertThat(currentScene).isEqualTo(Scenes.Shade)
+            assertThat(lockscreenVisibility).isTrue()
+
+            kosmos.setSceneTransition(Idle(Scenes.Bouncer))
             kosmos.sceneInteractor.changeScene(Scenes.Bouncer, "")
             assertThat(currentScene).isEqualTo(Scenes.Bouncer)
             assertThat(lockscreenVisibility).isTrue()
 
+            kosmos.setSceneTransition(Transition(from = Scenes.Bouncer, to = Scenes.Gone))
+            assertThat(lockscreenVisibility).isTrue()
+
+            kosmos.setSceneTransition(Idle(Scenes.Gone))
             kosmos.authenticationInteractor.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)
             assertThat(isDeviceUnlocked).isTrue()
             kosmos.sceneInteractor.changeScene(Scenes.Gone, "")
             assertThat(currentScene).isEqualTo(Scenes.Gone)
             assertThat(lockscreenVisibility).isFalse()
 
+            kosmos.setSceneTransition(Idle(Scenes.Shade))
             kosmos.sceneInteractor.changeScene(Scenes.Shade, "")
             assertThat(currentScene).isEqualTo(Scenes.Shade)
             assertThat(lockscreenVisibility).isFalse()
 
+            kosmos.setSceneTransition(Transition(from = Scenes.Shade, to = Scenes.QuickSettings))
+            assertThat(lockscreenVisibility).isFalse()
+
+            kosmos.setSceneTransition(Idle(Scenes.QuickSettings))
             kosmos.sceneInteractor.changeScene(Scenes.QuickSettings, "")
             assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
             assertThat(lockscreenVisibility).isFalse()
 
+            kosmos.setSceneTransition(Idle(Scenes.Shade))
             kosmos.sceneInteractor.changeScene(Scenes.Shade, "")
             assertThat(currentScene).isEqualTo(Scenes.Shade)
             assertThat(lockscreenVisibility).isFalse()
 
+            kosmos.setSceneTransition(Idle(Scenes.Gone))
             kosmos.sceneInteractor.changeScene(Scenes.Gone, "")
             assertThat(currentScene).isEqualTo(Scenes.Gone)
             assertThat(lockscreenVisibility).isFalse()
 
+            kosmos.setSceneTransition(Transition(from = Scenes.Gone, to = Scenes.Lockscreen))
+            assertThat(lockscreenVisibility).isFalse()
+
+            kosmos.setSceneTransition(Idle(Scenes.Lockscreen))
             kosmos.sceneInteractor.changeScene(Scenes.Lockscreen, "")
             assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
             assertThat(lockscreenVisibility).isTrue()
@@ -1037,7 +1015,7 @@
                 flowOf(Scenes.Lockscreen),
                 progress,
                 false,
-                flowOf(false)
+                flowOf(false),
             )
 
         private val goneToLs =
@@ -1047,7 +1025,7 @@
                 flowOf(Scenes.Lockscreen),
                 progress,
                 false,
-                flowOf(false)
+                flowOf(false),
             )
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
index 9c58e2b..92764ae 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
@@ -29,11 +29,13 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mock
 import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyNoMoreInteractions
 import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -112,4 +114,20 @@
         verify(activityTaskManagerService).setLockScreenShown(true, false)
         verifyNoMoreInteractions(activityTaskManagerService)
     }
+
+    @Test
+    fun setSurfaceBehindVisibility_goesAwayFirst_andIgnoresSecondCall() {
+        underTest.setLockscreenShown(true)
+        underTest.setSurfaceBehindVisibility(true)
+        verify(activityTaskManagerService).keyguardGoingAway(0)
+
+        underTest.setSurfaceBehindVisibility(true)
+        verifyNoMoreInteractions(keyguardTransitions)
+    }
+
+    @Test
+    fun setSurfaceBehindVisibility_falseSetsLockscreenVisibility() {
+        underTest.setSurfaceBehindVisibility(false)
+        verify(activityTaskManagerService).setLockScreenShown(eq(true), any())
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
index 7f09370..0e3b03f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
@@ -44,6 +44,7 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Ignore
@@ -107,6 +108,7 @@
     fun translationAndScale_whenNotDozing() =
         testScope.runTest {
             val movement by collectLastValue(underTest.movement)
+            assertThat(movement?.translationX).isEqualTo(0)
 
             // Set to not dozing (on lockscreen)
             keyguardTransitionRepository.sendTransitionStep(
@@ -180,6 +182,7 @@
         testScope.runTest {
             underTest.updateBurnInParams(burnInParameters.copy(minViewY = 100))
             val movement by collectLastValue(underTest.movement)
+            assertThat(movement?.translationX).isEqualTo(0)
 
             // Set to dozing (on AOD)
             keyguardTransitionRepository.sendTransitionStep(
@@ -221,6 +224,7 @@
         testScope.runTest {
             underTest.updateBurnInParams(burnInParameters.copy(minViewY = 100, topInset = 80))
             val movement by collectLastValue(underTest.movement)
+            assertThat(movement?.translationX).isEqualTo(0)
 
             // Set to dozing (on AOD)
             keyguardTransitionRepository.sendTransitionStep(
@@ -263,6 +267,7 @@
         testScope.runTest {
             underTest.updateBurnInParams(burnInParameters.copy(minViewY = 100, topInset = 80))
             val movement by collectLastValue(underTest.movement)
+            assertThat(movement?.translationX).isEqualTo(0)
 
             // Set to dozing (on AOD)
             keyguardTransitionRepository.sendTransitionStep(
@@ -305,6 +310,7 @@
             whenever(clockController.config.useAlternateSmartspaceAODTransition).thenReturn(true)
 
             val movement by collectLastValue(underTest.movement)
+            assertThat(movement?.translationX).isEqualTo(0)
 
             // Set to dozing (on AOD)
             keyguardTransitionRepository.sendTransitionStep(
@@ -423,6 +429,7 @@
                 .thenReturn(if (isWeatherClock) true else false)
 
             val movement by collectLastValue(underTest.movement)
+            assertThat(movement?.translationX).isEqualTo(0)
 
             // Set to dozing (on AOD)
             keyguardTransitionRepository.sendTransitionStep(
@@ -434,6 +441,7 @@
                 ),
                 validateStep = false,
             )
+            runCurrent()
 
             // Trigger a change to the burn-in model
             burnInFlow.value = BurnInModel(translationX = 20, translationY = 30, scale = 0.5f)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryTest.kt
new file mode 100644
index 0000000..9e3fdf3
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryTest.kt
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.domain.pipeline
+
+import android.media.session.MediaSession
+import android.os.Bundle
+import android.os.Handler
+import android.os.looper
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import androidx.media.utils.MediaConstants
+import androidx.media3.common.Player
+import androidx.media3.session.CommandButton
+import androidx.media3.session.MediaController as Media3Controller
+import androidx.media3.session.SessionCommand
+import androidx.media3.session.SessionToken
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.graphics.imageLoader
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.media.controls.shared.mediaLogger
+import com.android.systemui.media.controls.shared.model.MediaButton
+import com.android.systemui.media.controls.util.fakeMediaControllerFactory
+import com.android.systemui.media.controls.util.fakeSessionTokenFactory
+import com.android.systemui.res.R
+import com.android.systemui.testKosmos
+import com.google.common.collect.ImmutableList
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.clearInvocations
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+private const val PACKAGE_NAME = "package_name"
+private const val CUSTOM_ACTION_NAME = "Custom Action"
+private const val CUSTOM_ACTION_COMMAND = "custom-action"
+
+@SmallTest
+@RunWithLooper
+@RunWith(AndroidJUnit4::class)
+class Media3ActionFactoryTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val controllerFactory = kosmos.fakeMediaControllerFactory
+    private val tokenFactory = kosmos.fakeSessionTokenFactory
+    private lateinit var testableLooper: TestableLooper
+
+    private var commandCaptor = argumentCaptor<SessionCommand>()
+    private var runnableCaptor = argumentCaptor<Runnable>()
+
+    private val legacyToken = MediaSession.Token(1, null)
+    private val token = mock<SessionToken>()
+    private val handler =
+        mock<Handler> {
+            on { post(runnableCaptor.capture()) } doAnswer
+                {
+                    runnableCaptor.lastValue.run()
+                    true
+                }
+        }
+    private val customLayout = ImmutableList.of<CommandButton>()
+    private val media3Controller =
+        mock<Media3Controller> {
+            on { customLayout } doReturn customLayout
+            on { sessionExtras } doReturn Bundle()
+            on { isCommandAvailable(any()) } doReturn true
+            on { isSessionCommandAvailable(any<SessionCommand>()) } doReturn true
+        }
+
+    private lateinit var underTest: Media3ActionFactory
+
+    @Before
+    fun setup() {
+        testableLooper = TestableLooper.get(this)
+
+        underTest =
+            Media3ActionFactory(
+                context,
+                kosmos.imageLoader,
+                controllerFactory,
+                tokenFactory,
+                kosmos.mediaLogger,
+                kosmos.looper,
+                handler,
+                kosmos.testScope,
+            )
+
+        controllerFactory.setMedia3Controller(media3Controller)
+        tokenFactory.setMedia3SessionToken(token)
+    }
+
+    @Test
+    fun media3Actions_playingState_withCustomActions() =
+        testScope.runTest {
+            // Media is playing, all commands available, with custom actions
+            val customLayout = ImmutableList.copyOf((0..1).map { createCustomCommandButton(it) })
+            whenever(media3Controller.customLayout).thenReturn(customLayout)
+            whenever(media3Controller.isPlaying).thenReturn(true)
+            val result = getActions()
+
+            assertThat(result).isNotNull()
+
+            val actions = result!!
+            assertThat(actions.playOrPause!!.contentDescription)
+                .isEqualTo(context.getString(R.string.controls_media_button_pause))
+            actions.playOrPause!!.action!!.run()
+            runCurrent()
+            verify(media3Controller).pause()
+            verify(media3Controller).release()
+            clearInvocations(media3Controller)
+
+            assertThat(actions.prevOrCustom!!.contentDescription)
+                .isEqualTo(context.getString(R.string.controls_media_button_prev))
+            actions.prevOrCustom!!.action!!.run()
+            runCurrent()
+            verify(media3Controller).seekToPrevious()
+            verify(media3Controller).release()
+            clearInvocations(media3Controller)
+
+            assertThat(actions.nextOrCustom!!.contentDescription)
+                .isEqualTo(context.getString(R.string.controls_media_button_next))
+            actions.nextOrCustom!!.action!!.run()
+            runCurrent()
+            verify(media3Controller).seekToNext()
+            verify(media3Controller).release()
+            clearInvocations(media3Controller)
+
+            assertThat(actions.custom0!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 0")
+            actions.custom0!!.action!!.run()
+            runCurrent()
+            verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>())
+            assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 0")
+            verify(media3Controller).release()
+            clearInvocations(media3Controller)
+
+            assertThat(actions.custom1!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 1")
+            actions.custom1!!.action!!.run()
+            runCurrent()
+            verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>())
+            assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 1")
+            verify(media3Controller).release()
+        }
+
+    @Test
+    fun media3Actions_pausedState_hasPauseAction() =
+        testScope.runTest {
+            whenever(media3Controller.isPlaying).thenReturn(false)
+            val result = getActions()
+
+            assertThat(result).isNotNull()
+            val actions = result!!
+            assertThat(actions.playOrPause!!.contentDescription)
+                .isEqualTo(context.getString(R.string.controls_media_button_play))
+            clearInvocations(media3Controller)
+
+            actions.playOrPause!!.action!!.run()
+            runCurrent()
+            verify(media3Controller).play()
+            verify(media3Controller).release()
+            clearInvocations(media3Controller)
+        }
+
+    @Test
+    fun media3Actions_bufferingState_hasLoadingSpinner() =
+        testScope.runTest {
+            whenever(media3Controller.isPlaying).thenReturn(false)
+            whenever(media3Controller.playbackState).thenReturn(Player.STATE_BUFFERING)
+            val result = getActions()
+
+            assertThat(result).isNotNull()
+            val actions = result!!
+            assertThat(actions.playOrPause!!.contentDescription)
+                .isEqualTo(context.getString(R.string.controls_media_button_connecting))
+            assertThat(actions.playOrPause!!.action).isNull()
+            assertThat(actions.playOrPause!!.rebindId)
+                .isEqualTo(com.android.internal.R.drawable.progress_small_material)
+        }
+
+    @Test
+    fun media3Actions_noPrevNext_usesCustom() =
+        testScope.runTest {
+            val customLayout = ImmutableList.copyOf((0..4).map { createCustomCommandButton(it) })
+            whenever(media3Controller.customLayout).thenReturn(customLayout)
+            whenever(media3Controller.isPlaying).thenReturn(true)
+            whenever(media3Controller.isCommandAvailable(eq(Player.COMMAND_SEEK_TO_PREVIOUS)))
+                .thenReturn(false)
+            whenever(
+                    media3Controller.isCommandAvailable(
+                        eq(Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
+                    )
+                )
+                .thenReturn(false)
+            whenever(media3Controller.isCommandAvailable(eq(Player.COMMAND_SEEK_TO_NEXT)))
+                .thenReturn(false)
+            whenever(
+                    media3Controller.isCommandAvailable(eq(Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM))
+                )
+                .thenReturn(false)
+            val result = getActions()
+
+            assertThat(result).isNotNull()
+            val actions = result!!
+
+            assertThat(actions.prevOrCustom!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 0")
+            actions.prevOrCustom!!.action!!.run()
+            runCurrent()
+            verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>())
+            assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 0")
+            verify(media3Controller).release()
+            clearInvocations(media3Controller)
+
+            assertThat(actions.nextOrCustom!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 1")
+            actions.nextOrCustom!!.action!!.run()
+            runCurrent()
+            verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>())
+            assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 1")
+            verify(media3Controller).release()
+            clearInvocations(media3Controller)
+
+            assertThat(actions.custom0!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 2")
+            actions.custom0!!.action!!.run()
+            runCurrent()
+            testableLooper.processAllMessages()
+            verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>())
+            assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 2")
+            verify(media3Controller).release()
+            clearInvocations(media3Controller)
+
+            assertThat(actions.custom1!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 3")
+            actions.custom1!!.action!!.run()
+            runCurrent()
+            verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>())
+            assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 3")
+            verify(media3Controller).release()
+        }
+
+    @Test
+    fun media3Actions_noPrevNext_reservedSpace() =
+        testScope.runTest {
+            val customLayout = ImmutableList.copyOf((0..4).map { createCustomCommandButton(it) })
+            whenever(media3Controller.customLayout).thenReturn(customLayout)
+            whenever(media3Controller.isPlaying).thenReturn(true)
+            whenever(media3Controller.isCommandAvailable(eq(Player.COMMAND_SEEK_TO_PREVIOUS)))
+                .thenReturn(false)
+            whenever(
+                    media3Controller.isCommandAvailable(
+                        eq(Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
+                    )
+                )
+                .thenReturn(false)
+            whenever(media3Controller.isCommandAvailable(eq(Player.COMMAND_SEEK_TO_NEXT)))
+                .thenReturn(false)
+            whenever(
+                    media3Controller.isCommandAvailable(eq(Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM))
+                )
+                .thenReturn(false)
+            val extras =
+                Bundle().apply {
+                    putBoolean(
+                        MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV,
+                        true,
+                    )
+                    putBoolean(
+                        MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT,
+                        true,
+                    )
+                }
+            whenever(media3Controller.sessionExtras).thenReturn(extras)
+            val result = getActions()
+
+            assertThat(result).isNotNull()
+            val actions = result!!
+
+            assertThat(actions.prevOrCustom).isNull()
+            assertThat(actions.nextOrCustom).isNull()
+
+            assertThat(actions.custom0!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 0")
+            actions.custom0!!.action!!.run()
+            runCurrent()
+            verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>())
+            assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 0")
+            verify(media3Controller).release()
+            clearInvocations(media3Controller)
+
+            assertThat(actions.custom1!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 1")
+            actions.custom1!!.action!!.run()
+            runCurrent()
+            verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>())
+            assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 1")
+            verify(media3Controller).release()
+        }
+
+    private suspend fun getActions(): MediaButton? {
+        val result = underTest.createActionsFromSession(PACKAGE_NAME, legacyToken)
+        testScope.runCurrent()
+        verify(media3Controller).release()
+
+        // Clear so tests can verify the correct number of release() calls in later operations
+        clearInvocations(media3Controller)
+        return result
+    }
+
+    private fun createCustomCommandButton(id: Int): CommandButton {
+        return CommandButton.Builder()
+            .setDisplayName("$CUSTOM_ACTION_NAME $id")
+            .setSessionCommand(SessionCommand("$CUSTOM_ACTION_COMMAND $id", Bundle()))
+            .build()
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt
index fc9e595..1a7265b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt
@@ -29,6 +29,7 @@
 import android.media.session.PlaybackState
 import android.os.Bundle
 import android.service.notification.StatusBarNotification
+import android.testing.TestableLooper.RunWithLooper
 import androidx.media.utils.MediaConstants
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -69,6 +70,7 @@
 private const val SESSION_EMPTY_TITLE = ""
 
 @SmallTest
+@RunWithLooper
 @RunWith(AndroidJUnit4::class)
 class MediaDataLoaderTest : SysuiTestCase() {
 
@@ -80,6 +82,7 @@
     private val fakeFeatureFlags = kosmos.fakeFeatureFlagsClassic
     private val mediaFlags = kosmos.mediaFlags
     private val mediaControllerFactory = kosmos.fakeMediaControllerFactory
+    private val media3ActionFactory = kosmos.media3ActionFactory
     private val session = MediaSession(context, "MediaDataLoaderTestSession")
     private val metadataBuilder =
         MediaMetadata.Builder().apply {
@@ -87,21 +90,25 @@
             putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
         }
 
-    private val underTest: MediaDataLoader =
-        MediaDataLoader(
-            context,
-            testDispatcher,
-            testScope,
-            mediaControllerFactory,
-            mediaFlags,
-            kosmos.imageLoader,
-            statusBarManager,
-        )
+    private lateinit var underTest: MediaDataLoader
 
     @Before
     fun setUp() {
         mediaControllerFactory.setControllerForToken(session.sessionToken, mediaController)
+        whenever(mediaController.sessionToken).thenReturn(session.sessionToken)
         whenever(mediaController.metadata).then { metadataBuilder.build() }
+
+        underTest =
+            MediaDataLoader(
+                context,
+                testDispatcher,
+                testScope,
+                mediaControllerFactory,
+                mediaFlags,
+                kosmos.imageLoader,
+                statusBarManager,
+                kosmos.media3ActionFactory,
+            )
     }
 
     @Test
@@ -394,6 +401,7 @@
                     mediaFlags,
                     mockImageLoader,
                     statusBarManager,
+                    media3ActionFactory,
                 )
             metadataBuilder.putString(
                 MediaMetadata.METADATA_KEY_ALBUM_ART_URI,
@@ -422,6 +430,7 @@
                     mediaFlags,
                     mockImageLoader,
                     statusBarManager,
+                    media3ActionFactory,
                 )
             metadataBuilder.putString(
                 MediaMetadata.METADATA_KEY_ALBUM_ART_URI,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
index e12c67b..104aa51 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
@@ -16,7 +16,7 @@
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.wm.shell.recents.RecentTasks
-import com.android.wm.shell.shared.GroupedRecentTaskInfo
+import com.android.wm.shell.shared.GroupedTaskInfo
 import com.android.wm.shell.shared.split.SplitBounds
 import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50
 import com.google.common.truth.Truth.assertThat
@@ -219,9 +219,9 @@
     }
 
     @Suppress("UNCHECKED_CAST")
-    private fun givenRecentTasks(vararg tasks: GroupedRecentTaskInfo) {
+    private fun givenRecentTasks(vararg tasks: GroupedTaskInfo) {
         whenever(recentTasks.getRecentTasks(any(), any(), any(), any(), any())).thenAnswer {
-            val consumer = it.arguments.last() as Consumer<List<GroupedRecentTaskInfo>>
+            val consumer = it.arguments.last() as Consumer<List<GroupedTaskInfo>>
             consumer.accept(tasks.toList())
         }
     }
@@ -247,7 +247,7 @@
         userId: Int = 0,
         isVisible: Boolean = false,
         userType: RecentTask.UserType = STANDARD,
-    ): GroupedRecentTaskInfo {
+    ): GroupedTaskInfo {
         val userInfo =
             mock<UserInfo> {
                 whenever(isCloneProfile).thenReturn(userType == CLONED)
@@ -255,7 +255,7 @@
                 whenever(isPrivateProfile).thenReturn(userType == PRIVATE)
             }
         whenever(userManager.getUserInfo(userId)).thenReturn(userInfo)
-        return GroupedRecentTaskInfo.forSingleTask(createTaskInfo(taskId, userId, isVisible))
+        return GroupedTaskInfo.forFullscreenTasks(createTaskInfo(taskId, userId, isVisible))
     }
 
     private fun createTaskPair(
@@ -263,9 +263,9 @@
         userId1: Int = 0,
         taskId2: Int,
         userId2: Int = 0,
-        isVisible: Boolean = false
-    ): GroupedRecentTaskInfo =
-        GroupedRecentTaskInfo.forSplitTasks(
+        isVisible: Boolean = false,
+    ): GroupedTaskInfo =
+        GroupedTaskInfo.forSplitTasks(
             createTaskInfo(taskId1, userId1, isVisible),
             createTaskInfo(taskId2, userId2, isVisible),
             SplitBounds(Rect(), Rect(), taskId1, taskId2, SNAP_TO_2_50_50)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
index 2905a73..646722b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
@@ -116,6 +116,7 @@
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.data.repository.LightBarControllerStore;
 import com.android.systemui.statusbar.phone.AutoHideController;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.LightBarController;
@@ -208,7 +209,7 @@
     @Mock
     private LightBarController mLightBarController;
     @Mock
-    private LightBarController.Factory mLightBarcontrollerFactory;
+    private LightBarControllerStore mLightBarControllerStore;
     @Mock
     private AutoHideController mAutoHideController;
     @Mock
@@ -257,7 +258,7 @@
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
 
-        when(mLightBarcontrollerFactory.create(any(Context.class))).thenReturn(mLightBarController);
+        when(mLightBarControllerStore.forDisplay(anyInt())).thenReturn(mLightBarController);
         when(mAutoHideControllerFactory.create(any(Context.class))).thenReturn(mAutoHideController);
         when(mNavigationBarView.getHomeButton()).thenReturn(mHomeButton);
         when(mNavigationBarView.getRecentsButton()).thenReturn(mRecentsButton);
@@ -649,8 +650,7 @@
                 mFakeExecutor,
                 mUiEventLogger,
                 mNavBarHelper,
-                mLightBarController,
-                mLightBarcontrollerFactory,
+                mLightBarControllerStore,
                 mAutoHideController,
                 mAutoHideControllerFactory,
                 Optional.of(mTelecomManager),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
index ff40e43..a063531 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
@@ -115,7 +115,7 @@
         underTest.logUserActionPipeline(
             TileSpec.create("test_spec"),
             QSTileUserAction.Click(null),
-            QSTileState.build({ Icon.Resource(0, ContentDescription.Resource(0)) }, "") {},
+            QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {},
             "test_data",
         )
 
@@ -141,7 +141,7 @@
     fun testLogStateUpdate() {
         underTest.logStateUpdate(
             TileSpec.create("test_spec"),
-            QSTileState.build({ Icon.Resource(0, ContentDescription.Resource(0)) }, "") {},
+            QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {},
             "test_data",
         )
 
@@ -162,18 +162,14 @@
 
     @Test
     fun testLogForceUpdate() {
-        underTest.logForceUpdate(
-            TileSpec.create("test_spec"),
-        )
+        underTest.logForceUpdate(TileSpec.create("test_spec"))
 
         assertThat(logBuffer.getStringBuffer()).contains("tile data force update")
     }
 
     @Test
     fun testLogInitialUpdate() {
-        underTest.logInitialRequest(
-            TileSpec.create("test_spec"),
-        )
+        underTest.logInitialRequest(TileSpec.create("test_spec"))
 
         assertThat(logBuffer.getStringBuffer()).contains("tile data initial update")
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt
index c918ed8..056efb3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt
@@ -85,8 +85,8 @@
                     object : QSTileDataToStateMapper<Any> {
                         override fun map(config: QSTileConfig, data: Any): QSTileState =
                             QSTileState.build(
-                                { Icon.Resource(0, ContentDescription.Resource(0)) },
-                                data.toString()
+                                Icon.Resource(0, ContentDescription.Resource(0)),
+                                data.toString(),
                             ) {}
                     }
                 },
@@ -116,7 +116,7 @@
                 .isEqualTo(
                     "test_spec:\n" +
                         "    QSTileState(" +
-                        "icon=() -> com.android.systemui.common.shared.model.Icon?, " +
+                        "icon=Resource(res=0, contentDescription=Resource(res=0)), " +
                         "iconRes=null, " +
                         "label=test_data, " +
                         "activationState=INACTIVE, " +
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/AirplaneModeMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/AirplaneModeMapperTest.kt
index 5a73fe2..00460bf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/AirplaneModeMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/AirplaneModeMapperTest.kt
@@ -66,7 +66,7 @@
             createAirplaneModeState(
                 QSTileState.ActivationState.ACTIVE,
                 context.resources.getStringArray(R.array.tile_states_airplane)[Tile.STATE_ACTIVE],
-                R.drawable.qs_airplane_icon_on
+                R.drawable.qs_airplane_icon_on,
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -81,7 +81,7 @@
             createAirplaneModeState(
                 QSTileState.ActivationState.INACTIVE,
                 context.resources.getStringArray(R.array.tile_states_airplane)[Tile.STATE_INACTIVE],
-                R.drawable.qs_airplane_icon_off
+                R.drawable.qs_airplane_icon_off,
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -89,11 +89,11 @@
     private fun createAirplaneModeState(
         activationState: QSTileState.ActivationState,
         secondaryLabel: String,
-        iconRes: Int
+        iconRes: Int,
     ): QSTileState {
         val label = context.getString(R.string.airplane_mode)
         return QSTileState(
-            { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+            Icon.Loaded(context.getDrawable(iconRes)!!, null),
             iconRes,
             label,
             activationState,
@@ -103,7 +103,7 @@
             null,
             QSTileState.SideViewIcon.None,
             QSTileState.EnabledState.ENABLED,
-            Switch::class.qualifiedName
+            Switch::class.qualifiedName,
         )
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt
index 79e4fef..632aae0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt
@@ -51,7 +51,7 @@
                 .apply { addOverride(R.drawable.ic_alarm, TestStubDrawable()) }
                 .resources,
             context.theme,
-            fakeClock
+            fakeClock,
         )
     }
 
@@ -69,7 +69,7 @@
         val expectedState =
             createAlarmTileState(
                 QSTileState.ActivationState.INACTIVE,
-                context.getString(R.string.qs_alarm_tile_no_alarm)
+                context.getString(R.string.qs_alarm_tile_no_alarm),
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -85,7 +85,7 @@
         val localDateTime =
             LocalDateTime.ofInstant(
                 Instant.ofEpochMilli(triggerTime),
-                TimeZone.getDefault().toZoneId()
+                TimeZone.getDefault().toZoneId(),
             )
         val expectedSecondaryLabel = AlarmTileMapper.formatter24Hour.format(localDateTime)
         val expectedState =
@@ -104,7 +104,7 @@
         val localDateTime =
             LocalDateTime.ofInstant(
                 Instant.ofEpochMilli(triggerTime),
-                TimeZone.getDefault().toZoneId()
+                TimeZone.getDefault().toZoneId(),
             )
         val expectedSecondaryLabel = AlarmTileMapper.formatter12Hour.format(localDateTime)
         val expectedState =
@@ -124,7 +124,7 @@
         val localDateTime =
             LocalDateTime.ofInstant(
                 Instant.ofEpochMilli(triggerTime),
-                TimeZone.getDefault().toZoneId()
+                TimeZone.getDefault().toZoneId(),
             )
         val expectedSecondaryLabel = AlarmTileMapper.formatterDateOnly.format(localDateTime)
         val expectedState =
@@ -144,7 +144,7 @@
         val localDateTime =
             LocalDateTime.ofInstant(
                 Instant.ofEpochMilli(triggerTime),
-                TimeZone.getDefault().toZoneId()
+                TimeZone.getDefault().toZoneId(),
             )
         val expectedSecondaryLabel = AlarmTileMapper.formatter12Hour.format(localDateTime)
         val expectedState =
@@ -164,7 +164,7 @@
         val localDateTime =
             LocalDateTime.ofInstant(
                 Instant.ofEpochMilli(triggerTime),
-                TimeZone.getDefault().toZoneId()
+                TimeZone.getDefault().toZoneId(),
             )
         val expectedSecondaryLabel = AlarmTileMapper.formatterDateOnly.format(localDateTime)
         val expectedState =
@@ -174,11 +174,11 @@
 
     private fun createAlarmTileState(
         activationState: QSTileState.ActivationState,
-        secondaryLabel: String
+        secondaryLabel: String,
     ): QSTileState {
         val label = context.getString(R.string.status_bar_alarm)
         return QSTileState(
-            { Icon.Loaded(context.getDrawable(R.drawable.ic_alarm)!!, null) },
+            Icon.Loaded(context.getDrawable(R.drawable.ic_alarm)!!, null),
             R.drawable.ic_alarm,
             label,
             activationState,
@@ -188,7 +188,7 @@
             null,
             QSTileState.SideViewIcon.Chevron,
             QSTileState.EnabledState.ENABLED,
-            Switch::class.qualifiedName
+            Switch::class.qualifiedName,
         )
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt
index a0d26c2..5385f94 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt
@@ -253,7 +253,7 @@
     ): QSTileState {
         val label = context.getString(R.string.battery_detail_switch_title)
         return QSTileState(
-            { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+            Icon.Loaded(context.getDrawable(iconRes)!!, null),
             iconRes,
             label,
             activationState,
@@ -265,7 +265,7 @@
             stateDescription,
             QSTileState.SideViewIcon.None,
             QSTileState.EnabledState.ENABLED,
-            Switch::class.qualifiedName
+            Switch::class.qualifiedName,
         )
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt
index ea7b7c5..356b98e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt
@@ -45,7 +45,7 @@
             context.orCreateTestableResources
                 .apply { addOverride(R.drawable.ic_qs_color_correction, TestStubDrawable()) }
                 .resources,
-            context.theme
+            context.theme,
         )
     }
 
@@ -73,11 +73,11 @@
 
     private fun createColorCorrectionTileState(
         activationState: QSTileState.ActivationState,
-        secondaryLabel: String
+        secondaryLabel: String,
     ): QSTileState {
         val label = context.getString(R.string.quick_settings_color_correction_label)
         return QSTileState(
-            { Icon.Loaded(context.getDrawable(R.drawable.ic_qs_color_correction)!!, null) },
+            Icon.Loaded(context.getDrawable(R.drawable.ic_qs_color_correction)!!, null),
             R.drawable.ic_qs_color_correction,
             label,
             activationState,
@@ -87,7 +87,7 @@
             null,
             QSTileState.SideViewIcon.None,
             QSTileState.EnabledState.ENABLED,
-            Switch::class.qualifiedName
+            Switch::class.qualifiedName,
         )
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt
index f1d08c0..8236c4c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt
@@ -57,10 +57,7 @@
     private val kosmos =
         testKosmos().apply { customTileSpec = TileSpec.Companion.create(TEST_COMPONENT) }
     private val underTest by lazy {
-        CustomTileMapper(
-            context = mockContext,
-            uriGrantsManager = uriGrantsManager,
-        )
+        CustomTileMapper(context = mockContext, uriGrantsManager = uriGrantsManager)
     }
 
     @Test
@@ -68,10 +65,7 @@
         with(kosmos) {
             testScope.runTest {
                 val actual =
-                    underTest.map(
-                        customTileQsTileConfig,
-                        createModel(hasPendingBind = true),
-                    )
+                    underTest.map(customTileQsTileConfig, createModel(hasPendingBind = true))
                 val expected =
                     createTileState(
                         activationState = QSTileState.ActivationState.UNAVAILABLE,
@@ -91,10 +85,7 @@
                         customTileQsTileConfig,
                         createModel(tileState = Tile.STATE_ACTIVE),
                     )
-                val expected =
-                    createTileState(
-                        activationState = QSTileState.ActivationState.ACTIVE,
-                    )
+                val expected = createTileState(activationState = QSTileState.ActivationState.ACTIVE)
 
                 assertThat(actual).isEqualTo(expected)
             }
@@ -110,9 +101,7 @@
                         createModel(tileState = Tile.STATE_INACTIVE),
                     )
                 val expected =
-                    createTileState(
-                        activationState = QSTileState.ActivationState.INACTIVE,
-                    )
+                    createTileState(activationState = QSTileState.ActivationState.INACTIVE)
 
                 assertThat(actual).isEqualTo(expected)
             }
@@ -142,10 +131,7 @@
         with(kosmos) {
             testScope.runTest {
                 val actual =
-                    underTest.map(
-                        customTileQsTileConfig,
-                        createModel(isToggleable = false),
-                    )
+                    underTest.map(customTileQsTileConfig, createModel(isToggleable = false))
                 val expected =
                     createTileState(
                         sideIcon = QSTileState.SideViewIcon.Chevron,
@@ -184,7 +170,7 @@
                         customTileQsTileConfig,
                         createModel(
                             tileIcon = createIcon(RuntimeException(), false),
-                            defaultTileIcon = createIcon(null, true)
+                            defaultTileIcon = createIcon(null, true),
                         ),
                     )
                 val expected =
@@ -266,7 +252,7 @@
         a11yClass: String? = Switch::class.qualifiedName,
     ): QSTileState {
         return QSTileState(
-            { icon?.let { com.android.systemui.common.shared.model.Icon.Loaded(icon, null) } },
+            icon?.let { com.android.systemui.common.shared.model.Icon.Loaded(icon, null) },
             null,
             "test label",
             activationState,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt
index 63fb67d..587585c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt
@@ -44,7 +44,7 @@
                     addOverride(R.drawable.qs_flashlight_icon_on, TestStubDrawable())
                 }
                 .resources,
-            context.theme
+            context.theme,
         )
     }
 
@@ -74,7 +74,7 @@
 
         val expectedIcon =
             Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_on)!!, null)
-        val actualIcon = tileState.icon()
+        val actualIcon = tileState.icon
         assertThat(actualIcon).isEqualTo(expectedIcon)
     }
 
@@ -85,7 +85,7 @@
 
         val expectedIcon =
             Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_off)!!, null)
-        val actualIcon = tileState.icon()
+        val actualIcon = tileState.icon
         assertThat(actualIcon).isEqualTo(expectedIcon)
     }
 
@@ -96,7 +96,7 @@
 
         val expectedIcon =
             Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_off)!!, null)
-        val actualIcon = tileState.icon()
+        val actualIcon = tileState.icon
         assertThat(actualIcon).isEqualTo(expectedIcon)
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt
index f8e01be..e81771e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt
@@ -42,7 +42,7 @@
             context.orCreateTestableResources
                 .apply { addOverride(R.drawable.ic_qs_font_scaling, TestStubDrawable()) }
                 .resources,
-            context.theme
+            context.theme,
         )
     }
 
@@ -58,14 +58,7 @@
 
     private fun createFontScalingTileState(): QSTileState =
         QSTileState(
-            {
-                Icon.Loaded(
-                    context.getDrawable(
-                        R.drawable.ic_qs_font_scaling,
-                    )!!,
-                    null
-                )
-            },
+            Icon.Loaded(context.getDrawable(R.drawable.ic_qs_font_scaling)!!, null),
             R.drawable.ic_qs_font_scaling,
             context.getString(R.string.quick_settings_font_scaling_label),
             QSTileState.ActivationState.ACTIVE,
@@ -75,6 +68,6 @@
             null,
             QSTileState.SideViewIcon.Chevron,
             QSTileState.EnabledState.ENABLED,
-            Switch::class.qualifiedName
+            Switch::class.qualifiedName,
         )
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapperTest.kt
index cdf6bda..12d604f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapperTest.kt
@@ -102,7 +102,7 @@
         val label = context.getString(R.string.quick_settings_hearing_devices_label)
         val iconRes = R.drawable.qs_hearing_devices_icon
         return QSTileState(
-            { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+            Icon.Loaded(context.getDrawable(iconRes)!!, null),
             iconRes,
             label,
             activationState,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt
index d32ba47..9dcf49e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt
@@ -187,7 +187,7 @@
     ): QSTileState {
         val label = context.getString(R.string.quick_settings_internet_label)
         return QSTileState(
-            { icon },
+            icon,
             iconRes,
             label,
             activationState,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt
index a7bd697..30fce73 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt
@@ -49,7 +49,7 @@
                     addOverride(R.drawable.qs_invert_colors_icon_on, TestStubDrawable())
                 }
                 .resources,
-            context.theme
+            context.theme,
         )
     }
 
@@ -63,7 +63,7 @@
             createColorInversionTileState(
                 QSTileState.ActivationState.INACTIVE,
                 subtitleArray[1],
-                R.drawable.qs_invert_colors_icon_off
+                R.drawable.qs_invert_colors_icon_off,
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -78,7 +78,7 @@
             createColorInversionTileState(
                 QSTileState.ActivationState.ACTIVE,
                 subtitleArray[2],
-                R.drawable.qs_invert_colors_icon_on
+                R.drawable.qs_invert_colors_icon_on,
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -90,7 +90,7 @@
     ): QSTileState {
         val label = context.getString(R.string.quick_settings_inversion_label)
         return QSTileState(
-            { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+            Icon.Loaded(context.getDrawable(iconRes)!!, null),
             iconRes,
             label,
             activationState,
@@ -100,7 +100,7 @@
             null,
             QSTileState.SideViewIcon.None,
             QSTileState.EnabledState.ENABLED,
-            Switch::class.qualifiedName
+            Switch::class.qualifiedName,
         )
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt
index ea74a4c..37e8a60 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt
@@ -45,7 +45,7 @@
                     addOverride(R.drawable.qs_location_icon_on, TestStubDrawable())
                 }
                 .resources,
-            context.theme
+            context.theme,
         )
     }
 
@@ -70,7 +70,7 @@
         val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(true))
 
         val expectedIcon = Icon.Loaded(context.getDrawable(R.drawable.qs_location_icon_on)!!, null)
-        val actualIcon = tileState.icon()
+        val actualIcon = tileState.icon
         Truth.assertThat(actualIcon).isEqualTo(expectedIcon)
     }
 
@@ -79,7 +79,7 @@
         val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(false))
 
         val expectedIcon = Icon.Loaded(context.getDrawable(R.drawable.qs_location_icon_off)!!, null)
-        val actualIcon = tileState.icon()
+        val actualIcon = tileState.icon
         Truth.assertThat(actualIcon).isEqualTo(expectedIcon)
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt
index c3d45db..4e91d16 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt
@@ -59,34 +59,24 @@
     @Test
     fun inactiveState() {
         val icon = TestStubDrawable("res123").asIcon()
-        val model =
-            ModesTileModel(
-                isActivated = false,
-                activeModes = emptyList(),
-                icon = icon,
-            )
+        val model = ModesTileModel(isActivated = false, activeModes = emptyList(), icon = icon)
 
         val state = underTest.map(config, model)
 
         assertThat(state.activationState).isEqualTo(QSTileState.ActivationState.INACTIVE)
-        assertThat(state.icon()).isEqualTo(icon)
+        assertThat(state.icon).isEqualTo(icon)
         assertThat(state.secondaryLabel).isEqualTo("No active modes")
     }
 
     @Test
     fun activeState_oneMode() {
         val icon = TestStubDrawable("res123").asIcon()
-        val model =
-            ModesTileModel(
-                isActivated = true,
-                activeModes = listOf("DND"),
-                icon = icon,
-            )
+        val model = ModesTileModel(isActivated = true, activeModes = listOf("DND"), icon = icon)
 
         val state = underTest.map(config, model)
 
         assertThat(state.activationState).isEqualTo(QSTileState.ActivationState.ACTIVE)
-        assertThat(state.icon()).isEqualTo(icon)
+        assertThat(state.icon).isEqualTo(icon)
         assertThat(state.secondaryLabel).isEqualTo("DND is active")
     }
 
@@ -103,7 +93,7 @@
         val state = underTest.map(config, model)
 
         assertThat(state.activationState).isEqualTo(QSTileState.ActivationState.ACTIVE)
-        assertThat(state.icon()).isEqualTo(icon)
+        assertThat(state.icon).isEqualTo(icon)
         assertThat(state.secondaryLabel).isEqualTo("3 modes are active")
     }
 
@@ -115,12 +105,12 @@
                 isActivated = false,
                 activeModes = emptyList(),
                 icon = icon,
-                iconResId = 123
+                iconResId = 123,
             )
 
         val state = underTest.map(config, model)
 
-        assertThat(state.icon()).isEqualTo(icon)
+        assertThat(state.icon).isEqualTo(icon)
         assertThat(state.iconRes).isEqualTo(123)
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt
index 75273f2..1457f53 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt
@@ -73,7 +73,7 @@
         val expectedState =
             createNightDisplayTileState(
                 QSTileState.ActivationState.INACTIVE,
-                context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_INACTIVE]
+                context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_INACTIVE],
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -88,7 +88,7 @@
         val expectedState =
             createNightDisplayTileState(
                 QSTileState.ActivationState.INACTIVE,
-                context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_INACTIVE]
+                context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_INACTIVE],
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -102,7 +102,7 @@
         val expectedState =
             createNightDisplayTileState(
                 QSTileState.ActivationState.ACTIVE,
-                context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_ACTIVE]
+                context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_ACTIVE],
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -116,7 +116,7 @@
         val expectedState =
             createNightDisplayTileState(
                 QSTileState.ActivationState.ACTIVE,
-                context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_ACTIVE]
+                context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_ACTIVE],
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -140,7 +140,7 @@
         val expectedState =
             createNightDisplayTileState(
                 QSTileState.ActivationState.ACTIVE,
-                context.getString(R.string.quick_settings_night_secondary_label_until_sunrise)
+                context.getString(R.string.quick_settings_night_secondary_label_until_sunrise),
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -154,7 +154,7 @@
         val expectedState =
             createNightDisplayTileState(
                 QSTileState.ActivationState.INACTIVE,
-                context.getString(R.string.quick_settings_night_secondary_label_on_at_sunset)
+                context.getString(R.string.quick_settings_night_secondary_label_on_at_sunset),
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -181,8 +181,8 @@
                 QSTileState.ActivationState.INACTIVE,
                 context.getString(
                     R.string.quick_settings_night_secondary_label_on_at,
-                    formatter24Hour.format(testStartTime)
-                )
+                    formatter24Hour.format(testStartTime),
+                ),
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -199,8 +199,8 @@
                 QSTileState.ActivationState.INACTIVE,
                 context.getString(
                     R.string.quick_settings_night_secondary_label_on_at,
-                    formatter12Hour.format(testStartTime)
-                )
+                    formatter12Hour.format(testStartTime),
+                ),
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -218,8 +218,8 @@
                 QSTileState.ActivationState.INACTIVE,
                 context.getString(
                     R.string.quick_settings_night_secondary_label_on_at,
-                    formatter12Hour.format(testStartTime)
-                )
+                    formatter12Hour.format(testStartTime),
+                ),
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -235,8 +235,8 @@
                 QSTileState.ActivationState.ACTIVE,
                 context.getString(
                     R.string.quick_settings_secondary_label_until,
-                    formatter24Hour.format(testEndTime)
-                )
+                    formatter24Hour.format(testEndTime),
+                ),
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -252,8 +252,8 @@
                 QSTileState.ActivationState.ACTIVE,
                 context.getString(
                     R.string.quick_settings_secondary_label_until,
-                    formatter12Hour.format(testEndTime)
-                )
+                    formatter12Hour.format(testEndTime),
+                ),
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -270,15 +270,15 @@
                 QSTileState.ActivationState.ACTIVE,
                 context.getString(
                     R.string.quick_settings_secondary_label_until,
-                    formatter24Hour.format(testEndTime)
-                )
+                    formatter24Hour.format(testEndTime),
+                ),
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
 
     private fun createNightDisplayTileState(
         activationState: QSTileState.ActivationState,
-        secondaryLabel: String?
+        secondaryLabel: String?,
     ): QSTileState {
         val label = context.getString(R.string.quick_settings_night_display_label)
         val iconRes =
@@ -289,7 +289,7 @@
             if (TextUtils.isEmpty(secondaryLabel)) label
             else TextUtils.concat(label, ", ", secondaryLabel)
         return QSTileState(
-            { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+            Icon.Loaded(context.getDrawable(iconRes)!!, null),
             iconRes,
             label,
             activationState,
@@ -299,7 +299,7 @@
             null,
             QSTileState.SideViewIcon.None,
             QSTileState.EnabledState.ENABLED,
-            Switch::class.qualifiedName
+            Switch::class.qualifiedName,
         )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt
index 3189a9e..7782d2b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt
@@ -51,11 +51,11 @@
                     .apply {
                         addOverride(
                             com.android.internal.R.drawable.ic_qs_one_handed_mode,
-                            TestStubDrawable()
+                            TestStubDrawable(),
                         )
                     }
                     .resources,
-                context.theme
+                context.theme,
             )
     }
 
@@ -69,7 +69,7 @@
             createOneHandedModeTileState(
                 QSTileState.ActivationState.INACTIVE,
                 subtitleArray[1],
-                com.android.internal.R.drawable.ic_qs_one_handed_mode
+                com.android.internal.R.drawable.ic_qs_one_handed_mode,
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -84,7 +84,7 @@
             createOneHandedModeTileState(
                 QSTileState.ActivationState.ACTIVE,
                 subtitleArray[2],
-                com.android.internal.R.drawable.ic_qs_one_handed_mode
+                com.android.internal.R.drawable.ic_qs_one_handed_mode,
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -96,7 +96,7 @@
     ): QSTileState {
         val label = context.getString(R.string.quick_settings_onehanded_label)
         return QSTileState(
-            { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+            Icon.Loaded(context.getDrawable(iconRes)!!, null),
             iconRes,
             label,
             activationState,
@@ -106,7 +106,7 @@
             null,
             QSTileState.SideViewIcon.None,
             QSTileState.EnabledState.ENABLED,
-            Switch::class.qualifiedName
+            Switch::class.qualifiedName,
         )
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt
index 08e5cbe..ed33250 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt
@@ -49,11 +49,11 @@
                     .apply {
                         addOverride(
                             com.android.systemui.res.R.drawable.ic_qr_code_scanner,
-                            TestStubDrawable()
+                            TestStubDrawable(),
                         )
                     }
                     .resources,
-                context.theme
+                context.theme,
             )
     }
 
@@ -64,11 +64,7 @@
 
         val outputState = mapper.map(config, inputModel)
 
-        val expectedState =
-            createQRCodeScannerTileState(
-                QSTileState.ActivationState.INACTIVE,
-                null,
-            )
+        val expectedState = createQRCodeScannerTileState(QSTileState.ActivationState.INACTIVE, null)
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
 
@@ -83,7 +79,7 @@
                 QSTileState.ActivationState.UNAVAILABLE,
                 context.getString(
                     com.android.systemui.res.R.string.qr_code_scanner_updating_secondary_label
-                )
+                ),
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -94,12 +90,10 @@
     ): QSTileState {
         val label = context.getString(com.android.systemui.res.R.string.qr_code_scanner_title)
         return QSTileState(
-            {
-                Icon.Loaded(
-                    context.getDrawable(com.android.systemui.res.R.drawable.ic_qr_code_scanner)!!,
-                    null
-                )
-            },
+            Icon.Loaded(
+                context.getDrawable(com.android.systemui.res.R.drawable.ic_qr_code_scanner)!!,
+                null,
+            ),
             com.android.systemui.res.R.drawable.ic_qr_code_scanner,
             label,
             activationState,
@@ -109,7 +103,7 @@
             null,
             QSTileState.SideViewIcon.Chevron,
             QSTileState.EnabledState.ENABLED,
-            Switch::class.qualifiedName
+            Switch::class.qualifiedName,
         )
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt
index ca30e9c..85111fd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt
@@ -51,7 +51,7 @@
                         addOverride(R.drawable.qs_extra_dim_icon_off, TestStubDrawable())
                     }
                     .resources,
-                context.theme
+                context.theme,
             )
     }
 
@@ -61,10 +61,7 @@
 
         val outputState = mapper.map(config, inputModel)
 
-        val expectedState =
-            createReduceBrightColorsTileState(
-                QSTileState.ActivationState.INACTIVE,
-            )
+        val expectedState = createReduceBrightColorsTileState(QSTileState.ActivationState.INACTIVE)
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
 
@@ -79,7 +76,7 @@
     }
 
     private fun createReduceBrightColorsTileState(
-        activationState: QSTileState.ActivationState,
+        activationState: QSTileState.ActivationState
     ): QSTileState {
         val label =
             context.getString(com.android.internal.R.string.reduce_bright_colors_feature_name)
@@ -88,7 +85,7 @@
                 R.drawable.qs_extra_dim_icon_on
             else R.drawable.qs_extra_dim_icon_off
         return QSTileState(
-            { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+            Icon.Loaded(context.getDrawable(iconRes)!!, null),
             iconRes,
             label,
             activationState,
@@ -101,7 +98,7 @@
             null,
             QSTileState.SideViewIcon.None,
             QSTileState.EnabledState.ENABLED,
-            Switch::class.qualifiedName
+            Switch::class.qualifiedName,
         )
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt
index 3e40c5c..53671ba 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt
@@ -66,13 +66,13 @@
                         addOverride(com.android.internal.R.bool.config_allowRotationResolver, true)
                         addOverride(
                             com.android.internal.R.array.config_foldedDeviceStates,
-                            intArrayOf() // empty array <=> device is not foldable
+                            intArrayOf(), // empty array <=> device is not foldable
                         )
                     }
                     .resources,
                 context.theme,
                 devicePostureController,
-                deviceStateManager
+                deviceStateManager,
             )
     }
 
@@ -86,7 +86,7 @@
             createRotationLockTileState(
                 QSTileState.ActivationState.ACTIVE,
                 EMPTY_SECONDARY_STRING,
-                R.drawable.qs_auto_rotate_icon_on
+                R.drawable.qs_auto_rotate_icon_on,
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -101,7 +101,7 @@
             createRotationLockTileState(
                 QSTileState.ActivationState.ACTIVE,
                 context.getString(R.string.rotation_lock_camera_rotation_on),
-                R.drawable.qs_auto_rotate_icon_on
+                R.drawable.qs_auto_rotate_icon_on,
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -116,7 +116,7 @@
             createRotationLockTileState(
                 QSTileState.ActivationState.INACTIVE,
                 EMPTY_SECONDARY_STRING,
-                R.drawable.qs_auto_rotate_icon_off
+                R.drawable.qs_auto_rotate_icon_off,
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -167,7 +167,7 @@
         mapper.apply {
             overrideResource(
                 com.android.internal.R.array.config_foldedDeviceStates,
-                intArrayOf(1, 2, 3)
+                intArrayOf(1, 2, 3),
             )
         }
         whenever(deviceStateManager.supportedDeviceStates).thenReturn(kosmos.foldedDeviceStateList)
@@ -176,11 +176,11 @@
     private fun createRotationLockTileState(
         activationState: QSTileState.ActivationState,
         secondaryLabel: String,
-        iconRes: Int
+        iconRes: Int,
     ): QSTileState {
         val label = context.getString(R.string.quick_settings_rotation_unlocked_label)
         return QSTileState(
-            { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+            Icon.Loaded(context.getDrawable(iconRes)!!, null),
             iconRes,
             label,
             activationState,
@@ -190,7 +190,7 @@
             secondaryLabel,
             QSTileState.SideViewIcon.None,
             QSTileState.EnabledState.ENABLED,
-            Switch::class.qualifiedName
+            Switch::class.qualifiedName,
         )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt
index 9bb6141..9a45065 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt
@@ -46,7 +46,7 @@
                     addOverride(R.drawable.qs_data_saver_icon_on, TestStubDrawable())
                 }
                 .resources,
-            context.theme
+            context.theme,
         )
     }
 
@@ -59,7 +59,7 @@
         val expectedState =
             createDataSaverTileState(
                 QSTileState.ActivationState.ACTIVE,
-                R.drawable.qs_data_saver_icon_on
+                R.drawable.qs_data_saver_icon_on,
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -73,14 +73,14 @@
         val expectedState =
             createDataSaverTileState(
                 QSTileState.ActivationState.INACTIVE,
-                R.drawable.qs_data_saver_icon_off
+                R.drawable.qs_data_saver_icon_off,
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
 
     private fun createDataSaverTileState(
         activationState: QSTileState.ActivationState,
-        iconRes: Int
+        iconRes: Int,
     ): QSTileState {
         val label = context.getString(R.string.data_saver)
         val secondaryLabel =
@@ -91,7 +91,7 @@
             else context.resources.getStringArray(R.array.tile_states_saver)[0]
 
         return QSTileState(
-            { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+            Icon.Loaded(context.getDrawable(iconRes)!!, null),
             iconRes,
             label,
             activationState,
@@ -101,7 +101,7 @@
             null,
             QSTileState.SideViewIcon.None,
             QSTileState.EnabledState.ENABLED,
-            Switch::class.qualifiedName
+            Switch::class.qualifiedName,
         )
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt
index 336b566..cd683c4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt
@@ -52,7 +52,7 @@
                         addOverride(R.drawable.qs_screen_record_icon_off, TestStubDrawable())
                     }
                     .resources,
-                context.theme
+                context.theme,
             )
     }
 
@@ -82,7 +82,7 @@
             createScreenRecordTileState(
                 QSTileState.ActivationState.ACTIVE,
                 R.drawable.qs_screen_record_icon_on,
-                String.format("%d...", timeLeft)
+                String.format("%d...", timeLeft),
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -110,7 +110,7 @@
         val label = context.getString(R.string.quick_settings_screen_record_label)
 
         return QSTileState(
-            { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+            Icon.Loaded(context.getDrawable(iconRes)!!, null),
             iconRes,
             label,
             activationState,
@@ -123,7 +123,7 @@
                 QSTileState.SideViewIcon.Chevron
             else QSTileState.SideViewIcon.None,
             QSTileState.EnabledState.ENABLED,
-            Switch::class.qualifiedName
+            Switch::class.qualifiedName,
         )
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt
index b08f39b..c569403 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt
@@ -56,7 +56,7 @@
                 context.getString(R.string.quick_settings_camera_mic_available),
                 R.drawable.qs_camera_access_icon_on,
                 null,
-                CAMERA
+                CAMERA,
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -74,7 +74,7 @@
                 context.getString(R.string.quick_settings_camera_mic_blocked),
                 R.drawable.qs_camera_access_icon_off,
                 null,
-                CAMERA
+                CAMERA,
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -92,7 +92,7 @@
                 context.getString(R.string.quick_settings_camera_mic_available),
                 R.drawable.qs_mic_access_on,
                 null,
-                MICROPHONE
+                MICROPHONE,
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -110,7 +110,7 @@
                 context.getString(R.string.quick_settings_camera_mic_blocked),
                 R.drawable.qs_mic_access_off,
                 null,
-                MICROPHONE
+                MICROPHONE,
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -146,7 +146,7 @@
             else context.getString(R.string.quick_settings_mic_label)
 
         return QSTileState(
-            { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+            Icon.Loaded(context.getDrawable(iconRes)!!, null),
             iconRes,
             label,
             activationState,
@@ -156,7 +156,7 @@
             stateDescription,
             QSTileState.SideViewIcon.None,
             QSTileState.EnabledState.ENABLED,
-            Switch::class.qualifiedName
+            Switch::class.qualifiedName,
         )
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt
index c021caa..0d2ebe4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt
@@ -49,7 +49,7 @@
                     addOverride(R.drawable.qs_light_dark_theme_icon_on, TestStubDrawable())
                 }
                 .resources,
-            context.theme
+            context.theme,
         )
     }
 
@@ -69,7 +69,7 @@
         expandedAccessibilityClass: KClass<out View>? = Switch::class,
     ): QSTileState {
         return QSTileState(
-            { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+            Icon.Loaded(context.getDrawable(iconRes)!!, null),
             iconRes,
             label,
             activationState,
@@ -79,7 +79,7 @@
             stateDescription,
             sideViewIcon,
             enabledState,
-            expandedAccessibilityClass?.qualifiedName
+            expandedAccessibilityClass?.qualifiedName,
         )
     }
 
@@ -98,7 +98,7 @@
             createUiNightModeTileState(
                 activationState = QSTileState.ActivationState.UNAVAILABLE,
                 secondaryLabel = expectedSecondaryLabel,
-                contentDescription = expectedContentDescription
+                contentDescription = expectedContentDescription,
             )
         QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
     }
@@ -118,7 +118,7 @@
             createUiNightModeTileState(
                 activationState = QSTileState.ActivationState.UNAVAILABLE,
                 secondaryLabel = expectedSecondaryLabel,
-                contentDescription = expectedContentDescription
+                contentDescription = expectedContentDescription,
             )
         QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
     }
@@ -136,7 +136,7 @@
                 activationState = QSTileState.ActivationState.INACTIVE,
                 label = expectedLabel,
                 secondaryLabel = expectedSecondaryLabel,
-                contentDescription = expectedLabel
+                contentDescription = expectedLabel,
             )
         QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
     }
@@ -155,7 +155,7 @@
                 label = expectedLabel,
                 secondaryLabel = expectedSecondaryLabel,
                 activationState = QSTileState.ActivationState.ACTIVE,
-                contentDescription = expectedLabel
+                contentDescription = expectedLabel,
             )
         QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
     }
@@ -174,7 +174,7 @@
                 label = expectedLabel,
                 secondaryLabel = expectedSecondaryLabel,
                 activationState = QSTileState.ActivationState.ACTIVE,
-                contentDescription = expectedLabel
+                contentDescription = expectedLabel,
             )
         QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
     }
@@ -193,7 +193,7 @@
                 label = expectedLabel,
                 secondaryLabel = expectedSecondaryLabel,
                 activationState = QSTileState.ActivationState.INACTIVE,
-                contentDescription = expectedLabel
+                contentDescription = expectedLabel,
             )
         QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
     }
@@ -214,7 +214,7 @@
                 activationState = QSTileState.ActivationState.ACTIVE,
                 contentDescription = expectedLabel,
                 supportedActions =
-                    setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+                    setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
             )
         QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
     }
@@ -237,7 +237,7 @@
                 secondaryLabel = expectedSecondaryLabel,
                 activationState = QSTileState.ActivationState.UNAVAILABLE,
                 contentDescription = expectedContentDescription,
-                supportedActions = setOf(QSTileState.UserAction.LONG_CLICK)
+                supportedActions = setOf(QSTileState.UserAction.LONG_CLICK),
             )
         QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
     }
@@ -258,7 +258,7 @@
                 activationState = QSTileState.ActivationState.INACTIVE,
                 contentDescription = expectedLabel,
                 supportedActions =
-                    setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+                    setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
             )
         QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
     }
@@ -279,7 +279,7 @@
                 secondaryLabel = expectedSecondaryLabel,
                 activationState = QSTileState.ActivationState.UNAVAILABLE,
                 contentDescription = TextUtils.concat(expectedLabel, ", ", expectedSecondaryLabel),
-                supportedActions = setOf(QSTileState.UserAction.LONG_CLICK)
+                supportedActions = setOf(QSTileState.UserAction.LONG_CLICK),
             )
         QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
     }
@@ -300,7 +300,7 @@
                 secondaryLabel = expectedSecondaryLabel,
                 activationState = QSTileState.ActivationState.UNAVAILABLE,
                 contentDescription = TextUtils.concat(expectedLabel, ", ", expectedSecondaryLabel),
-                supportedActions = setOf(QSTileState.UserAction.LONG_CLICK)
+                supportedActions = setOf(QSTileState.UserAction.LONG_CLICK),
             )
         QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
     }
@@ -312,7 +312,7 @@
                 nightMode = true,
                 powerSave = false,
                 isLocationEnabled = true,
-                uiMode = UiModeManager.MODE_NIGHT_AUTO
+                uiMode = UiModeManager.MODE_NIGHT_AUTO,
             )
 
         val actualState: QSTileState = mapper.map(qsTileConfig, inputModel)
@@ -328,7 +328,7 @@
                 activationState = QSTileState.ActivationState.ACTIVE,
                 contentDescription = TextUtils.concat(expectedLabel, ", ", expectedSecondaryLabel),
                 supportedActions =
-                    setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+                    setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
             )
         QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
     }
@@ -340,7 +340,7 @@
                 nightMode = false,
                 powerSave = false,
                 isLocationEnabled = true,
-                uiMode = UiModeManager.MODE_NIGHT_AUTO
+                uiMode = UiModeManager.MODE_NIGHT_AUTO,
             )
 
         val actualState: QSTileState = mapper.map(qsTileConfig, inputModel)
@@ -356,7 +356,7 @@
                 activationState = QSTileState.ActivationState.INACTIVE,
                 contentDescription = TextUtils.concat(expectedLabel, ", ", expectedSecondaryLabel),
                 supportedActions =
-                    setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+                    setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
             )
         QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
     }
@@ -379,7 +379,7 @@
                 activationState = QSTileState.ActivationState.ACTIVE,
                 contentDescription = expectedLabel,
                 supportedActions =
-                    setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+                    setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
             )
         QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
     }
@@ -401,7 +401,7 @@
                 activationState = QSTileState.ActivationState.INACTIVE,
                 contentDescription = expectedLabel,
                 supportedActions =
-                    setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+                    setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
             )
         QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
     }
@@ -413,7 +413,7 @@
                 nightMode = false,
                 powerSave = false,
                 uiMode = UiModeManager.MODE_NIGHT_CUSTOM,
-                nighModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN
+                nighModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN,
             )
 
         val actualState: QSTileState = mapper.map(qsTileConfig, inputModel)
@@ -428,7 +428,7 @@
                 activationState = QSTileState.ActivationState.INACTIVE,
                 contentDescription = expectedLabel,
                 supportedActions =
-                    setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+                    setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
             )
         QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
     }
@@ -440,7 +440,7 @@
                 nightMode = true,
                 powerSave = false,
                 uiMode = UiModeManager.MODE_NIGHT_CUSTOM,
-                nighModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN
+                nighModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN,
             )
 
         val actualState: QSTileState = mapper.map(qsTileConfig, inputModel)
@@ -455,7 +455,7 @@
                 activationState = QSTileState.ActivationState.ACTIVE,
                 contentDescription = expectedLabel,
                 supportedActions =
-                    setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+                    setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
             )
         QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
     }
@@ -467,7 +467,7 @@
                 nightMode = false,
                 powerSave = true,
                 uiMode = UiModeManager.MODE_NIGHT_CUSTOM,
-                nighModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN
+                nighModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN,
             )
 
         val actualState: QSTileState = mapper.map(qsTileConfig, inputModel)
@@ -484,7 +484,7 @@
                 secondaryLabel = expectedSecondaryLabel,
                 activationState = QSTileState.ActivationState.UNAVAILABLE,
                 contentDescription = expectedContentDescription,
-                supportedActions = setOf(QSTileState.UserAction.LONG_CLICK)
+                supportedActions = setOf(QSTileState.UserAction.LONG_CLICK),
             )
         QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt
index e7bde681..86321ea 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt
@@ -56,7 +56,7 @@
         whenever(
                 devicePolicyResourceManager.getString(
                     eq(DevicePolicyResources.Strings.SystemUi.QS_WORK_PROFILE_LABEL),
-                    any()
+                    any(),
                 )
             )
             .thenReturn(testLabel)
@@ -66,12 +66,12 @@
                     .apply {
                         addOverride(
                             com.android.internal.R.drawable.stat_sys_managed_profile_status,
-                            TestStubDrawable()
+                            TestStubDrawable(),
                         )
                     }
                     .resources,
                 context.theme,
-                devicePolicyManager
+                devicePolicyManager,
             )
     }
 
@@ -105,13 +105,11 @@
         QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
     }
 
-    private fun createWorkModeTileState(
-        activationState: QSTileState.ActivationState,
-    ): QSTileState {
+    private fun createWorkModeTileState(activationState: QSTileState.ActivationState): QSTileState {
         val label = testLabel
         val iconRes = com.android.internal.R.drawable.stat_sys_managed_profile_status
         return QSTileState(
-            icon = { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+            icon = Icon.Loaded(context.getDrawable(iconRes)!!, null),
             iconRes = iconRes,
             label = label,
             activationState = activationState,
@@ -134,7 +132,7 @@
             stateDescription = null,
             sideViewIcon = QSTileState.SideViewIcon.None,
             enabledState = QSTileState.EnabledState.ENABLED,
-            expandedAccessibilityClassName = Switch::class.qualifiedName
+            expandedAccessibilityClassName = Switch::class.qualifiedName,
         )
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt
index c33e2a4..954215ee 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt
@@ -184,10 +184,7 @@
             {
                 object : QSTileDataToStateMapper<String> {
                     override fun map(config: QSTileConfig, data: String): QSTileState =
-                        QSTileState.build(
-                            { Icon.Resource(0, ContentDescription.Resource(0)) },
-                            data
-                        ) {}
+                        QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), data) {}
                 }
             },
             disabledByPolicyInteractor,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt
index 7955f2f..0219a4c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt
@@ -104,7 +104,7 @@
                     eq(tileConfig.tileSpec),
                     eq(userAction),
                     any(),
-                    eq("initial_data")
+                    eq("initial_data"),
                 )
             verify(qsTileAnalytics).trackUserAction(eq(tileConfig), eq(userAction))
         }
@@ -130,7 +130,7 @@
                 .logUserActionRejectedByPolicy(
                     eq(userAction),
                     eq(tileConfig.tileSpec),
-                    eq(DISABLED_RESTRICTION)
+                    eq(DISABLED_RESTRICTION),
                 )
             verify(qsTileAnalytics, never()).trackUserAction(any(), any())
         }
@@ -159,7 +159,7 @@
                 .logUserActionRejectedByPolicy(
                     eq(userAction),
                     eq(tileConfig.tileSpec),
-                    eq(DISABLED_RESTRICTION)
+                    eq(DISABLED_RESTRICTION),
                 )
             verify(qsTileAnalytics, never()).trackUserAction(any(), any())
         }
@@ -174,7 +174,7 @@
                         QSTilePolicy.Restricted(
                             listOf(
                                 DISABLED_RESTRICTION,
-                                FakeDisabledByPolicyInteractor.DISABLED_RESTRICTION_2
+                                FakeDisabledByPolicyInteractor.DISABLED_RESTRICTION_2,
                             )
                         )
                 }
@@ -194,13 +194,13 @@
                 .logUserActionRejectedByPolicy(
                     eq(userAction),
                     eq(tileConfig.tileSpec),
-                    eq(DISABLED_RESTRICTION)
+                    eq(DISABLED_RESTRICTION),
                 )
             verify(qsTileLogger, never())
                 .logUserActionRejectedByPolicy(
                     eq(userAction),
                     eq(tileConfig.tileSpec),
-                    eq(FakeDisabledByPolicyInteractor.DISABLED_RESTRICTION_2)
+                    eq(FakeDisabledByPolicyInteractor.DISABLED_RESTRICTION_2),
                 )
             verify(qsTileAnalytics, never()).trackUserAction(any(), any())
         }
@@ -243,10 +243,7 @@
             {
                 object : QSTileDataToStateMapper<String> {
                     override fun map(config: QSTileConfig, data: String): QSTileState =
-                        QSTileState.build(
-                            { Icon.Resource(0, ContentDescription.Resource(0)) },
-                            data
-                        ) {}
+                        QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), data) {}
                 }
             },
             disabledByPolicyInteractor,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
index 22913f1..8769022 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
@@ -26,7 +26,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.domain.interactor.displayStateInteractor
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
-import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.display.data.repository.displayStateRepository
 import com.android.systemui.dump.DumpManager
@@ -101,7 +101,7 @@
 
     private val fakeConfigurationRepository =
         FakeConfigurationRepository().apply { onConfigurationChange(configuration) }
-    private val configurationInteractor = ConfigurationInteractor(fakeConfigurationRepository)
+    private val configurationInteractor = ConfigurationInteractorImpl(fakeConfigurationRepository)
 
     private val mockAsyncLayoutInflater =
         mock<AsyncLayoutInflater>() {
@@ -151,10 +151,7 @@
             inOrder.verify(qsImpl!!).onCreate(nullable())
             inOrder
                 .verify(qsImpl!!)
-                .onComponentCreated(
-                    eq(qsSceneComponentFactory.components[0]),
-                    any(),
-                )
+                .onComponentCreated(eq(qsSceneComponentFactory.components[0]), any())
         }
 
     @Test
@@ -422,10 +419,7 @@
             inOrder.verify(newQSImpl).onCreate(nullable())
             inOrder
                 .verify(newQSImpl)
-                .onComponentCreated(
-                    qsSceneComponentFactory.components[1],
-                    bundleArgCaptor.value,
-                )
+                .onComponentCreated(qsSceneComponentFactory.components[1], bundleArgCaptor.value)
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 319f1e5..3be8a38 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -44,6 +44,7 @@
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.ui.viewmodel.lockscreenUserActionsViewModel
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.collectLastValue
 import com.android.systemui.kosmos.runCurrent
 import com.android.systemui.kosmos.runTest
 import com.android.systemui.kosmos.testScope
@@ -153,7 +154,7 @@
     @Test
     fun swipeUpOnLockscreen_enterCorrectPin_unlocksDevice() =
         kosmos.runTest {
-            val actions by testScope.collectLastValue(kosmos.lockscreenUserActionsViewModel.actions)
+            val actions by collectLastValue(kosmos.lockscreenUserActionsViewModel.actions)
             val upDestinationSceneKey =
                 (actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
             assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
@@ -170,7 +171,7 @@
         kosmos.runTest {
             setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
 
-            val actions by testScope.collectLastValue(lockscreenUserActionsViewModel.actions)
+            val actions by collectLastValue(lockscreenUserActionsViewModel.actions)
             val upDestinationSceneKey =
                 (actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
             assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone)
@@ -180,7 +181,7 @@
     @Test
     fun swipeUpOnShadeScene_withAuthMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
         kosmos.runTest {
-            val actions by testScope.collectLastValue(shadeUserActionsViewModel.actions)
+            val actions by collectLastValue(shadeUserActionsViewModel.actions)
             setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
             assertCurrentScene(Scenes.Lockscreen)
 
@@ -197,8 +198,8 @@
     @Test
     fun swipeUpOnShadeScene_withAuthMethodSwipe_lockscreenDismissed_goesToGone() =
         kosmos.runTest {
-            val actions by testScope.collectLastValue(shadeUserActionsViewModel.actions)
-            val canSwipeToEnter by testScope.collectLastValue(deviceEntryInteractor.canSwipeToEnter)
+            val actions by collectLastValue(shadeUserActionsViewModel.actions)
+            val canSwipeToEnter by collectLastValue(deviceEntryInteractor.canSwipeToEnter)
 
             setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
 
@@ -279,7 +280,7 @@
     fun swipeUpOnLockscreenWhileUnlocked_dismissesLockscreen() =
         kosmos.runTest {
             unlockDevice()
-            val actions by testScope.collectLastValue(lockscreenUserActionsViewModel.actions)
+            val actions by collectLastValue(lockscreenUserActionsViewModel.actions)
             val upDestinationSceneKey =
                 (actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
             assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone)
@@ -302,7 +303,7 @@
     fun dismissingIme_whileOnPasswordBouncer_navigatesToLockscreen() =
         kosmos.runTest {
             setAuthMethod(AuthenticationMethodModel.Password)
-            val actions by testScope.collectLastValue(lockscreenUserActionsViewModel.actions)
+            val actions by collectLastValue(lockscreenUserActionsViewModel.actions)
             val upDestinationSceneKey =
                 (actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
             assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
@@ -319,14 +320,13 @@
     fun bouncerActionButtonClick_opensEmergencyServicesDialer() =
         kosmos.runTest {
             setAuthMethod(AuthenticationMethodModel.Password)
-            val actions by testScope.collectLastValue(lockscreenUserActionsViewModel.actions)
+            val actions by collectLastValue(lockscreenUserActionsViewModel.actions)
             val upDestinationSceneKey =
                 (actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
             assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
             emulateUserDrivenTransition(to = upDestinationSceneKey)
 
-            val bouncerActionButton by
-                testScope.collectLastValue(bouncerSceneContentViewModel.actionButton)
+            val bouncerActionButton by collectLastValue(bouncerSceneContentViewModel.actionButton)
             assertWithMessage("Bouncer action button not visible")
                 .that(bouncerActionButton)
                 .isNotNull()
@@ -341,14 +341,13 @@
         kosmos.runTest {
             setAuthMethod(AuthenticationMethodModel.Password)
             startPhoneCall()
-            val actions by testScope.collectLastValue(lockscreenUserActionsViewModel.actions)
+            val actions by collectLastValue(lockscreenUserActionsViewModel.actions)
             val upDestinationSceneKey =
                 (actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
             assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
             emulateUserDrivenTransition(to = upDestinationSceneKey)
 
-            val bouncerActionButton by
-                testScope.collectLastValue(bouncerSceneContentViewModel.actionButton)
+            val bouncerActionButton by collectLastValue(bouncerSceneContentViewModel.actionButton)
             assertWithMessage("Bouncer action button not visible during call")
                 .that(bouncerActionButton)
                 .isNotNull()
@@ -574,7 +573,7 @@
             .that(getCurrentSceneInUi())
             .isEqualTo(Scenes.Bouncer)
         val authMethodViewModel by
-            testScope.collectLastValue(bouncerSceneContentViewModel.authMethodViewModel)
+            collectLastValue(bouncerSceneContentViewModel.authMethodViewModel)
         assertWithMessage("Cannot enter PIN when not using a PIN authentication method!")
             .that(authMethodViewModel)
             .isInstanceOf(PinBouncerViewModel::class.java)
@@ -603,7 +602,7 @@
             .that(getCurrentSceneInUi())
             .isEqualTo(Scenes.Bouncer)
         val authMethodViewModel by
-            testScope.collectLastValue(bouncerSceneContentViewModel.authMethodViewModel)
+            collectLastValue(bouncerSceneContentViewModel.authMethodViewModel)
         assertWithMessage("Cannot enter PIN when not using a PIN authentication method!")
             .that(authMethodViewModel)
             .isInstanceOf(PinBouncerViewModel::class.java)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
index bfe5ef7..db2297c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
@@ -32,7 +32,7 @@
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory
 import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
 import com.android.systemui.statusbar.NotificationPresenter
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
 import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
 import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
 import com.android.systemui.statusbar.notification.init.NotificationsController
@@ -69,7 +69,7 @@
     private val notificationPresenter = mock<NotificationPresenter>()
     private val notificationsController = mock<NotificationsController>()
     private val powerInteractor = PowerInteractorFactory.create().powerInteractor
-    private val activeNotificationsRepository = ActiveNotificationListRepository()
+    private val activeNotificationsRepository = kosmos.activeNotificationListRepository
     private val activeNotificationsInteractor =
         ActiveNotificationsInteractor(activeNotificationsRepository, testDispatcher)
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 2c8f7cf..55f88cc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -2119,40 +2119,6 @@
         }
 
     @Test
-    fun switchToGone_whenSurfaceBehindLockscreenVisibleMidTransition() =
-        testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
-            val transitionStateFlow =
-                prepareState(authenticationMethod = AuthenticationMethodModel.None)
-            underTest.start()
-            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
-            // Swipe to Gone, more than halfway
-            transitionStateFlow.value =
-                ObservableTransitionState.Transition(
-                    fromScene = Scenes.Lockscreen,
-                    toScene = Scenes.Gone,
-                    currentScene = flowOf(Scenes.Gone),
-                    progress = flowOf(0.51f),
-                    isInitiatedByUserInput = true,
-                    isUserInputOngoing = flowOf(true),
-                )
-            runCurrent()
-            // Lift finger
-            transitionStateFlow.value =
-                ObservableTransitionState.Transition(
-                    fromScene = Scenes.Lockscreen,
-                    toScene = Scenes.Gone,
-                    currentScene = flowOf(Scenes.Gone),
-                    progress = flowOf(0.51f),
-                    isInitiatedByUserInput = true,
-                    isUserInputOngoing = flowOf(false),
-                )
-            runCurrent()
-
-            assertThat(currentScene).isEqualTo(Scenes.Gone)
-        }
-
-    @Test
     fun switchToGone_extendUnlock() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.currentScene)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/data/model/DisplayContentScenarios.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/data/model/DisplayContentScenarios.kt
index 4d71dc4..4871564 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/data/model/DisplayContentScenarios.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/data/model/DisplayContentScenarios.kt
@@ -18,6 +18,8 @@
 
 import android.content.ComponentName
 import android.graphics.Rect
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.FREEFORM_FULL_SCREEN
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.FREEFORM_MAXIMIZED
 import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.FREE_FORM
 import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.FULL_SCREEN
 import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.PIP
@@ -153,11 +155,23 @@
     fun freeFormApps(
         vararg tasks: TaskSpec,
         focusedTaskId: Int,
+        maximizedTaskId: Int = -1,
         shadeExpanded: Boolean = false,
     ): DisplayContentModel {
         val freeFormTasks =
             tasks
-                .map { freeForm(it) }
+                .map {
+                    freeForm(
+                        task = it,
+                        bounds =
+                            if (it.taskId == maximizedTaskId) {
+                                FREEFORM_MAXIMIZED
+                            } else {
+                                FREE_FORM
+                            },
+                        maxBounds = FREEFORM_FULL_SCREEN,
+                    )
+                }
                 // Root tasks are ordered top-down in List<RootTaskInfo>.
                 // Sort 'focusedTaskId' last (Boolean natural ordering: [false, true])
                 .sortedBy { it.childTaskIds[0] != focusedTaskId }
@@ -180,9 +194,9 @@
         val PIP = Rect(440, 1458, 1038, 1794)
         val SPLIT_TOP = Rect(0, 0, 1080, 1187)
         val SPLIT_BOTTOM = Rect(0, 1213, 1080, 2400)
-        val FREE_FORM = Rect(119, 332, 1000, 1367)
 
         // "Tablet" size
+        val FREE_FORM = Rect(119, 332, 1000, 1367)
         val FREEFORM_FULL_SCREEN = Rect(0, 0, 2560, 1600)
         val FREEFORM_MAXIMIZED = Rect(0, 48, 2560, 1480)
         val FREEFORM_SPLIT_LEFT = Rect(0, 0, 1270, 1600)
@@ -301,11 +315,12 @@
             }
 
         /** An activity in FreeForm mode */
-        fun freeForm(task: TaskSpec, bounds: Rect = FREE_FORM) =
+        fun freeForm(task: TaskSpec, bounds: Rect = FREE_FORM, maxBounds: Rect = bounds) =
             newRootTaskInfo(
                 taskId = task.taskId,
                 userId = task.userId,
                 bounds = bounds,
+                maxBounds = maxBounds,
                 windowingMode = WindowingMode.Freeform,
                 topActivity = ComponentName.unflattenFromString(task.name),
             ) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/NewRootTaskInfo.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/NewRootTaskInfo.kt
index cedf0c8..4f6871e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/NewRootTaskInfo.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/NewRootTaskInfo.kt
@@ -84,6 +84,7 @@
     activityType: ActivityType = Standard,
     windowingMode: WindowingMode = FullScreen,
     bounds: Rect = Rect(),
+    maxBounds: Rect = bounds,
     topActivity: ComponentName? = null,
     topActivityType: ActivityType = Standard,
     numActivities: Int? = null,
@@ -94,6 +95,7 @@
             setWindowingMode(windowingMode.toInt())
             setActivityType(activityType.toInt())
             setBounds(bounds)
+            setMaxBounds(maxBounds)
         }
         this.bounds = bounds
         this.displayId = displayId
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/PrivateProfilePolicyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/PrivateProfilePolicyTest.kt
index b7f565d..c884b9a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/PrivateProfilePolicyTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/PrivateProfilePolicyTest.kt
@@ -90,9 +90,11 @@
                 Matched(
                     PrivateProfilePolicy.NAME,
                     PrivateProfilePolicy.PRIVATE_TASK_VISIBLE,
-                    CaptureParameters(
+                    LegacyCaptureParameters(
                         type = FullScreen(displayId = 0),
-                        component = ComponentName.unflattenFromString(YOUTUBE),
+                        component =
+                            ComponentName.unflattenFromString(YOUTUBE)
+                                ?: error("Invalid component name"),
                         owner = UserHandle.of(PRIVATE),
                     ),
                 )
@@ -142,7 +144,7 @@
                 Matched(
                     PrivateProfilePolicy.NAME,
                     PrivateProfilePolicy.PRIVATE_TASK_VISIBLE,
-                    CaptureParameters(
+                    LegacyCaptureParameters(
                         type = FullScreen(displayId = 0),
                         component = ComponentName.unflattenFromString(YOUTUBE),
                         owner = UserHandle.of(PRIVATE),
@@ -167,7 +169,7 @@
                 Matched(
                     PrivateProfilePolicy.NAME,
                     PrivateProfilePolicy.PRIVATE_TASK_VISIBLE,
-                    CaptureParameters(
+                    LegacyCaptureParameters(
                         type = FullScreen(displayId = 0),
                         component = ComponentName.unflattenFromString(FILES),
                         owner = UserHandle.of(PRIVATE),
@@ -188,7 +190,7 @@
                 Matched(
                     PrivateProfilePolicy.NAME,
                     PrivateProfilePolicy.PRIVATE_TASK_VISIBLE,
-                    CaptureParameters(
+                    LegacyCaptureParameters(
                         type = FullScreen(displayId = 0),
                         component = ComponentName.unflattenFromString(YOUTUBE_PIP),
                         owner = UserHandle.of(PRIVATE),
@@ -212,7 +214,7 @@
                 Matched(
                     PrivateProfilePolicy.NAME,
                     PrivateProfilePolicy.PRIVATE_TASK_VISIBLE,
-                    CaptureParameters(
+                    LegacyCaptureParameters(
                         type = FullScreen(displayId = 0),
                         component = ComponentName.unflattenFromString(YOUTUBE_PIP),
                         owner = UserHandle.of(PRIVATE),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/ScreenshotPolicyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/ScreenshotPolicyTest.kt
index 28eb9fc..948c24e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/ScreenshotPolicyTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/ScreenshotPolicyTest.kt
@@ -17,11 +17,11 @@
 package com.android.systemui.screenshot.policy
 
 import android.content.ComponentName
+import android.graphics.Rect
 import android.os.UserHandle
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.FILES
-import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.LAUNCHER
 import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.MESSAGES
 import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.YOUTUBE
 import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.FREEFORM_FULL_SCREEN
@@ -32,10 +32,10 @@
 import com.android.systemui.screenshot.data.model.DisplayContentScenarios.pictureInPictureApp
 import com.android.systemui.screenshot.data.model.DisplayContentScenarios.singleFullScreen
 import com.android.systemui.screenshot.data.model.DisplayContentScenarios.splitScreenApps
+import com.android.systemui.screenshot.data.model.allTasks
 import com.android.systemui.screenshot.data.repository.profileTypeRepository
 import com.android.systemui.screenshot.policy.CaptureType.FullScreen
 import com.android.systemui.screenshot.policy.CaptureType.IsolatedTask
-import com.android.systemui.screenshot.policy.CaptureType.RootTask
 import com.android.systemui.screenshot.policy.TestUserIds.PERSONAL
 import com.android.systemui.screenshot.policy.TestUserIds.PRIVATE
 import com.android.systemui.screenshot.policy.TestUserIds.WORK
@@ -50,69 +50,81 @@
 
     private val defaultComponent = ComponentName("default", "default")
     private val defaultOwner = UserHandle.SYSTEM
+    private val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
 
     @Test
     fun fullScreen_work() = runTest {
-        val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
+        val displayContent = singleFullScreen(TaskSpec(taskId = 1002, name = FILES, userId = WORK))
+        val expectedFocusedTask =
+            displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1002 }
 
-        val result =
-            policy.apply(
-                singleFullScreen(TaskSpec(taskId = 1002, name = FILES, userId = WORK)),
-                defaultComponent,
-                defaultOwner,
-            )
+        val result = policy.apply(displayContent, defaultComponent, defaultOwner)
 
         assertThat(result)
             .isEqualTo(
                 CaptureParameters(
                     type = IsolatedTask(taskId = 1002, taskBounds = FULL_SCREEN),
-                    component = ComponentName.unflattenFromString(FILES),
-                    owner = UserHandle.of(WORK),
+                    contentTask =
+                        TaskReference(
+                            taskId = expectedFocusedTask.id,
+                            component = expectedFocusedTask.componentName,
+                            owner = UserHandle.of(expectedFocusedTask.userId),
+                            bounds = expectedFocusedTask.bounds,
+                        ),
+                    owner = UserHandle.of(expectedFocusedTask.userId),
                 )
             )
     }
 
     @Test
     fun fullScreen_private() = runTest {
-        val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
+        val displayContent =
+            singleFullScreen(TaskSpec(taskId = 1002, name = YOUTUBE, userId = PRIVATE))
+        val expectedFocusedTask =
+            displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1002 }
 
-        val result =
-            policy.apply(
-                singleFullScreen(TaskSpec(taskId = 1002, name = YOUTUBE, userId = PRIVATE)),
-                defaultComponent,
-                defaultOwner,
-            )
+        val result = policy.apply(displayContent, defaultComponent, defaultOwner)
 
         assertThat(result)
             .isEqualTo(
                 CaptureParameters(
                     type = FullScreen(displayId = 0),
-                    component = ComponentName.unflattenFromString(YOUTUBE),
-                    owner = UserHandle.of(PRIVATE),
+                    contentTask =
+                        TaskReference(
+                            taskId = expectedFocusedTask.id,
+                            component = expectedFocusedTask.componentName,
+                            owner = UserHandle.of(expectedFocusedTask.userId),
+                            bounds = expectedFocusedTask.bounds,
+                        ),
+                    owner = UserHandle.of(expectedFocusedTask.userId),
                 )
             )
     }
 
     @Test
     fun splitScreen_workAndPersonal() = runTest {
-        val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
-
-        val result =
-            policy.apply(
-                splitScreenApps(
-                    first = TaskSpec(taskId = 1002, name = FILES, userId = WORK),
-                    second = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PERSONAL),
-                    focusedTaskId = 1002,
-                ),
-                defaultComponent,
-                defaultOwner,
+        val displayContent =
+            splitScreenApps(
+                first = TaskSpec(taskId = 1002, name = FILES, userId = WORK),
+                second = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PERSONAL),
+                focusedTaskId = 1002,
             )
+        val expectedFocusedTask =
+            displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1002 }
+
+        val result = policy.apply(displayContent, defaultComponent, defaultOwner)
 
         assertThat(result)
             .isEqualTo(
                 CaptureParameters(
                     type = FullScreen(displayId = 0),
-                    component = ComponentName.unflattenFromString(YOUTUBE),
+                    contentTask =
+                        TaskReference(
+                            taskId = expectedFocusedTask.id,
+                            component = expectedFocusedTask.componentName,
+                            owner = UserHandle.of(expectedFocusedTask.userId),
+                            bounds = expectedFocusedTask.bounds,
+                        ),
                     owner = UserHandle.of(PERSONAL),
                 )
             )
@@ -120,24 +132,28 @@
 
     @Test
     fun splitScreen_personalAndPrivate() = runTest {
-        val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
-
-        val result =
-            policy.apply(
-                splitScreenApps(
-                    first = TaskSpec(taskId = 1002, name = FILES, userId = PERSONAL),
-                    second = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PRIVATE),
-                    focusedTaskId = 1002,
-                ),
-                defaultComponent,
-                defaultOwner,
+        val displayContent =
+            splitScreenApps(
+                first = TaskSpec(taskId = 1002, name = FILES, userId = PERSONAL),
+                second = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PRIVATE),
+                focusedTaskId = 1002,
             )
+        val expectedFocusedTask =
+            displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1002 }
+
+        val result = policy.apply(displayContent, defaultComponent, defaultOwner)
 
         assertThat(result)
             .isEqualTo(
                 CaptureParameters(
                     type = FullScreen(displayId = 0),
-                    component = ComponentName.unflattenFromString(YOUTUBE),
+                    contentTask =
+                        TaskReference(
+                            taskId = expectedFocusedTask.id,
+                            component = expectedFocusedTask.componentName,
+                            owner = UserHandle.of(expectedFocusedTask.userId),
+                            bounds = expectedFocusedTask.bounds,
+                        ),
                     owner = UserHandle.of(PRIVATE),
                 )
             )
@@ -145,24 +161,28 @@
 
     @Test
     fun splitScreen_workAndPrivate() = runTest {
-        val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
-
-        val result =
-            policy.apply(
-                splitScreenApps(
-                    first = TaskSpec(taskId = 1002, name = FILES, userId = WORK),
-                    second = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PRIVATE),
-                    focusedTaskId = 1002,
-                ),
-                defaultComponent,
-                defaultOwner,
+        val displayContent =
+            splitScreenApps(
+                first = TaskSpec(taskId = 1002, name = FILES, userId = WORK),
+                second = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PRIVATE),
+                focusedTaskId = 1002,
             )
+        val expectedFocusedTask =
+            displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1002 }
+
+        val result = policy.apply(displayContent, defaultComponent, defaultOwner)
 
         assertThat(result)
             .isEqualTo(
                 CaptureParameters(
                     type = FullScreen(displayId = 0),
-                    component = ComponentName.unflattenFromString(YOUTUBE),
+                    contentTask =
+                        TaskReference(
+                            taskId = expectedFocusedTask.id,
+                            component = expectedFocusedTask.componentName,
+                            owner = UserHandle.of(expectedFocusedTask.userId),
+                            bounds = expectedFocusedTask.bounds,
+                        ),
                     owner = UserHandle.of(PRIVATE),
                 )
             )
@@ -170,32 +190,31 @@
 
     @Test
     fun splitScreen_twoWorkTasks() = runTest {
-        val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
-
-        val result =
-            policy.apply(
-                splitScreenApps(
-                    parentTaskId = 1,
-                    parentBounds = FREEFORM_FULL_SCREEN,
-                    orientation = VERTICAL,
-                    first = TaskSpec(taskId = 1002, name = FILES, userId = WORK),
-                    second = TaskSpec(taskId = 1003, name = YOUTUBE, userId = WORK),
-                    focusedTaskId = 1002,
-                ),
-                defaultComponent,
-                defaultOwner,
+        val displayContent =
+            splitScreenApps(
+                parentTaskId = 1,
+                parentBounds = FREEFORM_FULL_SCREEN,
+                orientation = VERTICAL,
+                first = TaskSpec(taskId = 1002, name = FILES, userId = WORK),
+                second = TaskSpec(taskId = 1003, name = YOUTUBE, userId = WORK),
+                focusedTaskId = 1002,
             )
+        val expectedFocusedTask =
+            displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1002 }
+
+        val result = policy.apply(displayContent, defaultComponent, defaultOwner)
 
         assertThat(result)
             .isEqualTo(
                 CaptureParameters(
-                    type =
-                        RootTask(
-                            parentTaskId = 1,
-                            taskBounds = FREEFORM_FULL_SCREEN,
-                            childTaskIds = listOf(1002, 1003),
+                    type = IsolatedTask(taskBounds = FREEFORM_FULL_SCREEN, taskId = 1),
+                    contentTask =
+                        TaskReference(
+                            taskId = expectedFocusedTask.id,
+                            component = expectedFocusedTask.componentName,
+                            owner = UserHandle.of(expectedFocusedTask.userId),
+                            bounds = expectedFocusedTask.bounds,
                         ),
-                    component = ComponentName.unflattenFromString(FILES),
                     owner = UserHandle.of(WORK),
                 )
             )
@@ -203,99 +222,112 @@
 
     @Test
     fun freeform_floatingWindows() = runTest {
-        val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
-
-        val result =
-            policy.apply(
-                freeFormApps(
-                    TaskSpec(taskId = 1002, name = FILES, userId = WORK),
-                    TaskSpec(taskId = 1003, name = YOUTUBE, userId = PERSONAL),
-                    focusedTaskId = 1003,
-                ),
-                defaultComponent,
-                defaultOwner,
+        val displayContent =
+            freeFormApps(
+                TaskSpec(taskId = 1002, name = FILES, userId = WORK),
+                TaskSpec(taskId = 1003, name = YOUTUBE, userId = PERSONAL),
+                focusedTaskId = 1003,
             )
+        val expectedFocusedTask =
+            displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1003 }
+
+        val result = policy.apply(displayContent, defaultComponent, defaultOwner)
 
         assertThat(result)
             .isEqualTo(
                 CaptureParameters(
                     type = FullScreen(displayId = 0),
-                    component = ComponentName.unflattenFromString(YOUTUBE),
+                    contentTask =
+                        TaskReference(
+                            taskId = expectedFocusedTask.id,
+                            component = expectedFocusedTask.componentName,
+                            owner = UserHandle.of(expectedFocusedTask.userId),
+                            bounds = expectedFocusedTask.bounds,
+                        ),
                     owner = UserHandle.of(PERSONAL),
                 )
             )
     }
 
     @Test
-    fun freeform_floatingWindows_maximized() = runTest {
-        val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
-
-        val result =
-            policy.apply(
-                freeFormApps(
-                    TaskSpec(taskId = 1002, name = FILES, userId = WORK),
-                    TaskSpec(taskId = 1003, name = YOUTUBE, userId = PERSONAL),
-                    focusedTaskId = 1003,
-                ),
-                defaultComponent,
-                defaultOwner,
+    fun freeform_floatingWindows_work_maximized() = runTest {
+        val displayContent =
+            freeFormApps(
+                TaskSpec(taskId = 1002, name = FILES, userId = WORK),
+                TaskSpec(taskId = 1003, name = YOUTUBE, userId = PERSONAL),
+                focusedTaskId = 1002,
+                maximizedTaskId = 1002,
             )
+        val expectedFocusedTask =
+            displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1002 }
+
+        val result = policy.apply(displayContent, defaultComponent, defaultOwner)
 
         assertThat(result)
             .isEqualTo(
                 CaptureParameters(
-                    type = FullScreen(displayId = 0),
-                    component = ComponentName.unflattenFromString(YOUTUBE),
-                    owner = UserHandle.of(PERSONAL),
+                    type = IsolatedTask(taskId = 1002, taskBounds = expectedFocusedTask.bounds),
+                    contentTask =
+                        TaskReference(
+                            taskId = expectedFocusedTask.id,
+                            component = expectedFocusedTask.componentName,
+                            owner = UserHandle.of(expectedFocusedTask.userId),
+                            bounds = expectedFocusedTask.bounds,
+                        ),
+                    owner = UserHandle.of(WORK),
                 )
             )
     }
 
     @Test
     fun freeform_floatingWindows_withPrivate() = runTest {
-        val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
-
-        val result =
-            policy.apply(
-                freeFormApps(
-                    TaskSpec(taskId = 1002, name = FILES, userId = WORK),
-                    TaskSpec(taskId = 1003, name = YOUTUBE, userId = PRIVATE),
-                    TaskSpec(taskId = 1004, name = MESSAGES, userId = PERSONAL),
-                    focusedTaskId = 1004,
-                ),
-                defaultComponent,
-                defaultOwner,
+        val displayContent =
+            freeFormApps(
+                TaskSpec(taskId = 1002, name = FILES, userId = WORK),
+                TaskSpec(taskId = 1003, name = YOUTUBE, userId = PRIVATE),
+                TaskSpec(taskId = 1004, name = MESSAGES, userId = PERSONAL),
+                focusedTaskId = 1004,
             )
+        val expectedFocusedTask = displayContent.allTasks().single { it.id == 1004 }
+
+        val result = policy.apply(displayContent, defaultComponent, defaultOwner)
 
         assertThat(result)
             .isEqualTo(
                 CaptureParameters(
                     type = FullScreen(displayId = 0),
-                    component = ComponentName.unflattenFromString(YOUTUBE),
+                    contentTask =
+                        TaskReference(
+                            taskId = expectedFocusedTask.id,
+                            component = expectedFocusedTask.componentName,
+                            owner = UserHandle.of(expectedFocusedTask.userId),
+                            bounds = expectedFocusedTask.bounds,
+                        ),
                     owner = UserHandle.of(PRIVATE),
                 )
             )
     }
 
     @Test
-    fun freeform_floating_workOnly() = runTest {
-        val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
+    fun freeform_floating_work() = runTest {
+        val displayContent =
+            freeFormApps(TaskSpec(taskId = 1002, name = FILES, userId = WORK), focusedTaskId = 1002)
+        val expectedFocusedTask =
+            displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1002 }
 
-        val result =
-            policy.apply(
-                freeFormApps(
-                    TaskSpec(taskId = 1002, name = FILES, userId = WORK),
-                    focusedTaskId = 1002,
-                ),
-                defaultComponent,
-                defaultOwner,
-            )
+        val result = policy.apply(displayContent, defaultComponent, defaultOwner)
 
         assertThat(result)
             .isEqualTo(
                 CaptureParameters(
                     type = FullScreen(displayId = 0),
-                    component = ComponentName.unflattenFromString(LAUNCHER),
+                    contentTask =
+                        TaskReference(
+                            taskId = expectedFocusedTask.id,
+                            component = expectedFocusedTask.componentName,
+                            owner = UserHandle.of(expectedFocusedTask.userId),
+                            bounds = expectedFocusedTask.bounds,
+                        ),
                     owner = defaultOwner,
                 )
             )
@@ -303,23 +335,27 @@
 
     @Test
     fun fullScreen_shadeExpanded() = runTest {
-        val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
-
-        val result =
-            policy.apply(
-                singleFullScreen(
-                    TaskSpec(taskId = 1002, name = FILES, userId = WORK),
-                    shadeExpanded = true,
-                ),
-                defaultComponent,
-                defaultOwner,
+        val displayContent =
+            singleFullScreen(
+                TaskSpec(taskId = 1002, name = FILES, userId = WORK),
+                shadeExpanded = true,
             )
+        val expectedFocusedTask =
+            displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1002 }
+
+        val result = policy.apply(displayContent, defaultComponent, defaultOwner)
 
         assertThat(result)
             .isEqualTo(
                 CaptureParameters(
                     type = FullScreen(displayId = 0),
-                    component = defaultComponent,
+                    contentTask =
+                        TaskReference(
+                            taskId = -1,
+                            component = defaultComponent,
+                            owner = defaultOwner,
+                            bounds = Rect(),
+                        ),
                     owner = defaultOwner,
                 )
             )
@@ -327,25 +363,55 @@
 
     @Test
     fun fullScreen_with_PictureInPicture() = runTest {
-        val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
-
-        val result =
-            policy.apply(
-                pictureInPictureApp(
-                    pip = TaskSpec(taskId = 1002, name = YOUTUBE, userId = PERSONAL),
-                    fullScreen = TaskSpec(taskId = 1003, name = FILES, userId = WORK),
-                ),
-                defaultComponent,
-                defaultOwner,
+        val displayContent =
+            pictureInPictureApp(
+                pip = TaskSpec(taskId = 1002, name = YOUTUBE, userId = PERSONAL),
+                fullScreen = TaskSpec(taskId = 1003, name = FILES, userId = WORK),
             )
+        val expectedFocusedTask = displayContent.allTasks().single { it.id == 1003 }
+
+        val result = policy.apply(displayContent, defaultComponent, defaultOwner)
 
         assertThat(result)
             .isEqualTo(
                 CaptureParameters(
                     type = IsolatedTask(taskId = 1003, taskBounds = FULL_SCREEN),
-                    component = ComponentName.unflattenFromString(FILES),
+                    contentTask =
+                        TaskReference(
+                            taskId = expectedFocusedTask.id,
+                            component = expectedFocusedTask.componentName,
+                            owner = UserHandle.of(expectedFocusedTask.userId),
+                            bounds = expectedFocusedTask.bounds,
+                        ),
                     owner = UserHandle.of(WORK),
                 )
             )
     }
+
+    // TODO: PiP tasks should affect ownership (e.g. Private)
+    @Test
+    fun fullScreen_with_PictureInPicture_private() = runTest {
+        val displayContent =
+            pictureInPictureApp(
+                pip = TaskSpec(taskId = 1002, name = YOUTUBE, userId = PRIVATE),
+                fullScreen = TaskSpec(taskId = 1003, name = FILES, userId = PERSONAL),
+            )
+        val expectedFocusedTask = displayContent.allTasks().single { it.id == 1003 }
+
+        val result = policy.apply(displayContent, defaultComponent, defaultOwner)
+        assertThat(result)
+            .isEqualTo(
+                CaptureParameters(
+                    type = FullScreen(displayId = 0),
+                    contentTask =
+                        TaskReference(
+                            taskId = expectedFocusedTask.id,
+                            component = expectedFocusedTask.componentName,
+                            owner = UserHandle.of(expectedFocusedTask.userId),
+                            bounds = expectedFocusedTask.bounds,
+                        ),
+                    owner = UserHandle.of(PRIVATE),
+                )
+            )
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
index 30a786c..c1477fe 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
@@ -135,7 +135,7 @@
                 PolicyResult.Matched(
                     policy = WorkProfilePolicy.NAME,
                     reason = WORK_TASK_IS_TOP,
-                    CaptureParameters(
+                    LegacyCaptureParameters(
                         type = IsolatedTask(taskId = 1002, taskBounds = FULL_SCREEN),
                         component = ComponentName.unflattenFromString(FILES),
                         owner = UserHandle.of(WORK),
@@ -162,7 +162,7 @@
                 PolicyResult.Matched(
                     policy = WorkProfilePolicy.NAME,
                     reason = WORK_TASK_IS_TOP,
-                    CaptureParameters(
+                    LegacyCaptureParameters(
                         type = IsolatedTask(taskId = 1002, taskBounds = FULL_SCREEN.splitTop(20)),
                         component = ComponentName.unflattenFromString(FILES),
                         owner = UserHandle.of(WORK),
@@ -200,7 +200,7 @@
                 PolicyResult.Matched(
                     policy = WorkProfilePolicy.NAME,
                     reason = WORK_TASK_IS_TOP,
-                    CaptureParameters(
+                    LegacyCaptureParameters(
                         type = IsolatedTask(taskId = 1003, taskBounds = FULL_SCREEN),
                         component = ComponentName.unflattenFromString(FILES),
                         owner = UserHandle.of(WORK),
@@ -226,7 +226,7 @@
                 PolicyResult.Matched(
                     policy = WorkProfilePolicy.NAME,
                     reason = WORK_TASK_IS_TOP,
-                    CaptureParameters(
+                    LegacyCaptureParameters(
                         type = IsolatedTask(taskId = 1003, taskBounds = FREE_FORM),
                         component = ComponentName.unflattenFromString(FILES),
                         owner = UserHandle.of(WORK),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
index 2e759a3..443595d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
@@ -17,7 +17,6 @@
 package com.android.systemui.shade;
 
 import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
-import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doAnswer;
@@ -63,8 +62,6 @@
 import com.android.systemui.statusbar.QsFrameTranslateController;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository;
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository;
-import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
 import com.android.systemui.statusbar.notification.stack.AmbientState;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.phone.DozeParameters;
@@ -159,8 +156,6 @@
     protected SysuiStatusBarStateController mStatusBarStateController;
     protected ShadeInteractor mShadeInteractor;
 
-    protected ActiveNotificationsInteractor mActiveNotificationsInteractor;
-
     protected Handler mMainHandler;
     protected LockscreenShadeTransitionController.Callback mLockscreenShadeTransitionCallback;
 
@@ -204,11 +199,6 @@
                 ),
                 mKosmos.getShadeModeInteractor());
 
-        mActiveNotificationsInteractor = new ActiveNotificationsInteractor(
-                        new ActiveNotificationListRepository(),
-                        StandardTestDispatcher(/* scheduler = */ null, /* name = */ null)
-                );
-
         KeyguardStatusView keyguardStatusView = new KeyguardStatusView(mContext);
         keyguardStatusView.setId(R.id.keyguard_status_view);
 
@@ -277,7 +267,7 @@
                 mock(DeviceEntryFaceAuthInteractor.class),
                 mShadeRepository,
                 mShadeInteractor,
-                mActiveNotificationsInteractor,
+                mKosmos.getActiveNotificationsInteractor(),
                 new JavaAdapter(mTestScope.getBackgroundScope()),
                 mCastController,
                 splitShadeStateController,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
index 943fb62..0f476d0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
@@ -35,8 +35,7 @@
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.NotificationShadeWindowController
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
-import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
@@ -50,7 +49,6 @@
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import dagger.Lazy
-import kotlinx.coroutines.test.StandardTestDispatcher
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -65,8 +63,6 @@
 @SmallTest
 class ShadeControllerImplTest : SysuiTestCase() {
     private val executor = FakeExecutor(FakeSystemClock())
-    private val testDispatcher = StandardTestDispatcher()
-    private val activeNotificationsRepository = ActiveNotificationListRepository()
     private val kosmos = Kosmos()
     private val testScope = kosmos.testScope
 
@@ -95,7 +91,7 @@
             FakeKeyguardRepository(),
             headsUpManager,
             PowerInteractorFactory.create().powerInteractor,
-            ActiveNotificationsInteractor(activeNotificationsRepository, testDispatcher),
+            kosmos.activeNotificationsInteractor,
             kosmos::sceneInteractor,
         )
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt
index ce9b3be..7842d75 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.plugins.BcSmartspaceDataPlugin
 import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
 import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.concurrency.Execution
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.withArgCaptor
@@ -52,6 +53,10 @@
 @RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper
 class CommunalSmartspaceControllerTest : SysuiTestCase() {
+    @Mock private lateinit var userTracker: UserTracker
+
+    @Mock private lateinit var userContextPrimary: Context
+
     @Mock private lateinit var smartspaceManager: SmartspaceManager
 
     @Mock private lateinit var execution: Execution
@@ -113,15 +118,18 @@
         MockitoAnnotations.initMocks(this)
         `when`(smartspaceManager.createSmartspaceSession(any())).thenReturn(session)
 
+        `when`(userTracker.userContext).thenReturn(userContextPrimary)
+        `when`(userContextPrimary.getSystemService(SmartspaceManager::class.java))
+            .thenReturn(smartspaceManager)
+
         controller =
             CommunalSmartspaceController(
-                context,
-                smartspaceManager,
+                userTracker,
                 execution,
                 uiExecutor,
                 precondition,
                 Optional.of(targetFilter),
-                Optional.of(plugin)
+                Optional.of(plugin),
             )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
index e774aed..c83c82d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.plugins.BcSmartspaceDataPlugin
 import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
 import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.smartspace.dagger.SmartspaceViewComponent
 import com.android.systemui.util.concurrency.Execution
 import com.android.systemui.util.mockito.any
@@ -46,66 +47,51 @@
 import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.Mockito
+import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
-import org.mockito.Mockito.anyInt
 import org.mockito.MockitoAnnotations
-import org.mockito.Spy
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper
 class DreamSmartspaceControllerTest : SysuiTestCase() {
-    @Mock
-    private lateinit var smartspaceManager: SmartspaceManager
+    @Mock private lateinit var userTracker: UserTracker
 
-    @Mock
-    private lateinit var execution: Execution
+    @Mock private lateinit var userContextPrimary: Context
 
-    @Mock
-    private lateinit var uiExecutor: Executor
+    @Mock private lateinit var smartspaceManager: SmartspaceManager
 
-    @Mock
-    private lateinit var viewComponentFactory: SmartspaceViewComponent.Factory
+    @Mock private lateinit var execution: Execution
 
-    @Mock
-    private lateinit var viewComponent: SmartspaceViewComponent
+    @Mock private lateinit var uiExecutor: Executor
 
-    @Mock
-    private lateinit var weatherViewComponent: SmartspaceViewComponent
+    @Mock private lateinit var viewComponentFactory: SmartspaceViewComponent.Factory
 
-    private val weatherSmartspaceView: SmartspaceView by lazy {
-        Mockito.spy(TestView(context))
-    }
+    @Mock private lateinit var viewComponent: SmartspaceViewComponent
 
-    @Mock
-    private lateinit var targetFilter: SmartspaceTargetFilter
+    @Mock private lateinit var weatherViewComponent: SmartspaceViewComponent
 
-    @Mock
-    private lateinit var plugin: BcSmartspaceDataPlugin
+    private val weatherSmartspaceView: SmartspaceView by lazy { Mockito.spy(TestView(context)) }
 
-    @Mock
-    private lateinit var weatherPlugin: BcSmartspaceDataPlugin
+    @Mock private lateinit var targetFilter: SmartspaceTargetFilter
 
-    @Mock
-    private lateinit var precondition: SmartspacePrecondition
+    @Mock private lateinit var plugin: BcSmartspaceDataPlugin
 
-    private val smartspaceView: SmartspaceView by lazy {
-        Mockito.spy(TestView(context))
-    }
+    @Mock private lateinit var weatherPlugin: BcSmartspaceDataPlugin
 
-    @Mock
-    private lateinit var listener: BcSmartspaceDataPlugin.SmartspaceTargetListener
+    @Mock private lateinit var precondition: SmartspacePrecondition
 
-    @Mock
-    private lateinit var session: SmartspaceSession
+    private val smartspaceView: SmartspaceView by lazy { Mockito.spy(TestView(context)) }
+
+    @Mock private lateinit var listener: BcSmartspaceDataPlugin.SmartspaceTargetListener
+
+    @Mock private lateinit var session: SmartspaceSession
 
     private lateinit var controller: DreamSmartspaceController
 
     // TODO(b/272811280): Remove usage of real view
-    private val fakeParent by lazy {
-        FrameLayout(context)
-    }
+    private val fakeParent by lazy { FrameLayout(context) }
 
     /**
      * A class which implements SmartspaceView and extends View. This is mocked to provide the right
@@ -134,30 +120,44 @@
 
         override fun setMediaTarget(target: SmartspaceTarget?) {}
 
-        override fun getSelectedPage(): Int { return 0; }
+        override fun getSelectedPage(): Int {
+            return 0
+        }
 
-        override fun getCurrentCardTopPadding(): Int { return 0; }
+        override fun getCurrentCardTopPadding(): Int {
+            return 0
+        }
     }
 
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
         `when`(viewComponentFactory.create(any(), eq(plugin), any(), eq(null)))
-                .thenReturn(viewComponent)
+            .thenReturn(viewComponent)
         `when`(viewComponent.getView()).thenReturn(smartspaceView)
         `when`(viewComponentFactory.create(any(), eq(weatherPlugin), any(), any()))
             .thenReturn(weatherViewComponent)
         `when`(weatherViewComponent.getView()).thenReturn(weatherSmartspaceView)
         `when`(smartspaceManager.createSmartspaceSession(any())).thenReturn(session)
 
-        controller = DreamSmartspaceController(context, smartspaceManager, execution, uiExecutor,
-                viewComponentFactory, precondition, Optional.of(targetFilter), Optional.of(plugin),
-        Optional.of(weatherPlugin))
+        `when`(userTracker.userContext).thenReturn(userContextPrimary)
+        `when`(userContextPrimary.getSystemService(SmartspaceManager::class.java))
+            .thenReturn(smartspaceManager)
+
+        controller =
+            DreamSmartspaceController(
+                userTracker,
+                execution,
+                uiExecutor,
+                viewComponentFactory,
+                precondition,
+                Optional.of(targetFilter),
+                Optional.of(plugin),
+                Optional.of(weatherPlugin),
+            )
     }
 
-    /**
-     * Ensures smartspace session begins on a listener only flow.
-     */
+    /** Ensures smartspace session begins on a listener only flow. */
     @Test
     fun testConnectOnListen() {
         `when`(precondition.conditionsMet()).thenReturn(true)
@@ -165,18 +165,18 @@
 
         verify(smartspaceManager).createSmartspaceSession(any())
 
-        var targetListener = withArgCaptor<SmartspaceSession.OnTargetsAvailableListener> {
-            verify(session).addOnTargetsAvailableListener(any(), capture())
-        }
+        var targetListener =
+            withArgCaptor<SmartspaceSession.OnTargetsAvailableListener> {
+                verify(session).addOnTargetsAvailableListener(any(), capture())
+            }
 
         `when`(targetFilter.filterSmartspaceTarget(any())).thenReturn(true)
 
         var target = Mockito.mock(SmartspaceTarget::class.java)
         targetListener.onTargetsAvailable(listOf(target))
 
-        var targets = withArgCaptor<List<SmartspaceTarget>> {
-            verify(plugin).onTargetsAvailable(capture())
-        }
+        var targets =
+            withArgCaptor<List<SmartspaceTarget>> { verify(plugin).onTargetsAvailable(capture()) }
 
         assertThat(targets.contains(target)).isTrue()
 
@@ -185,17 +185,16 @@
         verify(session).close()
     }
 
-    /**
-     * Ensures session begins when a view is attached.
-     */
+    /** Ensures session begins when a view is attached. */
     @Test
     fun testConnectOnViewCreate() {
         `when`(precondition.conditionsMet()).thenReturn(true)
         controller.buildAndConnectView(Mockito.mock(ViewGroup::class.java))
 
-        val stateChangeListener = withArgCaptor<View.OnAttachStateChangeListener> {
-            verify(viewComponentFactory).create(any(), eq(plugin), capture(), eq(null))
-        }
+        val stateChangeListener =
+            withArgCaptor<View.OnAttachStateChangeListener> {
+                verify(viewComponentFactory).create(any(), eq(plugin), capture(), eq(null))
+            }
 
         val mockView = Mockito.mock(TestView::class.java)
         `when`(precondition.conditionsMet()).thenReturn(true)
@@ -209,9 +208,7 @@
         verify(session).close()
     }
 
-    /**
-     * Ensures session is created when weather smartspace view is created and attached.
-     */
+    /** Ensures session is created when weather smartspace view is created and attached. */
     @Test
     fun testConnectOnWeatherViewCreate() {
         `when`(precondition.conditionsMet()).thenReturn(true)
@@ -223,8 +220,8 @@
 
         // Then weather view is created with custom view and the default weatherPlugin.getView
         // should not be called
-        verify(viewComponentFactory).create(eq(fakeParent), eq(weatherPlugin), any(),
-            eq(customView))
+        verify(viewComponentFactory)
+            .create(eq(fakeParent), eq(weatherPlugin), any(), eq(customView))
         verify(weatherPlugin, Mockito.never()).getView(fakeParent)
 
         // And then session is created
@@ -234,9 +231,7 @@
         verify(weatherSmartspaceView).setDozeAmount(0f)
     }
 
-    /**
-     * Ensures weather plugin registers target listener when it is added from the controller.
-     */
+    /** Ensures weather plugin registers target listener when it is added from the controller. */
     @Test
     fun testAddListenerInController_registersListenerForWeatherPlugin() {
         val customView = Mockito.mock(TestView::class.java)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
index 6e19096..32f4164 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
@@ -66,18 +66,34 @@
         testScope.runTest {
             val latest by collectLastValue(underTest.chips)
 
-            setNotifs(listOf(activeNotificationModel(key = "notif", statusBarChipIcon = null)))
+            setNotifs(
+                listOf(
+                    activeNotificationModel(
+                        key = "notif",
+                        statusBarChipIcon = null,
+                        isPromoted = true,
+                    )
+                )
+            )
 
             assertThat(latest).isEmpty()
         }
 
     @Test
-    fun chips_oneNotif_statusBarIconViewMatches() =
+    fun chips_onePromotedNotif_statusBarIconViewMatches() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chips)
 
             val icon = mock<StatusBarIconView>()
-            setNotifs(listOf(activeNotificationModel(key = "notif", statusBarChipIcon = icon)))
+            setNotifs(
+                listOf(
+                    activeNotificationModel(
+                        key = "notif",
+                        statusBarChipIcon = icon,
+                        isPromoted = true,
+                    )
+                )
+            )
 
             assertThat(latest).hasSize(1)
             val chip = latest!![0]
@@ -86,7 +102,7 @@
         }
 
     @Test
-    fun chips_twoNotifs_twoChips() =
+    fun chips_onlyForPromotedNotifs() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chips)
 
@@ -94,8 +110,21 @@
             val secondIcon = mock<StatusBarIconView>()
             setNotifs(
                 listOf(
-                    activeNotificationModel(key = "notif1", statusBarChipIcon = firstIcon),
-                    activeNotificationModel(key = "notif2", statusBarChipIcon = secondIcon),
+                    activeNotificationModel(
+                        key = "notif1",
+                        statusBarChipIcon = firstIcon,
+                        isPromoted = true,
+                    ),
+                    activeNotificationModel(
+                        key = "notif2",
+                        statusBarChipIcon = secondIcon,
+                        isPromoted = true,
+                    ),
+                    activeNotificationModel(
+                        key = "notif3",
+                        statusBarChipIcon = mock<StatusBarIconView>(),
+                        isPromoted = false,
+                    ),
                 )
             )
 
@@ -118,6 +147,7 @@
                     activeNotificationModel(
                         key = "clickTest",
                         statusBarChipIcon = mock<StatusBarIconView>(),
+                        isPromoted = true,
                     )
                 )
             )
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
index b12d7c5..25d5ce5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
@@ -293,19 +293,27 @@
         }
 
     @Test
-    fun chips_singleNotifChip_primaryIsNotifSecondaryIsHidden() =
+    fun chips_singlePromotedNotif_primaryIsNotifSecondaryIsHidden() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chips)
 
             val icon = mock<StatusBarIconView>()
-            setNotifs(listOf(activeNotificationModel(key = "notif", statusBarChipIcon = icon)))
+            setNotifs(
+                listOf(
+                    activeNotificationModel(
+                        key = "notif",
+                        statusBarChipIcon = icon,
+                        isPromoted = true,
+                    )
+                )
+            )
 
             assertIsNotifChip(latest!!.primary, icon)
             assertThat(latest!!.secondary).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
         }
 
     @Test
-    fun chips_twoNotifChips_primaryAndSecondaryAreNotifsInOrder() =
+    fun chips_twoPromotedNotifs_primaryAndSecondaryAreNotifsInOrder() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chips)
 
@@ -313,8 +321,16 @@
             val secondIcon = mock<StatusBarIconView>()
             setNotifs(
                 listOf(
-                    activeNotificationModel(key = "firstNotif", statusBarChipIcon = firstIcon),
-                    activeNotificationModel(key = "secondNotif", statusBarChipIcon = secondIcon),
+                    activeNotificationModel(
+                        key = "firstNotif",
+                        statusBarChipIcon = firstIcon,
+                        isPromoted = true,
+                    ),
+                    activeNotificationModel(
+                        key = "secondNotif",
+                        statusBarChipIcon = secondIcon,
+                        isPromoted = true,
+                    ),
                 )
             )
 
@@ -323,7 +339,7 @@
         }
 
     @Test
-    fun chips_threeNotifChips_topTwoShown() =
+    fun chips_threePromotedNotifs_topTwoShown() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chips)
 
@@ -332,9 +348,21 @@
             val thirdIcon = mock<StatusBarIconView>()
             setNotifs(
                 listOf(
-                    activeNotificationModel(key = "firstNotif", statusBarChipIcon = firstIcon),
-                    activeNotificationModel(key = "secondNotif", statusBarChipIcon = secondIcon),
-                    activeNotificationModel(key = "thirdNotif", statusBarChipIcon = thirdIcon),
+                    activeNotificationModel(
+                        key = "firstNotif",
+                        statusBarChipIcon = firstIcon,
+                        isPromoted = true,
+                    ),
+                    activeNotificationModel(
+                        key = "secondNotif",
+                        statusBarChipIcon = secondIcon,
+                        isPromoted = true,
+                    ),
+                    activeNotificationModel(
+                        key = "thirdNotif",
+                        statusBarChipIcon = thirdIcon,
+                        isPromoted = true,
+                    ),
                 )
             )
 
@@ -343,7 +371,7 @@
         }
 
     @Test
-    fun chips_callAndNotifs_primaryIsCallSecondaryIsNotif() =
+    fun chips_callAndPromotedNotifs_primaryIsCallSecondaryIsNotif() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chips)
 
@@ -351,10 +379,15 @@
             val firstIcon = mock<StatusBarIconView>()
             setNotifs(
                 listOf(
-                    activeNotificationModel(key = "firstNotif", statusBarChipIcon = firstIcon),
+                    activeNotificationModel(
+                        key = "firstNotif",
+                        statusBarChipIcon = firstIcon,
+                        isPromoted = true,
+                    ),
                     activeNotificationModel(
                         key = "secondNotif",
                         statusBarChipIcon = mock<StatusBarIconView>(),
+                        isPromoted = true,
                     ),
                 )
             )
@@ -364,7 +397,7 @@
         }
 
     @Test
-    fun chips_screenRecordAndCallAndNotifs_notifsNotShown() =
+    fun chips_screenRecordAndCallAndPromotedNotifs_notifsNotShown() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chips)
 
@@ -375,6 +408,7 @@
                     activeNotificationModel(
                         key = "notif",
                         statusBarChipIcon = mock<StatusBarIconView>(),
+                        isPromoted = true,
                     )
                 )
             )
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt
index 20a19a9..938da88 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt
@@ -26,11 +26,14 @@
 import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.fragments.FragmentHostManager
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.statusbar.data.repository.fakeStatusBarModePerDisplayRepository
 import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment
 import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent
 import com.android.systemui.statusbar.pipeline.shared.ui.composable.StatusBarRootFactory
 import com.android.systemui.statusbar.window.StatusBarWindowController
 import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlin.test.Test
 import org.junit.Assert.assertThrows
@@ -45,12 +48,14 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class StatusBarInitializerTest : SysuiTestCase() {
+    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
     private val windowController = mock(StatusBarWindowController::class.java)
     private val windowControllerStore = mock(StatusBarWindowControllerStore::class.java)
     private val transaction = mock(FragmentTransaction::class.java)
     private val fragmentManager = mock(FragmentManager::class.java)
     private val fragmentHostManager = mock(FragmentHostManager::class.java)
     private val backgroundView = mock(ViewGroup::class.java)
+    private val statusBarModePerDisplayRepository = kosmos.fakeStatusBarModePerDisplayRepository
 
     @Before
     fun setup() {
@@ -72,6 +77,7 @@
             statusBarRootFactory = mock(StatusBarRootFactory::class.java),
             componentFactory = mock(HomeStatusBarComponent.Factory::class.java),
             creationListeners = setOf(),
+            statusBarModePerDisplayRepository = statusBarModePerDisplayRepository,
         )
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreImplTest.kt
new file mode 100644
index 0000000..18eef33
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreImplTest.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2024 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.data.repository
+
+import android.view.Display.DEFAULT_DISPLAY
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class LightBarControllerStoreImplTest : SysuiTestCase() {
+    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+    private val testScope = kosmos.testScope
+    private val fakeDisplayRepository = kosmos.displayRepository
+
+    private val underTest = kosmos.lightBarControllerStoreImpl
+
+    @Before
+    fun start() {
+        underTest.start()
+    }
+
+    @Before fun addDisplays() = runBlocking { fakeDisplayRepository.addDisplay(DEFAULT_DISPLAY) }
+
+    @Test
+    fun forDisplay_startsInstance() =
+        testScope.runTest {
+            val instance = underTest.forDisplay(DEFAULT_DISPLAY)
+
+            verify(instance).start()
+        }
+
+    @Test
+    fun beforeDisplayRemoved_doesNotStopInstances() =
+        testScope.runTest {
+            val instance = underTest.forDisplay(DEFAULT_DISPLAY)
+
+            verify(instance, never()).stop()
+        }
+
+    @Test
+    fun displayRemoved_stopsInstance() =
+        testScope.runTest {
+            val instance = underTest.forDisplay(DEFAULT_DISPLAY)
+
+            fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY)
+
+            verify(instance).stop()
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarModeRepositoryStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarModeRepositoryStoreTest.kt
new file mode 100644
index 0000000..a9920ec5
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarModeRepositoryStoreTest.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 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.data.repository
+
+import android.platform.test.annotations.EnableFlags
+import android.view.Display.DEFAULT_DISPLAY
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+class MultiDisplayStatusBarModeRepositoryStoreTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+    private val testScope = kosmos.testScope
+    private val fakeDisplayRepository = kosmos.displayRepository
+    private val underTest by lazy { kosmos.multiDisplayStatusBarModeRepositoryStore }
+
+    @Before
+    fun start() {
+        underTest.start()
+    }
+
+    @Before fun addDisplays() = runBlocking { fakeDisplayRepository.addDisplay(DEFAULT_DISPLAY) }
+
+    @Test
+    fun forDisplay_startsInstance() =
+        testScope.runTest {
+            val instance = underTest.forDisplay(DEFAULT_DISPLAY)
+
+            verify(instance).start()
+        }
+
+    @Test
+    fun displayRemoved_stopsInstance() =
+        testScope.runTest {
+            val instance = underTest.forDisplay(DEFAULT_DISPLAY)
+
+            fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY)
+
+            verify(instance).stop()
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
index 9f40f60..99bda85 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
@@ -18,11 +18,14 @@
 
 package com.android.systemui.statusbar.notification.domain.interactor
 
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
 import com.android.systemui.statusbar.notification.collection.render.NotifStats
 import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
@@ -44,7 +47,7 @@
     private val testScope = kosmos.testScope
     private val activeNotificationListRepository = kosmos.activeNotificationListRepository
 
-    private val underTest = kosmos.activeNotificationsInteractor
+    private val underTest by lazy { kosmos.activeNotificationsInteractor }
 
     @Test
     fun testAllNotificationsCount() =
@@ -65,14 +68,8 @@
 
             val normalNotifs =
                 listOf(
-                    activeNotificationModel(
-                        key = "notif1",
-                        callType = CallType.None,
-                    ),
-                    activeNotificationModel(
-                        key = "notif2",
-                        callType = CallType.None,
-                    )
+                    activeNotificationModel(key = "notif1", callType = CallType.None),
+                    activeNotificationModel(key = "notif2", callType = CallType.None),
                 )
 
             activeNotificationListRepository.activeNotifications.value =
@@ -129,10 +126,7 @@
             val latest by collectLastValue(underTest.ongoingCallNotification)
 
             val ongoingNotif =
-                activeNotificationModel(
-                    key = "ongoingNotif",
-                    callType = CallType.Ongoing,
-                )
+                activeNotificationModel(key = "ongoingNotif", callType = CallType.Ongoing)
 
             activeNotificationListRepository.activeNotifications.value =
                 ActiveNotificationsStore.Builder()
@@ -170,6 +164,62 @@
         }
 
     @Test
+    @DisableFlags(StatusBarNotifChips.FLAG_NAME)
+    fun promotedOngoingNotifications_flagOff_empty() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.promotedOngoingNotifications)
+
+            val promoted1 = activeNotificationModel(key = "notif1", isPromoted = true)
+            val notPromoted2 = activeNotificationModel(key = "notif2", isPromoted = false)
+
+            activeNotificationListRepository.activeNotifications.value =
+                ActiveNotificationsStore.Builder()
+                    .apply { addIndividualNotif(promoted1) }
+                    .apply { addIndividualNotif(notPromoted2) }
+                    .build()
+
+            assertThat(latest!!).isEmpty()
+        }
+
+    @Test
+    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+    fun promotedOngoingNotifications_nonePromoted_empty() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.promotedOngoingNotifications)
+
+            activeNotificationListRepository.activeNotifications.value =
+                ActiveNotificationsStore.Builder()
+                    .apply { activeNotificationModel(key = "notif1", isPromoted = false) }
+                    .apply { activeNotificationModel(key = "notif2", isPromoted = false) }
+                    .apply { activeNotificationModel(key = "notif3", isPromoted = false) }
+                    .build()
+
+            assertThat(latest!!).isEmpty()
+        }
+
+    @Test
+    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+    fun promotedOngoingNotifications_somePromoted_hasOnlyPromoted() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.promotedOngoingNotifications)
+
+            val promoted1 = activeNotificationModel(key = "notif1", isPromoted = true)
+            val notPromoted2 = activeNotificationModel(key = "notif2", isPromoted = false)
+            val notPromoted3 = activeNotificationModel(key = "notif3", isPromoted = false)
+            val promoted4 = activeNotificationModel(key = "notif4", isPromoted = true)
+
+            activeNotificationListRepository.activeNotifications.value =
+                ActiveNotificationsStore.Builder()
+                    .apply { addIndividualNotif(promoted1) }
+                    .apply { addIndividualNotif(notPromoted2) }
+                    .apply { addIndividualNotif(notPromoted3) }
+                    .apply { addIndividualNotif(promoted4) }
+                    .build()
+
+            assertThat(latest!!).containsExactly(promoted1, promoted4)
+        }
+
+    @Test
     fun areAnyNotificationsPresent_isTrue() =
         testScope.runTest {
             val areAnyNotificationsPresent by collectLastValue(underTest.areAnyNotificationsPresent)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
index 572a0c1..183f901 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
@@ -16,22 +16,25 @@
 package com.android.systemui.statusbar.notification.domain.interactor
 
 import android.app.Notification
-import android.os.Bundle
+import android.app.Notification.FLAG_PROMOTED_ONGOING
+import android.platform.test.annotations.EnableFlags
 import android.service.notification.StatusBarNotification
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.statusbar.RankingBuilder
 import com.android.systemui.statusbar.notification.collection.GroupEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
+import com.android.systemui.statusbar.notification.promoted.promotedNotificationsProvider
 import com.android.systemui.statusbar.notification.shared.byKey
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -39,16 +42,16 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class RenderNotificationsListInteractorTest : SysuiTestCase() {
-    private val backgroundDispatcher = StandardTestDispatcher()
-    private val testScope = TestScope(backgroundDispatcher)
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
 
-    private val notifsRepository = ActiveNotificationListRepository()
-    private val notifsInteractor =
-        ActiveNotificationsInteractor(notifsRepository, backgroundDispatcher)
+    private val notifsRepository = kosmos.activeNotificationListRepository
+    private val notifsInteractor = kosmos.activeNotificationsInteractor
     private val underTest =
         RenderNotificationListInteractor(
             notifsRepository,
             sectionStyleProvider = mock(),
+            promotedNotificationsProvider = kosmos.promotedNotificationsProvider,
         )
 
     @Test
@@ -85,12 +88,7 @@
 
             assertThat(ranks)
                 .containsExactlyEntriesIn(
-                    mapOf(
-                        "single" to 0,
-                        "summary" to 1,
-                        "child0" to 2,
-                        "child1" to 3,
-                    )
+                    mapOf("single" to 0, "summary" to 1, "child0" to 2, "child1" to 3)
                 )
         }
 
@@ -126,6 +124,53 @@
 
             assertThat(actual).containsAtLeastEntriesIn(expected)
         }
+
+    @Test
+    @EnableFlags(PromotedNotificationUi.FLAG_NAME)
+    fun setRenderList_setsPromotionStatus() =
+        testScope.runTest {
+            val actual by collectLastValue(notifsInteractor.topLevelRepresentativeNotifications)
+
+            val notPromoted1 = mockNotificationEntry("key1", flag = null)
+            val promoted2 = mockNotificationEntry("key2", flag = FLAG_PROMOTED_ONGOING)
+
+            underTest.setRenderedList(listOf(notPromoted1, promoted2))
+
+            assertThat(actual!!.size).isEqualTo(2)
+
+            val first = actual!![0]
+            assertThat(first.key).isEqualTo("key1")
+            assertThat(first.isPromoted).isFalse()
+
+            val second = actual!![1]
+            assertThat(second.key).isEqualTo("key2")
+            assertThat(second.isPromoted).isTrue()
+        }
+
+    private fun mockNotificationEntry(
+        key: String,
+        rank: Int = 0,
+        flag: Int? = null,
+    ): NotificationEntry {
+        val nBuilder = Notification.Builder(context, "a")
+        if (flag != null) {
+            nBuilder.setFlag(flag, true)
+        }
+        val notification = nBuilder.build()
+
+        val mockSbn =
+            mock<StatusBarNotification>() {
+                whenever(this.notification).thenReturn(notification)
+                whenever(packageName).thenReturn("com.android")
+            }
+        return mock<NotificationEntry> {
+            whenever(this.key).thenReturn(key)
+            whenever(this.icons).thenReturn(mock())
+            whenever(this.representativeEntry).thenReturn(this)
+            whenever(this.ranking).thenReturn(RankingBuilder().setRank(rank).build())
+            whenever(this.sbn).thenReturn(mockSbn)
+        }
+    }
 }
 
 private fun mockGroupEntry(
@@ -139,19 +184,3 @@
         whenever(this.children).thenReturn(children)
     }
 }
-
-private fun mockNotificationEntry(key: String, rank: Int = 0): NotificationEntry {
-    val mockNotification = mock<Notification> { this.extras = Bundle() }
-    val mockSbn =
-        mock<StatusBarNotification>() {
-            whenever(notification).thenReturn(mockNotification)
-            whenever(packageName).thenReturn("com.android")
-        }
-    return mock<NotificationEntry> {
-        whenever(this.key).thenReturn(key)
-        whenever(this.icons).thenReturn(mock())
-        whenever(this.representativeEntry).thenReturn(this)
-        whenever(this.ranking).thenReturn(RankingBuilder().setRank(rank).build())
-        whenever(this.sbn).thenReturn(mockSbn)
-    }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProviderTest.kt
new file mode 100644
index 0000000..a9dbe63
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProviderTest.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2024 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.promoted
+
+import android.app.Notification
+import android.app.Notification.FLAG_PROMOTED_ONGOING
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+
+@SmallTest
+class PromotedNotificationsProviderTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+
+    private val underTest = kosmos.promotedNotificationsProvider
+
+    @Test
+    @DisableFlags(PromotedNotificationUi.FLAG_NAME)
+    fun shouldPromote_uiFlagOff_false() {
+        val entry = createNotification(FLAG_PROMOTED_ONGOING)
+
+        assertThat(underTest.shouldPromote(entry)).isFalse()
+    }
+
+    @Test
+    @EnableFlags(PromotedNotificationUi.FLAG_NAME)
+    fun shouldPromote_uiFlagOn_notifDoesNotHaveFlag_false() {
+        val entry = createNotification(flag = null)
+
+        assertThat(underTest.shouldPromote(entry)).isFalse()
+    }
+
+    @Test
+    @EnableFlags(PromotedNotificationUi.FLAG_NAME)
+    fun shouldPromote_uiFlagOn_notifHasFlag_true() {
+        val entry = createNotification(FLAG_PROMOTED_ONGOING)
+
+        assertThat(underTest.shouldPromote(entry)).isTrue()
+    }
+
+    private fun createNotification(flag: Int? = null): NotificationEntry {
+        val n = Notification.Builder(context, "a")
+        if (flag != null) {
+            n.setFlag(flag, true)
+        }
+
+        return NotificationEntryBuilder().setNotification(n.build()).build()
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt
new file mode 100644
index 0000000..a1b63b1
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt
@@ -0,0 +1,648 @@
+/*
+ * Copyright (C) 2024 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
+ */
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.notification.row
+
+import android.R
+import android.app.AppOpsManager
+import android.app.INotificationManager
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.content.Intent
+import android.content.pm.LauncherApps
+import android.content.pm.PackageManager
+import android.content.pm.ShortcutManager
+import android.graphics.Color
+import android.os.Binder
+import android.os.UserManager
+import android.os.fakeExecutorHandler
+import android.platform.test.flag.junit.FlagsParameterization
+import android.provider.Settings
+import android.service.notification.NotificationListenerService
+import android.testing.TestableLooper.RunWithLooper
+import android.util.ArraySet
+import android.view.View
+import android.view.accessibility.AccessibilityManager
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.internal.statusbar.IStatusBarService
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.flags.andSceneContainer
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.people.widget.PeopleSpaceWidgetManager
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.power.domain.interactor.PowerInteractorFactory.create
+import com.android.systemui.scene.data.repository.Idle
+import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
+import com.android.systemui.scene.data.repository.setSceneTransition
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.settings.UserContextProvider
+import com.android.systemui.shade.ShadeController
+import com.android.systemui.statusbar.NotificationEntryHelper
+import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import com.android.systemui.statusbar.NotificationPresenter
+import com.android.systemui.statusbar.notification.AssistantFeedbackController
+import com.android.systemui.statusbar.notification.NotificationActivityStarter
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
+import com.android.systemui.statusbar.notification.stack.NotificationListContainer
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.HeadsUpManager
+import com.android.systemui.testKosmos
+import com.android.systemui.util.kotlin.JavaAdapter
+import com.android.systemui.wmshell.BubblesManager
+import java.util.Optional
+import kotlin.test.assertNotNull
+import kotlin.test.fail
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.invocation.InvocationOnMock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.clearInvocations
+import org.mockito.kotlin.doNothing
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+
+/** Tests for [NotificationGutsManager]. */
+@SmallTest
+@RunWith(ParameterizedAndroidJunit4::class)
+@RunWithLooper
+class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase() {
+    private val testNotificationChannel =
+        NotificationChannel(
+            TEST_CHANNEL_ID,
+            TEST_CHANNEL_ID,
+            NotificationManager.IMPORTANCE_DEFAULT,
+        )
+
+    private val kosmos = testKosmos()
+
+    private val testScope = kosmos.testScope
+    private val javaAdapter = JavaAdapter(testScope.backgroundScope)
+    private val executor = kosmos.fakeExecutor
+    private val handler = kosmos.fakeExecutorHandler
+    private lateinit var helper: NotificationTestHelper
+    private lateinit var gutsManager: NotificationGutsManager
+
+    @get:Rule val rule: MockitoRule = MockitoJUnit.rule()
+
+    @Mock private lateinit var metricsLogger: MetricsLogger
+    @Mock private lateinit var onUserInteractionCallback: OnUserInteractionCallback
+    @Mock private lateinit var presenter: NotificationPresenter
+    @Mock private lateinit var notificationActivityStarter: NotificationActivityStarter
+    @Mock private lateinit var notificationListContainer: NotificationListContainer
+    @Mock
+    private lateinit var onSettingsClickListener: NotificationGutsManager.OnSettingsClickListener
+    @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
+    @Mock private lateinit var accessibilityManager: AccessibilityManager
+    @Mock private lateinit var highPriorityProvider: HighPriorityProvider
+    @Mock private lateinit var iNotificationManager: INotificationManager
+    @Mock private lateinit var barService: IStatusBarService
+    @Mock private lateinit var launcherApps: LauncherApps
+    @Mock private lateinit var shortcutManager: ShortcutManager
+    @Mock private lateinit var channelEditorDialogController: ChannelEditorDialogController
+    @Mock private lateinit var peopleNotificationIdentifier: PeopleNotificationIdentifier
+    @Mock private lateinit var contextTracker: UserContextProvider
+    @Mock private lateinit var bubblesManager: BubblesManager
+    @Mock private lateinit var shadeController: ShadeController
+    @Mock private lateinit var peopleSpaceWidgetManager: PeopleSpaceWidgetManager
+    @Mock private lateinit var assistantFeedbackController: AssistantFeedbackController
+    @Mock private lateinit var notificationLockscreenUserManager: NotificationLockscreenUserManager
+    @Mock private lateinit var statusBarStateController: StatusBarStateController
+    @Mock private lateinit var headsUpManager: HeadsUpManager
+    @Mock private lateinit var activityStarter: ActivityStarter
+    @Mock private lateinit var userManager: UserManager
+
+    private lateinit var windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor
+
+    companion object {
+        private const val TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId"
+
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf().andSceneContainer()
+        }
+    }
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags)
+    }
+
+    @Before
+    fun setUp() {
+        allowTestableLooperAsMainThread()
+        helper = NotificationTestHelper(mContext, mDependency)
+        whenever(accessibilityManager.isTouchExplorationEnabled).thenReturn(false)
+
+        windowRootViewVisibilityInteractor =
+            WindowRootViewVisibilityInteractor(
+                testScope.backgroundScope,
+                WindowRootViewVisibilityRepository(barService, executor),
+                FakeKeyguardRepository(),
+                headsUpManager,
+                create().powerInteractor,
+                kosmos.activeNotificationsInteractor,
+            ) {
+                kosmos.sceneInteractor
+            }
+
+        gutsManager =
+            NotificationGutsManager(
+                mContext,
+                handler,
+                handler,
+                javaAdapter,
+                accessibilityManager,
+                highPriorityProvider,
+                iNotificationManager,
+                userManager,
+                peopleSpaceWidgetManager,
+                launcherApps,
+                shortcutManager,
+                channelEditorDialogController,
+                contextTracker,
+                assistantFeedbackController,
+                Optional.of(bubblesManager),
+                UiEventLoggerFake(),
+                onUserInteractionCallback,
+                shadeController,
+                windowRootViewVisibilityInteractor,
+                notificationLockscreenUserManager,
+                statusBarStateController,
+                barService,
+                deviceProvisionedController,
+                metricsLogger,
+                headsUpManager,
+                activityStarter,
+            )
+        gutsManager.setUpWithPresenter(
+            presenter,
+            notificationListContainer,
+            onSettingsClickListener,
+        )
+        gutsManager.setNotificationActivityStarter(notificationActivityStarter)
+        gutsManager.start()
+    }
+
+    @Test
+    fun testOpenAndCloseGuts() {
+        val guts = spy(NotificationGuts(mContext))
+        whenever(guts.post(any())).thenAnswer { invocation: InvocationOnMock ->
+            handler.post(((invocation.arguments[0] as Runnable)))
+            null
+        }
+
+        // Test doesn't support animation since the guts view is not attached.
+        doNothing().whenever(guts).openControls(anyInt(), anyInt(), anyBoolean(), any())
+
+        val realRow = createTestNotificationRow()
+        val menuItem = createTestMenuItem(realRow)
+
+        val row = spy(realRow)
+        whenever(row.windowToken).thenReturn(Binder())
+        whenever(row.guts).thenReturn(guts)
+
+        assertTrue(gutsManager.openGutsInternal(row, 0, 0, menuItem))
+        assertEquals(View.INVISIBLE.toLong(), guts.visibility.toLong())
+        executor.runAllReady()
+        verify(guts).openControls(anyInt(), anyInt(), anyBoolean(), any<Runnable>())
+        verify(headsUpManager).setGutsShown(realRow.entry, true)
+
+        assertEquals(View.VISIBLE.toLong(), guts.visibility.toLong())
+        gutsManager.closeAndSaveGuts(false, false, true, 0, 0, false)
+
+        verify(guts).closeControls(anyBoolean(), anyBoolean(), anyInt(), anyInt(), anyBoolean())
+        verify(row, times(1)).setGutsView(any<MenuItem>())
+        executor.runAllReady()
+        verify(headsUpManager).setGutsShown(realRow.entry, false)
+    }
+
+    @Test
+    fun testLockscreenShadeVisible_visible_gutsNotClosed() =
+        testScope.runTest {
+            // First, start out lockscreen or shade as not visible
+            windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(false)
+            runCurrent()
+
+            val guts: NotificationGuts = mock()
+            gutsManager.exposedGuts = guts
+
+            // WHEN the lockscreen or shade becomes visible
+            windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
+            runCurrent()
+
+            // THEN the guts are not closed
+            verify(guts, never()).removeCallbacks(any())
+            verify(guts, never())
+                .closeControls(anyBoolean(), anyBoolean(), anyInt(), anyInt(), anyBoolean())
+        }
+
+    @Test
+    @DisableSceneContainer
+    fun testLockscreenShadeVisible_notVisible_gutsClosed() =
+        testScope.runTest {
+            // First, start out lockscreen or shade as visible
+            windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
+            runCurrent()
+
+            val guts: NotificationGuts = mock()
+            gutsManager.exposedGuts = guts
+
+            // WHEN the lockscreen or shade is no longer visible
+            windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(false)
+            runCurrent()
+
+            // THEN the guts are closed
+            verify(guts).removeCallbacks(null)
+            verify(guts)
+                .closeControls(
+                    /* leavebehinds = */ eq(true),
+                    /* controls = */ eq(true),
+                    /* x = */ anyInt(),
+                    /* y = */ anyInt(),
+                    /* force = */ eq(true),
+                )
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun testShadeVisible_notVisible_gutsClosed() =
+        testScope.runTest {
+            // First, start with shade as visible
+            kosmos.setSceneTransition(Idle(Scenes.Shade))
+            runCurrent()
+
+            val guts: NotificationGuts = mock()
+            gutsManager.exposedGuts = guts
+
+            // WHEN the shade is no longer visible
+            kosmos.setSceneTransition(Idle(Scenes.Gone))
+            runCurrent()
+
+            // THEN the guts are closed
+            verify(guts).removeCallbacks(null)
+            verify(guts)
+                .closeControls(
+                    /* leavebehinds = */ eq(true),
+                    /* controls = */ eq(true),
+                    /* x = */ anyInt(),
+                    /* y = */ anyInt(),
+                    /* force = */ eq(true),
+                )
+        }
+
+    @Test
+    @DisableSceneContainer
+    fun testLockscreenShadeVisible_notVisible_listContainerReset() =
+        testScope.runTest {
+            // First, start out lockscreen or shade as visible
+            windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
+            runCurrent()
+            clearInvocations(notificationListContainer)
+
+            // WHEN the lockscreen or shade is no longer visible
+            windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(false)
+            runCurrent()
+
+            // THEN the list container is reset
+            verify(notificationListContainer).resetExposedMenuView(anyBoolean(), anyBoolean())
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun testShadeVisible_notVisible_listContainerReset() =
+        testScope.runTest {
+            // First, start with shade as visible
+            kosmos.setSceneTransition(Idle(Scenes.Shade))
+            runCurrent()
+            clearInvocations(notificationListContainer)
+
+            // WHEN the shade is no longer visible
+            kosmos.setSceneTransition(Idle(Scenes.Gone))
+            runCurrent()
+
+            // THEN the list container is reset
+            verify(notificationListContainer).resetExposedMenuView(anyBoolean(), anyBoolean())
+        }
+
+    @Test
+    fun testChangeDensityOrFontScale() {
+        val guts = spy(NotificationGuts(mContext))
+        whenever(guts.post(any())).thenAnswer { invocation: InvocationOnMock ->
+            handler.post(((invocation.arguments[0] as Runnable)))
+            null
+        }
+
+        // Test doesn't support animation since the guts view is not attached.
+        doNothing().whenever(guts).openControls(anyInt(), anyInt(), anyBoolean(), any<Runnable>())
+
+        val realRow = createTestNotificationRow()
+        val menuItem = createTestMenuItem(realRow)
+
+        val row = spy(realRow)
+
+        whenever(row.windowToken).thenReturn(Binder())
+        whenever(row.guts).thenReturn(guts)
+        doNothing().whenever(row).ensureGutsInflated()
+
+        val realEntry = realRow.entry
+        val entry = spy(realEntry)
+
+        whenever(entry.row).thenReturn(row)
+        whenever(entry.guts).thenReturn(guts)
+
+        assertTrue(gutsManager.openGutsInternal(row, 0, 0, menuItem))
+        executor.runAllReady()
+        verify(guts).openControls(anyInt(), anyInt(), anyBoolean(), any<Runnable>())
+
+        // called once by mGutsManager.bindGuts() in mGutsManager.openGuts()
+        verify(row).setGutsView(any<MenuItem>())
+
+        row.onDensityOrFontScaleChanged()
+        gutsManager.onDensityOrFontScaleChanged(entry)
+
+        executor.runAllReady()
+
+        gutsManager.closeAndSaveGuts(false, false, false, 0, 0, false)
+
+        verify(guts).closeControls(anyBoolean(), anyBoolean(), anyInt(), anyInt(), anyBoolean())
+
+        // called again by mGutsManager.bindGuts(), in mGutsManager.onDensityOrFontScaleChanged()
+        verify(row, times(2)).setGutsView(any<MenuItem>())
+    }
+
+    @Test
+    fun testAppOpsSettingsIntent_camera() {
+        val row = createTestNotificationRow()
+        val ops = ArraySet<Int>()
+        ops.add(AppOpsManager.OP_CAMERA)
+        gutsManager.startAppOpsSettingsActivity("", 0, ops, row)
+        val captor = argumentCaptor<Intent>()
+        verify(notificationActivityStarter, times(1))
+            .startNotificationGutsIntent(captor.capture(), anyInt(), eq(row))
+        assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.lastValue.action)
+    }
+
+    @Test
+    fun testAppOpsSettingsIntent_mic() {
+        val row = createTestNotificationRow()
+        val ops = ArraySet<Int>()
+        ops.add(AppOpsManager.OP_RECORD_AUDIO)
+        gutsManager.startAppOpsSettingsActivity("", 0, ops, row)
+        val captor = argumentCaptor<Intent>()
+        verify(notificationActivityStarter, times(1))
+            .startNotificationGutsIntent(captor.capture(), anyInt(), eq(row))
+        assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.lastValue.action)
+    }
+
+    @Test
+    fun testAppOpsSettingsIntent_camera_mic() {
+        val row = createTestNotificationRow()
+        val ops = ArraySet<Int>()
+        ops.add(AppOpsManager.OP_CAMERA)
+        ops.add(AppOpsManager.OP_RECORD_AUDIO)
+        gutsManager.startAppOpsSettingsActivity("", 0, ops, row)
+        val captor = argumentCaptor<Intent>()
+        verify(notificationActivityStarter, times(1))
+            .startNotificationGutsIntent(captor.capture(), anyInt(), eq(row))
+        assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.lastValue.action)
+    }
+
+    @Test
+    fun testAppOpsSettingsIntent_overlay() {
+        val row = createTestNotificationRow()
+        val ops = ArraySet<Int>()
+        ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
+        gutsManager.startAppOpsSettingsActivity("", 0, ops, row)
+        val captor = argumentCaptor<Intent>()
+        verify(notificationActivityStarter, times(1))
+            .startNotificationGutsIntent(captor.capture(), anyInt(), eq(row))
+        assertEquals(Settings.ACTION_MANAGE_APP_OVERLAY_PERMISSION, captor.lastValue.action)
+    }
+
+    @Test
+    fun testAppOpsSettingsIntent_camera_mic_overlay() {
+        val row = createTestNotificationRow()
+        val ops = ArraySet<Int>()
+        ops.add(AppOpsManager.OP_CAMERA)
+        ops.add(AppOpsManager.OP_RECORD_AUDIO)
+        ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
+        gutsManager.startAppOpsSettingsActivity("", 0, ops, row)
+        val captor = argumentCaptor<Intent>()
+        verify(notificationActivityStarter, times(1))
+            .startNotificationGutsIntent(captor.capture(), anyInt(), eq(row))
+        assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.lastValue.action)
+    }
+
+    @Test
+    fun testAppOpsSettingsIntent_camera_overlay() {
+        val row = createTestNotificationRow()
+        val ops = ArraySet<Int>()
+        ops.add(AppOpsManager.OP_CAMERA)
+        ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
+        gutsManager.startAppOpsSettingsActivity("", 0, ops, row)
+        val captor = argumentCaptor<Intent>()
+        verify(notificationActivityStarter, times(1))
+            .startNotificationGutsIntent(captor.capture(), anyInt(), eq(row))
+        assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.lastValue.action)
+    }
+
+    @Test
+    fun testAppOpsSettingsIntent_mic_overlay() {
+        val row = createTestNotificationRow()
+        val ops = ArraySet<Int>()
+        ops.add(AppOpsManager.OP_RECORD_AUDIO)
+        ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
+        gutsManager.startAppOpsSettingsActivity("", 0, ops, row)
+        val captor = argumentCaptor<Intent>()
+        verify(notificationActivityStarter, times(1))
+            .startNotificationGutsIntent(captor.capture(), anyInt(), eq(row))
+        assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.lastValue.action)
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun testInitializeNotificationInfoView_highPriority() {
+        val notificationInfoView: NotificationInfo = mock()
+        val row = spy(helper.createRow())
+        val entry = row.entry
+        NotificationEntryHelper.modifyRanking(entry)
+            .setUserSentiment(NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE)
+            .setImportance(NotificationManager.IMPORTANCE_HIGH)
+            .build()
+
+        whenever(row.isNonblockable).thenReturn(false)
+        whenever(highPriorityProvider.isHighPriority(entry)).thenReturn(true)
+        val statusBarNotification = entry.sbn
+        gutsManager.initializeNotificationInfo(row, notificationInfoView)
+
+        verify(notificationInfoView)
+            .bindNotification(
+                any<PackageManager>(),
+                any<INotificationManager>(),
+                eq(onUserInteractionCallback),
+                eq(channelEditorDialogController),
+                eq(statusBarNotification.packageName),
+                any<NotificationChannel>(),
+                eq(entry),
+                any<NotificationInfo.OnSettingsClickListener>(),
+                any<NotificationInfo.OnAppSettingsClickListener>(),
+                any<UiEventLogger>(),
+                /* isDeviceProvisioned = */ eq(false),
+                /* isNonblockable = */ eq(false),
+                /* wasShownHighPriority = */ eq(true),
+                eq(assistantFeedbackController),
+                eq(metricsLogger),
+            )
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun testInitializeNotificationInfoView_PassesAlongProvisionedState() {
+        val notificationInfoView: NotificationInfo = mock()
+        val row = spy(helper.createRow())
+        NotificationEntryHelper.modifyRanking(row.entry)
+            .setUserSentiment(NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE)
+            .build()
+        whenever(row.isNonblockable).thenReturn(false)
+        val statusBarNotification = row.entry.sbn
+        val entry = row.entry
+
+        whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
+
+        gutsManager.initializeNotificationInfo(row, notificationInfoView)
+
+        verify(notificationInfoView)
+            .bindNotification(
+                any<PackageManager>(),
+                any<INotificationManager>(),
+                eq(onUserInteractionCallback),
+                eq(channelEditorDialogController),
+                eq(statusBarNotification.packageName),
+                any<NotificationChannel>(),
+                eq(entry),
+                any<NotificationInfo.OnSettingsClickListener>(),
+                any<NotificationInfo.OnAppSettingsClickListener>(),
+                any<UiEventLogger>(),
+                /* isDeviceProvisioned = */ eq(true),
+                /* isNonblockable = */ eq(false),
+                /* wasShownHighPriority = */ eq(false),
+                eq(assistantFeedbackController),
+                eq(metricsLogger),
+            )
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun testInitializeNotificationInfoView_withInitialAction() {
+        val notificationInfoView: NotificationInfo = mock()
+        val row = spy(helper.createRow())
+        NotificationEntryHelper.modifyRanking(row.entry)
+            .setUserSentiment(NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE)
+            .build()
+        whenever(row.isNonblockable).thenReturn(false)
+        val statusBarNotification = row.entry.sbn
+        val entry = row.entry
+
+        gutsManager.initializeNotificationInfo(row, notificationInfoView)
+
+        verify(notificationInfoView)
+            .bindNotification(
+                any<PackageManager>(),
+                any<INotificationManager>(),
+                eq(onUserInteractionCallback),
+                eq(channelEditorDialogController),
+                eq(statusBarNotification.packageName),
+                any<NotificationChannel>(),
+                eq(entry),
+                any<NotificationInfo.OnSettingsClickListener>(),
+                any<NotificationInfo.OnAppSettingsClickListener>(),
+                any<UiEventLogger>(),
+                /* isDeviceProvisioned = */ eq(false),
+                /* isNonblockable = */ eq(false),
+                /* wasShownHighPriority = */ eq(false),
+                eq(assistantFeedbackController),
+                eq(metricsLogger),
+            )
+    }
+
+    private fun createTestNotificationRow(): ExpandableNotificationRow {
+        val nb =
+            Notification.Builder(mContext, testNotificationChannel.id)
+                .setContentTitle("foo")
+                .setColorized(true)
+                .setColor(Color.RED)
+                .setFlag(Notification.FLAG_CAN_COLORIZE, true)
+                .setSmallIcon(R.drawable.sym_def_app_icon)
+
+        try {
+            val row = helper.createRow(nb.build())
+            NotificationEntryHelper.modifyRanking(row.entry)
+                .setChannel(testNotificationChannel)
+                .build()
+            return row
+        } catch (e: Exception) {
+            fail()
+        }
+    }
+
+    private fun createTestMenuItem(
+        row: ExpandableNotificationRow
+    ): NotificationMenuRowPlugin.MenuItem {
+        val menuRow: NotificationMenuRowPlugin =
+            NotificationMenuRow(mContext, peopleNotificationIdentifier)
+        menuRow.createMenu(row, row.entry.sbn)
+
+        val menuItem = menuRow.getLongpressMenuItem(mContext)
+        assertNotNull(menuItem)
+        return menuItem
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
index 4762527..d665b31 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
@@ -24,7 +24,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImpl
-import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.power.data.repository.FakePowerRepository
 import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -75,9 +75,9 @@
             configurationController,
             context,
             testScope.backgroundScope,
-            mock()
+            mock(),
         )
-    private val configurationInteractor = ConfigurationInteractor(configurationRepository)
+    private val configurationInteractor = ConfigurationInteractorImpl(configurationRepository)
 
     private val unfoldTransitionRepository =
         UnfoldTransitionRepositoryImpl(Optional.of(unfoldTransitionProgressProvider))
@@ -103,7 +103,7 @@
                 unfoldTransitionInteractor,
                 configurationInteractor,
                 animationStatus,
-                powerInteractor
+                powerInteractor,
             )
     }
 
@@ -140,7 +140,7 @@
             updateDisplay(
                 width = INITIAL_DISPLAY_HEIGHT,
                 height = INITIAL_DISPLAY_WIDTH,
-                rotation = ROTATION_90
+                rotation = ROTATION_90,
             )
             runCurrent()
 
@@ -284,7 +284,7 @@
     private fun updateDisplay(
         width: Int = INITIAL_DISPLAY_WIDTH,
         height: Int = INITIAL_DISPLAY_HEIGHT,
-        @Surface.Rotation rotation: Int = ROTATION_0
+        @Surface.Rotation rotation: Int = ROTATION_0,
     ) {
         configuration.windowConfiguration.maxBounds.set(Rect(0, 0, width, height))
         configuration.windowConfiguration.displayRotation = rotation
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModelTest.kt
index e9d88cc..122cfdd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModelTest.kt
@@ -16,16 +16,22 @@
 
 package com.android.systemui.statusbar.notification.stack.ui.viewmodel
 
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
 import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.data.repository.Idle
+import com.android.systemui.scene.data.repository.setSceneTransition
 import com.android.systemui.scene.data.repository.windowRootViewVisibilityRepository
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
 import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
 import com.android.systemui.testKosmos
@@ -33,10 +39,12 @@
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 @SmallTest
-@RunWith(AndroidJUnit4::class)
-class NotificationLoggerViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class NotificationLoggerViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
 
     private val kosmos = testKosmos()
 
@@ -46,9 +54,22 @@
     private val powerInteractor = kosmos.powerInteractor
     private val windowRootViewVisibilityRepository = kosmos.windowRootViewVisibilityRepository
 
-    private val underTest = kosmos.notificationListLoggerViewModel
+    private val underTest by lazy { kosmos.notificationListLoggerViewModel }
+
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf().andSceneContainer()
+        }
+    }
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags)
+    }
 
     @Test
+    @DisableSceneContainer
     fun isLockscreenOrShadeInteractive_deviceActiveAndShadeIsInteractive_true() =
         testScope.runTest {
             powerInteractor.setAwakeForTest()
@@ -60,6 +81,7 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun isLockscreenOrShadeInteractive_deviceIsAsleepAndShadeIsInteractive_false() =
         testScope.runTest {
             powerInteractor.setAsleepForTest()
@@ -71,6 +93,7 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun isLockscreenOrShadeInteractive_deviceActiveAndShadeIsNotInteractive_false() =
         testScope.runTest {
             powerInteractor.setAwakeForTest()
@@ -82,6 +105,54 @@
         }
 
     @Test
+    @EnableSceneContainer
+    fun isLockscreenOrShadeInteractive_deviceAwakeAndShadeIsDisplayed_true() =
+        testScope.runTest {
+            powerInteractor.setAwakeForTest()
+            kosmos.setSceneTransition(Idle(Scenes.Shade))
+
+            val actual by collectLastValue(underTest.isLockscreenOrShadeInteractive)
+
+            assertThat(actual).isTrue()
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun isLockscreenOrShadeInteractive_deviceAwakeAndLockScreenIsDisplayed_true() =
+        testScope.runTest {
+            powerInteractor.setAwakeForTest()
+            kosmos.setSceneTransition(Idle(Scenes.Lockscreen))
+
+            val actual by collectLastValue(underTest.isLockscreenOrShadeInteractive)
+
+            assertThat(actual).isTrue()
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun isLockscreenOrShadeInteractive_deviceIsAsleepOnLockscreen_false() =
+        testScope.runTest {
+            powerInteractor.setAsleepForTest()
+            kosmos.setSceneTransition(Idle(Scenes.Lockscreen))
+
+            val actual by collectLastValue(underTest.isLockscreenOrShadeInteractive)
+
+            assertThat(actual).isFalse()
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun isLockscreenOrShadeInteractive_deviceActiveAndShadeIsClosed() =
+        testScope.runTest {
+            powerInteractor.setAwakeForTest()
+            kosmos.setSceneTransition(Idle(Scenes.Gone))
+
+            val actual by collectLastValue(underTest.isLockscreenOrShadeInteractive)
+
+            assertThat(actual).isFalse()
+        }
+
+    @Test
     fun activeNotifications_hasNotifications() =
         testScope.runTest {
             activeNotificationListRepository.setActiveNotifs(5)
@@ -135,6 +206,7 @@
 
             assertThat(isOnLockScreen).isTrue()
         }
+
     @Test
     fun isOnLockScreen_false() =
         testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
deleted file mode 100644
index 157f818..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ /dev/null
@@ -1,845 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone;
-
-
-import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS;
-import static android.app.StatusBarManager.DISABLE_SYSTEM_INFO;
-
-import static com.android.systemui.Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND;
-import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
-import static com.android.systemui.statusbar.StatusBarState.SHADE;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
-import android.provider.Settings;
-import android.testing.TestableLooper;
-import android.view.LayoutInflater;
-import android.view.View;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.keyguard.CarrierTextController;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.keyguard.logging.KeyguardLogger;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.battery.BatteryMeterViewController;
-import com.android.systemui.flags.DisableSceneContainer;
-import com.android.systemui.flags.EnableSceneContainer;
-import com.android.systemui.kosmos.KosmosJavaAdapter;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.res.R;
-import com.android.systemui.shade.ShadeViewStateProvider;
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore;
-import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
-import com.android.systemui.statusbar.phone.ui.StatusBarIconController;
-import com.android.systemui.statusbar.phone.ui.TintedIconManager;
-import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.UserInfoController;
-import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.settings.SecureSettings;
-import com.android.systemui.util.time.FakeSystemClock;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-@TestableLooper.RunWithLooper
-public class KeyguardStatusBarViewControllerTest extends SysuiTestCase {
-    @Mock
-    private CarrierTextController mCarrierTextController;
-    @Mock
-    private ConfigurationController mConfigurationController;
-    @Mock
-    private SystemStatusAnimationScheduler mAnimationScheduler;
-    @Mock
-    private BatteryController mBatteryController;
-    @Mock
-    private UserInfoController mUserInfoController;
-    @Mock
-    private StatusBarIconController mStatusBarIconController;
-    @Mock
-    private TintedIconManager.Factory mIconManagerFactory;
-    @Mock
-    private TintedIconManager mIconManager;
-    @Mock
-    private BatteryMeterViewController mBatteryMeterViewController;
-    @Mock
-    private KeyguardStateController mKeyguardStateController;
-    @Mock
-    private KeyguardBypassController mKeyguardBypassController;
-    @Mock
-    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    @Mock
-    private BiometricUnlockController mBiometricUnlockController;
-    @Mock
-    private SysuiStatusBarStateController mStatusBarStateController;
-    @Mock
-    private StatusBarContentInsetsProvider mStatusBarContentInsetsProvider;
-    @Mock
-    private StatusBarContentInsetsProviderStore mStatusBarContentInsetsProviderStore;
-    @Mock
-    private UserManager mUserManager;
-    @Mock
-    private StatusBarUserChipViewModel mStatusBarUserChipViewModel;
-    @Captor
-    private ArgumentCaptor<ConfigurationListener> mConfigurationListenerCaptor;
-    @Captor
-    private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardCallbackCaptor;
-    @Mock private SecureSettings mSecureSettings;
-    @Mock private CommandQueue mCommandQueue;
-    @Mock private KeyguardLogger mLogger;
-    @Mock private StatusOverlayHoverListenerFactory mStatusOverlayHoverListenerFactory;
-
-    private TestShadeViewStateProvider mShadeViewStateProvider;
-    private KeyguardStatusBarView mKeyguardStatusBarView;
-    private KeyguardStatusBarViewController mController;
-    private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
-    private final FakeExecutor mBackgroundExecutor = new FakeExecutor(new FakeSystemClock());
-    private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
-
-    @Before
-    public void setup() throws Exception {
-        mShadeViewStateProvider = new TestShadeViewStateProvider();
-
-        MockitoAnnotations.initMocks(this);
-        when(mStatusBarContentInsetsProviderStore.getDefaultDisplay())
-                .thenReturn(mStatusBarContentInsetsProvider);
-        when(mIconManagerFactory.create(any(), any())).thenReturn(mIconManager);
-
-        allowTestableLooperAsMainThread();
-        TestableLooper.get(this).runWithLooper(() -> {
-            mKeyguardStatusBarView =
-                    spy((KeyguardStatusBarView) LayoutInflater.from(mContext)
-                            .inflate(R.layout.keyguard_status_bar, null));
-            when(mKeyguardStatusBarView.getDisplay()).thenReturn(mContext.getDisplay());
-        });
-
-        mController = createController();
-    }
-
-    private KeyguardStatusBarViewController createController() {
-        return new KeyguardStatusBarViewController(
-                mKeyguardStatusBarView,
-                mCarrierTextController,
-                mConfigurationController,
-                mAnimationScheduler,
-                mBatteryController,
-                mUserInfoController,
-                mStatusBarIconController,
-                mIconManagerFactory,
-                mBatteryMeterViewController,
-                mShadeViewStateProvider,
-                mKeyguardStateController,
-                mKeyguardBypassController,
-                mKeyguardUpdateMonitor,
-                mKosmos.getKeyguardStatusBarViewModel(),
-                mBiometricUnlockController,
-                mStatusBarStateController,
-                mStatusBarContentInsetsProviderStore,
-                mUserManager,
-                mStatusBarUserChipViewModel,
-                mSecureSettings,
-                mCommandQueue,
-                mFakeExecutor,
-                mBackgroundExecutor,
-                mLogger,
-                mStatusOverlayHoverListenerFactory,
-                mKosmos.getCommunalSceneInteractor()
-        );
-    }
-
-    @Test
-    @EnableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
-    public void onViewAttached_updateUserSwitcherFlagEnabled_callbacksRegistered() {
-        mController.onViewAttached();
-
-        runAllScheduled();
-        verify(mConfigurationController).addCallback(any());
-        verify(mAnimationScheduler).addCallback(any());
-        verify(mUserInfoController).addCallback(any());
-        verify(mCommandQueue).addCallback(any());
-        verify(mStatusBarIconController).addIconGroup(any());
-        verify(mUserManager).isUserSwitcherEnabled(anyBoolean());
-    }
-
-    @Test
-    @DisableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
-    public void onViewAttached_updateUserSwitcherFlagDisabled_callbacksRegistered() {
-        mController.onViewAttached();
-
-        verify(mConfigurationController).addCallback(any());
-        verify(mAnimationScheduler).addCallback(any());
-        verify(mUserInfoController).addCallback(any());
-        verify(mCommandQueue).addCallback(any());
-        verify(mStatusBarIconController).addIconGroup(any());
-        verify(mUserManager).isUserSwitcherEnabled(anyBoolean());
-    }
-
-    @Test
-    @EnableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
-    public void
-            onConfigurationChanged_updateUserSwitcherFlagEnabled_updatesUserSwitcherVisibility() {
-        mController.onViewAttached();
-        runAllScheduled();
-        verify(mConfigurationController).addCallback(mConfigurationListenerCaptor.capture());
-        clearInvocations(mUserManager);
-        clearInvocations(mKeyguardStatusBarView);
-
-        mConfigurationListenerCaptor.getValue().onConfigChanged(null);
-
-        runAllScheduled();
-        verify(mUserManager).isUserSwitcherEnabled(anyBoolean());
-        verify(mKeyguardStatusBarView).setUserSwitcherEnabled(anyBoolean());
-    }
-
-    @Test
-    @DisableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
-    public void
-            onConfigurationChanged_updateUserSwitcherFlagDisabled_updatesUserSwitcherVisibility() {
-        mController.onViewAttached();
-        verify(mConfigurationController).addCallback(mConfigurationListenerCaptor.capture());
-        clearInvocations(mUserManager);
-        clearInvocations(mKeyguardStatusBarView);
-
-        mConfigurationListenerCaptor.getValue().onConfigChanged(null);
-        verify(mUserManager).isUserSwitcherEnabled(anyBoolean());
-        verify(mKeyguardStatusBarView).setUserSwitcherEnabled(anyBoolean());
-    }
-
-    @Test
-    @EnableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
-    public void
-            onKeyguardVisibilityChanged_userSwitcherFlagEnabled_updatesUserSwitcherVisibility() {
-        mController.onViewAttached();
-        runAllScheduled();
-        verify(mKeyguardUpdateMonitor).registerCallback(mKeyguardCallbackCaptor.capture());
-        clearInvocations(mUserManager);
-        clearInvocations(mKeyguardStatusBarView);
-
-        mKeyguardCallbackCaptor.getValue().onKeyguardVisibilityChanged(true);
-
-        runAllScheduled();
-        verify(mUserManager).isUserSwitcherEnabled(anyBoolean());
-        verify(mKeyguardStatusBarView).setUserSwitcherEnabled(anyBoolean());
-    }
-
-    @Test
-    @DisableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
-    public void
-            onKeyguardVisibilityChanged_userSwitcherFlagDisabled_updatesUserSwitcherVisibility() {
-        mController.onViewAttached();
-        verify(mKeyguardUpdateMonitor).registerCallback(mKeyguardCallbackCaptor.capture());
-        clearInvocations(mUserManager);
-        clearInvocations(mKeyguardStatusBarView);
-
-        mKeyguardCallbackCaptor.getValue().onKeyguardVisibilityChanged(true);
-        verify(mUserManager).isUserSwitcherEnabled(anyBoolean());
-        verify(mKeyguardStatusBarView).setUserSwitcherEnabled(anyBoolean());
-    }
-
-    @Test
-    public void onViewDetached_callbacksUnregistered() {
-        // Set everything up first.
-        mController.onViewAttached();
-
-        mController.onViewDetached();
-
-        verify(mConfigurationController).removeCallback(any());
-        verify(mAnimationScheduler).removeCallback(any());
-        verify(mUserInfoController).removeCallback(any());
-        verify(mCommandQueue).removeCallback(any());
-        verify(mStatusBarIconController).removeIconGroup(any());
-    }
-
-    @Test
-    @DisableSceneContainer
-    public void onViewReAttached_flagOff_iconManagerNotReRegistered() {
-        mController.onViewAttached();
-        mController.onViewDetached();
-        reset(mStatusBarIconController);
-
-        mController.onViewAttached();
-
-        verify(mStatusBarIconController, never()).addIconGroup(any());
-    }
-
-    @Test
-    @EnableSceneContainer
-    public void onViewReAttached_flagOn_iconManagerReRegistered() {
-        mController.onViewAttached();
-        mController.onViewDetached();
-        reset(mStatusBarIconController);
-
-        mController.onViewAttached();
-
-        verify(mStatusBarIconController).addIconGroup(any());
-    }
-
-    @Test
-    @DisableSceneContainer
-    public void setBatteryListening_true_callbackAdded() {
-        mController.setBatteryListening(true);
-
-        verify(mBatteryController).addCallback(any());
-    }
-
-    @Test
-    @DisableSceneContainer
-    public void setBatteryListening_false_callbackRemoved() {
-        // First set to true so that we know setting to false is a change in state.
-        mController.setBatteryListening(true);
-
-        mController.setBatteryListening(false);
-
-        verify(mBatteryController).removeCallback(any());
-    }
-
-    @Test
-    @DisableSceneContainer
-    public void setBatteryListening_trueThenTrue_callbackAddedOnce() {
-        mController.setBatteryListening(true);
-        mController.setBatteryListening(true);
-
-        verify(mBatteryController).addCallback(any());
-    }
-
-    @Test
-    @EnableSceneContainer
-    public void setBatteryListening_true_flagOn_callbackNotAdded() {
-        mController.setBatteryListening(true);
-
-        verify(mBatteryController, never()).addCallback(any());
-    }
-
-    @Test
-    public void updateTopClipping_viewClippingUpdated() {
-        int viewTop = 20;
-        mKeyguardStatusBarView.setTop(viewTop);
-        int notificationPanelTop = 30;
-
-        mController.updateTopClipping(notificationPanelTop);
-
-        assertThat(mKeyguardStatusBarView.getClipBounds().top).isEqualTo(
-                notificationPanelTop - viewTop);
-    }
-
-    @Test
-    public void setNotTopClipping_viewClippingUpdatedToZero() {
-        // Start out with some amount of top clipping.
-        mController.updateTopClipping(50);
-        assertThat(mKeyguardStatusBarView.getClipBounds().top).isGreaterThan(0);
-
-        mController.setNoTopClipping();
-
-        assertThat(mKeyguardStatusBarView.getClipBounds().top).isEqualTo(0);
-    }
-
-    @Test
-    @DisableSceneContainer
-    public void updateViewState_alphaAndVisibilityGiven_viewUpdated() {
-        // Verify the initial values so we know the method triggers changes.
-        assertThat(mKeyguardStatusBarView.getAlpha()).isEqualTo(1f);
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
-
-        float newAlpha = 0.5f;
-        int newVisibility = View.INVISIBLE;
-        mController.updateViewState(newAlpha, newVisibility);
-
-        assertThat(mKeyguardStatusBarView.getAlpha()).isEqualTo(newAlpha);
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(newVisibility);
-    }
-
-    @Test
-    @DisableSceneContainer
-    public void updateViewState_paramVisibleButIsDisabled_viewIsInvisible() {
-        mController.onViewAttached();
-        setDisableSystemIcons(true);
-
-        mController.updateViewState(1f, View.VISIBLE);
-
-        // Since we're disabled, we stay invisible
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
-    }
-
-    @Test
-    @DisableSceneContainer
-    public void updateViewState_notKeyguardState_nothingUpdated() {
-        mController.onViewAttached();
-        updateStateToNotKeyguard();
-
-        float oldAlpha = mKeyguardStatusBarView.getAlpha();
-
-        mController.updateViewState();
-
-        assertThat(mKeyguardStatusBarView.getAlpha()).isEqualTo(oldAlpha);
-    }
-
-    @Test
-    @DisableSceneContainer
-    public void updateViewState_bypassEnabledAndShouldListenForFace_viewHidden() {
-        mController.onViewAttached();
-        updateStateToKeyguard();
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
-
-        when(mKeyguardUpdateMonitor.shouldListenForFace()).thenReturn(true);
-        when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
-        onFinishedGoingToSleep();
-
-        mController.updateViewState();
-
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
-    }
-
-    @Test
-    @DisableSceneContainer
-    public void updateViewState_bypassNotEnabled_viewShown() {
-        mController.onViewAttached();
-        updateStateToKeyguard();
-
-        when(mKeyguardUpdateMonitor.shouldListenForFace()).thenReturn(true);
-        when(mKeyguardBypassController.getBypassEnabled()).thenReturn(false);
-        onFinishedGoingToSleep();
-
-        mController.updateViewState();
-
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
-    }
-
-    @Test
-    @DisableSceneContainer
-    public void updateViewState_shouldNotListenForFace_viewShown() {
-        mController.onViewAttached();
-        updateStateToKeyguard();
-
-        when(mKeyguardUpdateMonitor.shouldListenForFace()).thenReturn(false);
-        when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
-        onFinishedGoingToSleep();
-
-        mController.updateViewState();
-
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
-    }
-
-    @Test
-    @DisableSceneContainer
-    public void updateViewState_panelExpandedHeightZero_viewHidden() {
-        mController.onViewAttached();
-        updateStateToKeyguard();
-
-        mShadeViewStateProvider.setPanelViewExpandedHeight(0);
-
-        mController.updateViewState();
-
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
-    }
-
-    @Test
-    @DisableSceneContainer
-    public void updateViewState_dragProgressOne_viewHidden() {
-        mController.onViewAttached();
-        updateStateToKeyguard();
-
-        mShadeViewStateProvider.setLockscreenShadeDragProgress(1f);
-
-        mController.updateViewState();
-
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
-    }
-
-    @Test
-    @DisableSceneContainer
-    public void updateViewState_disableSystemInfoFalse_viewShown() {
-        mController.onViewAttached();
-        updateStateToKeyguard();
-        setDisableSystemInfo(false);
-
-        mController.updateViewState();
-
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
-    }
-
-    @Test
-    @DisableSceneContainer
-    public void updateViewState_disableSystemInfoTrue_viewHidden() {
-        mController.onViewAttached();
-        updateStateToKeyguard();
-        setDisableSystemInfo(true);
-
-        mController.updateViewState();
-
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
-    }
-
-    @Test
-    @DisableSceneContainer
-    public void updateViewState_disableSystemIconsFalse_viewShown() {
-        mController.onViewAttached();
-        updateStateToKeyguard();
-        setDisableSystemIcons(false);
-
-        mController.updateViewState();
-
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
-    }
-
-    @Test
-    @DisableSceneContainer
-    public void updateViewState_disableSystemIconsTrue_viewHidden() {
-        mController.onViewAttached();
-        updateStateToKeyguard();
-        setDisableSystemIcons(true);
-
-        mController.updateViewState();
-
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
-    }
-
-    @Test
-    @DisableSceneContainer
-    public void updateViewState_dozingTrue_flagOff_viewHidden() {
-        mController.init();
-        mController.onViewAttached();
-        updateStateToKeyguard();
-
-        mController.setDozing(true);
-        mController.updateViewState();
-
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
-    }
-
-    @Test
-    @DisableSceneContainer
-    public void updateViewState_dozingFalse_flagOff_viewShown() {
-        mController.init();
-        mController.onViewAttached();
-        updateStateToKeyguard();
-
-        mController.setDozing(false);
-        mController.updateViewState();
-
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
-    }
-
-    @Test
-    @EnableSceneContainer
-    public void updateViewState_flagOn_doesNothing() {
-        mController.init();
-        mController.onViewAttached();
-        updateStateToKeyguard();
-
-        mKeyguardStatusBarView.setVisibility(View.GONE);
-        mKeyguardStatusBarView.setAlpha(0.456f);
-
-        mController.updateViewState();
-
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.GONE);
-        assertThat(mKeyguardStatusBarView.getAlpha()).isEqualTo(0.456f);
-    }
-
-    @Test
-    @EnableSceneContainer
-    public void updateViewStateWithAlphaAndVis_flagOn_doesNothing() {
-        mController.init();
-        mController.onViewAttached();
-        updateStateToKeyguard();
-
-        mKeyguardStatusBarView.setVisibility(View.GONE);
-        mKeyguardStatusBarView.setAlpha(0.456f);
-
-        mController.updateViewState(0.789f, View.VISIBLE);
-
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.GONE);
-        assertThat(mKeyguardStatusBarView.getAlpha()).isEqualTo(0.456f);
-    }
-
-    @Test
-    @EnableSceneContainer
-    public void setAlpha_flagOn_doesNothing() {
-        mController.init();
-        mController.onViewAttached();
-        updateStateToKeyguard();
-
-        mKeyguardStatusBarView.setAlpha(0.456f);
-
-        mController.setAlpha(0.123f);
-
-        assertThat(mKeyguardStatusBarView.getAlpha()).isEqualTo(0.456f);
-    }
-
-    @Test
-    @EnableSceneContainer
-    public void setDozing_flagOn_doesNothing() {
-        mController.init();
-        mController.onViewAttached();
-        updateStateToKeyguard();
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
-
-        mController.setDozing(true);
-        mController.updateViewState();
-
-        // setDozing(true) should typically cause the view to hide. But since the flag is on, we
-        // should ignore these set dozing calls and stay the same visibility.
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
-    }
-
-    @Test
-    @DisableSceneContainer
-    public void setAlpha_explicitAlpha_setsExplicitAlpha() {
-        mController.onViewAttached();
-        updateStateToKeyguard();
-
-        mController.setAlpha(0.5f);
-
-        assertThat(mKeyguardStatusBarView.getAlpha()).isEqualTo(0.5f);
-    }
-
-    @Test
-    @DisableSceneContainer
-    public void setAlpha_explicitAlpha_thenMinusOneAlpha_setsAlphaBasedOnDefaultCriteria() {
-        mController.onViewAttached();
-        updateStateToKeyguard();
-
-        mController.setAlpha(0.5f);
-        mController.setAlpha(-1f);
-
-        assertThat(mKeyguardStatusBarView.getAlpha()).isGreaterThan(0);
-        assertThat(mKeyguardStatusBarView.getAlpha()).isNotEqualTo(0.5f);
-    }
-
-    // TODO(b/195442899): Add more tests for #updateViewState once CLs are finalized.
-
-    @Test
-    @DisableSceneContainer
-    public void updateForHeadsUp_headsUpShouldBeVisible_viewHidden() {
-        mController.onViewAttached();
-        updateStateToKeyguard();
-        mKeyguardStatusBarView.setVisibility(View.VISIBLE);
-
-        mShadeViewStateProvider.setShouldHeadsUpBeVisible(true);
-        mController.updateForHeadsUp(/* animate= */ false);
-
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
-    }
-
-    @Test
-    @DisableSceneContainer
-    public void updateForHeadsUp_headsUpShouldNotBeVisible_viewShown() {
-        mController.onViewAttached();
-        updateStateToKeyguard();
-
-        // Start with the opposite state.
-        mShadeViewStateProvider.setShouldHeadsUpBeVisible(true);
-        mController.updateForHeadsUp(/* animate= */ false);
-
-        mShadeViewStateProvider.setShouldHeadsUpBeVisible(false);
-        mController.updateForHeadsUp(/* animate= */ false);
-
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
-    }
-
-    @Test
-    public void testNewUserSwitcherDisablesAvatar_newUiOn() {
-        // GIVEN the status bar user switcher chip is enabled
-        when(mStatusBarUserChipViewModel.getChipEnabled()).thenReturn(true);
-
-        // WHEN the controller is created
-        mController = createController();
-
-        // THEN keyguard status bar view avatar is disabled
-        assertThat(mKeyguardStatusBarView.isKeyguardUserAvatarEnabled()).isFalse();
-    }
-
-    @Test
-    public void testNewUserSwitcherDisablesAvatar_newUiOff() {
-        // GIVEN the status bar user switcher chip is disabled
-        when(mStatusBarUserChipViewModel.getChipEnabled()).thenReturn(false);
-
-        // WHEN the controller is created
-        mController = createController();
-
-        // THEN keyguard status bar view avatar is enabled
-        assertThat(mKeyguardStatusBarView.isKeyguardUserAvatarEnabled()).isTrue();
-    }
-
-    @Test
-    public void testBlockedIcons_obeysSettingForVibrateIcon_settingOff() {
-        String str = mContext.getString(com.android.internal.R.string.status_bar_volume);
-
-        // GIVEN the setting is off
-        when(mSecureSettings.getInt(Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON, 0))
-                .thenReturn(0);
-
-        // WHEN CollapsedStatusBarFragment builds the blocklist
-        mController.updateBlockedIcons();
-
-        // THEN status_bar_volume SHOULD be present in the list
-        boolean contains = mController.getBlockedIcons().contains(str);
-        assertTrue(contains);
-    }
-
-    @Test
-    public void testBlockedIcons_obeysSettingForVibrateIcon_settingOn() {
-        String str = mContext.getString(com.android.internal.R.string.status_bar_volume);
-
-        // GIVEN the setting is ON
-        when(mSecureSettings.getIntForUser(Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON, 0,
-                UserHandle.USER_CURRENT))
-                .thenReturn(1);
-
-        // WHEN CollapsedStatusBarFragment builds the blocklist
-        mController.updateBlockedIcons();
-
-        // THEN status_bar_volume SHOULD NOT be present in the list
-        boolean contains = mController.getBlockedIcons().contains(str);
-        assertFalse(contains);
-    }
-
-    private void updateStateToNotKeyguard() {
-        updateStatusBarState(SHADE);
-    }
-
-    private void updateStateToKeyguard() {
-        updateStatusBarState(KEYGUARD);
-    }
-
-    private void updateStatusBarState(int state) {
-        ArgumentCaptor<StatusBarStateController.StateListener> statusBarStateListenerCaptor =
-                ArgumentCaptor.forClass(StatusBarStateController.StateListener.class);
-        verify(mStatusBarStateController).addCallback(statusBarStateListenerCaptor.capture());
-        StatusBarStateController.StateListener callback = statusBarStateListenerCaptor.getValue();
-
-        callback.onStateChanged(state);
-    }
-
-    @Test
-    @DisableSceneContainer
-    public void animateKeyguardStatusBarIn_isDisabled_viewStillHidden() {
-        mController.onViewAttached();
-        updateStateToKeyguard();
-        setDisableSystemInfo(true);
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
-
-        mController.animateKeyguardStatusBarIn();
-
-        // Since we're disabled, we don't actually animate in and stay invisible
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
-    }
-
-    /**
-     * Calls {@link com.android.keyguard.KeyguardUpdateMonitorCallback#onFinishedGoingToSleep(int)}
-     * to ensure values are updated properly.
-     */
-    private void onFinishedGoingToSleep() {
-        ArgumentCaptor<KeyguardUpdateMonitorCallback> keyguardUpdateCallbackCaptor =
-                ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class);
-        verify(mKeyguardUpdateMonitor).registerCallback(keyguardUpdateCallbackCaptor.capture());
-        KeyguardUpdateMonitorCallback callback = keyguardUpdateCallbackCaptor.getValue();
-
-        callback.onFinishedGoingToSleep(0);
-    }
-
-    private void setDisableSystemInfo(boolean disabled) {
-        CommandQueue.Callbacks callback = getCommandQueueCallback();
-        int disabled1 = disabled ? DISABLE_SYSTEM_INFO : 0;
-        callback.disable(mContext.getDisplayId(), disabled1, 0, false);
-    }
-
-    private void setDisableSystemIcons(boolean disabled) {
-        CommandQueue.Callbacks callback = getCommandQueueCallback();
-        int disabled2 = disabled ? DISABLE2_SYSTEM_ICONS : 0;
-        callback.disable(mContext.getDisplayId(), 0, disabled2, false);
-    }
-
-    private CommandQueue.Callbacks getCommandQueueCallback() {
-        ArgumentCaptor<CommandQueue.Callbacks> captor =
-                ArgumentCaptor.forClass(CommandQueue.Callbacks.class);
-        verify(mCommandQueue).addCallback(captor.capture());
-        return captor.getValue();
-    }
-
-    private void runAllScheduled() {
-        mBackgroundExecutor.runAllReady();
-        mFakeExecutor.runAllReady();
-    }
-
-    private static class TestShadeViewStateProvider
-            implements ShadeViewStateProvider {
-
-        TestShadeViewStateProvider() {}
-
-        private float mPanelViewExpandedHeight = 100f;
-        private boolean mShouldHeadsUpBeVisible = false;
-        private float mLockscreenShadeDragProgress = 0f;
-
-        @Override
-        public float getPanelViewExpandedHeight() {
-            return mPanelViewExpandedHeight;
-        }
-
-        @Override
-        public boolean shouldHeadsUpBeVisible() {
-            return mShouldHeadsUpBeVisible;
-        }
-
-        @Override
-        public float getLockscreenShadeDragProgress() {
-            return mLockscreenShadeDragProgress;
-        }
-
-        public void setPanelViewExpandedHeight(float panelViewExpandedHeight) {
-            this.mPanelViewExpandedHeight = panelViewExpandedHeight;
-        }
-
-        public void setShouldHeadsUpBeVisible(boolean shouldHeadsUpBeVisible) {
-            this.mShouldHeadsUpBeVisible = shouldHeadsUpBeVisible;
-        }
-
-        public void setLockscreenShadeDragProgress(float lockscreenShadeDragProgress) {
-            this.mLockscreenShadeDragProgress = lockscreenShadeDragProgress;
-        }
-    }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt
new file mode 100644
index 0000000..b815c6c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt
@@ -0,0 +1,867 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.phone
+
+import android.app.StatusBarManager
+import android.graphics.Insets
+import android.os.UserHandle
+import android.os.UserManager
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.provider.Settings
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import android.testing.ViewUtils
+import android.view.LayoutInflater
+import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.CarrierTextController
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.keyguard.logging.KeyguardLogger
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
+import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.lockscreenToGlanceableHubTransitionViewModel
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.statusbar.statusBarStateController
+import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeViewStateProvider
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore
+import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler
+import com.android.systemui.statusbar.phone.ui.StatusBarIconController
+import com.android.systemui.statusbar.phone.ui.TintedIconManager
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.policy.UserInfoController
+import com.android.systemui.statusbar.ui.viewmodel.keyguardStatusBarViewModel
+import com.android.systemui.statusbar.ui.viewmodel.statusBarUserChipViewModel
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@RunWithLooper(setAsMainLooper = true)
+class KeyguardStatusBarViewControllerTest : SysuiTestCase() {
+    private lateinit var kosmos: Kosmos
+    private lateinit var testScope: TestScope
+
+    @Mock private lateinit var carrierTextController: CarrierTextController
+
+    @Mock private lateinit var configurationController: ConfigurationController
+
+    @Mock private lateinit var animationScheduler: SystemStatusAnimationScheduler
+
+    @Mock private lateinit var batteryController: BatteryController
+
+    @Mock private lateinit var userInfoController: UserInfoController
+
+    @Mock private lateinit var statusBarIconController: StatusBarIconController
+
+    @Mock private lateinit var iconManagerFactory: TintedIconManager.Factory
+
+    @Mock private lateinit var iconManager: TintedIconManager
+
+    @Mock private lateinit var batteryMeterViewController: BatteryMeterViewController
+
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+
+    @Mock private lateinit var keyguardBypassController: KeyguardBypassController
+
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+
+    @Mock private lateinit var biometricUnlockController: BiometricUnlockController
+
+    @Mock
+    private lateinit var statusBarContentInsetsProviderStore: StatusBarContentInsetsProviderStore
+
+    @Mock private lateinit var userManager: UserManager
+
+    @Captor
+    private lateinit var configurationListenerCaptor:
+        ArgumentCaptor<ConfigurationController.ConfigurationListener>
+
+    @Captor
+    private lateinit var keyguardCallbackCaptor: ArgumentCaptor<KeyguardUpdateMonitorCallback>
+
+    @Mock private lateinit var secureSettings: SecureSettings
+
+    @Mock private lateinit var commandQueue: CommandQueue
+
+    @Mock private lateinit var logger: KeyguardLogger
+
+    @Mock private lateinit var statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory
+
+    private lateinit var shadeViewStateProvider: TestShadeViewStateProvider
+
+    private lateinit var keyguardStatusBarView: KeyguardStatusBarView
+    private lateinit var controller: KeyguardStatusBarViewController
+    private val fakeExecutor = FakeExecutor(FakeSystemClock())
+    private val backgroundExecutor = FakeExecutor(FakeSystemClock())
+
+    private lateinit var looper: TestableLooper
+
+    @Before
+    @Throws(Exception::class)
+    fun setup() {
+        looper = TestableLooper.get(this)
+        kosmos = testKosmos()
+        testScope = kosmos.testScope
+        shadeViewStateProvider = TestShadeViewStateProvider()
+
+        Mockito.`when`(
+                kosmos.statusBarContentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()
+            )
+            .thenReturn(Insets.of(0, 0, 0, 0))
+
+        MockitoAnnotations.initMocks(this)
+
+        Mockito.`when`(iconManagerFactory.create(ArgumentMatchers.any(), ArgumentMatchers.any()))
+            .thenReturn(iconManager)
+        Mockito.`when`(statusBarContentInsetsProviderStore.defaultDisplay)
+            .thenReturn(kosmos.statusBarContentInsetsProvider)
+        allowTestableLooperAsMainThread()
+        looper.runWithLooper {
+            keyguardStatusBarView =
+                Mockito.spy(
+                    LayoutInflater.from(mContext).inflate(R.layout.keyguard_status_bar, null)
+                        as KeyguardStatusBarView
+                )
+            Mockito.`when`(keyguardStatusBarView.getDisplay()).thenReturn(mContext.display)
+        }
+
+        controller = createController()
+    }
+
+    private fun createController(): KeyguardStatusBarViewController {
+        return KeyguardStatusBarViewController(
+            kosmos.testDispatcher,
+            keyguardStatusBarView,
+            carrierTextController,
+            configurationController,
+            animationScheduler,
+            batteryController,
+            userInfoController,
+            statusBarIconController,
+            iconManagerFactory,
+            batteryMeterViewController,
+            shadeViewStateProvider,
+            keyguardStateController,
+            keyguardBypassController,
+            keyguardUpdateMonitor,
+            kosmos.keyguardStatusBarViewModel,
+            biometricUnlockController,
+            kosmos.statusBarStateController,
+            statusBarContentInsetsProviderStore,
+            userManager,
+            kosmos.statusBarUserChipViewModel,
+            secureSettings,
+            commandQueue,
+            fakeExecutor,
+            backgroundExecutor,
+            logger,
+            statusOverlayHoverListenerFactory,
+            kosmos.communalSceneInteractor,
+            kosmos.glanceableHubToLockscreenTransitionViewModel,
+            kosmos.lockscreenToGlanceableHubTransitionViewModel,
+        )
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+    fun onViewAttached_updateUserSwitcherFlagEnabled_callbacksRegistered() {
+        controller.onViewAttached()
+
+        runAllScheduled()
+        Mockito.verify(configurationController).addCallback(ArgumentMatchers.any())
+        Mockito.verify(animationScheduler).addCallback(ArgumentMatchers.any())
+        Mockito.verify(userInfoController).addCallback(ArgumentMatchers.any())
+        Mockito.verify(commandQueue).addCallback(ArgumentMatchers.any())
+        Mockito.verify(statusBarIconController).addIconGroup(ArgumentMatchers.any())
+        Mockito.verify(userManager).isUserSwitcherEnabled(ArgumentMatchers.anyBoolean())
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+    fun onViewAttached_updateUserSwitcherFlagDisabled_callbacksRegistered() {
+        controller.onViewAttached()
+
+        Mockito.verify(configurationController).addCallback(ArgumentMatchers.any())
+        Mockito.verify(animationScheduler).addCallback(ArgumentMatchers.any())
+        Mockito.verify(userInfoController).addCallback(ArgumentMatchers.any())
+        Mockito.verify(commandQueue).addCallback(ArgumentMatchers.any())
+        Mockito.verify(statusBarIconController).addIconGroup(ArgumentMatchers.any())
+        Mockito.verify(userManager).isUserSwitcherEnabled(ArgumentMatchers.anyBoolean())
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+    fun onConfigurationChanged_updateUserSwitcherFlagEnabled_updatesUserSwitcherVisibility() {
+        controller.onViewAttached()
+        runAllScheduled()
+        Mockito.verify(configurationController).addCallback(configurationListenerCaptor.capture())
+        Mockito.clearInvocations(userManager)
+        Mockito.clearInvocations(keyguardStatusBarView)
+
+        configurationListenerCaptor.value.onConfigChanged(null)
+
+        runAllScheduled()
+        Mockito.verify(userManager).isUserSwitcherEnabled(ArgumentMatchers.anyBoolean())
+        Mockito.verify(keyguardStatusBarView).setUserSwitcherEnabled(ArgumentMatchers.anyBoolean())
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+    fun onConfigurationChanged_updateUserSwitcherFlagDisabled_updatesUserSwitcherVisibility() {
+        controller.onViewAttached()
+        Mockito.verify(configurationController).addCallback(configurationListenerCaptor.capture())
+        Mockito.clearInvocations(userManager)
+        Mockito.clearInvocations(keyguardStatusBarView)
+
+        configurationListenerCaptor.value.onConfigChanged(null)
+        Mockito.verify(userManager).isUserSwitcherEnabled(ArgumentMatchers.anyBoolean())
+        Mockito.verify(keyguardStatusBarView).setUserSwitcherEnabled(ArgumentMatchers.anyBoolean())
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+    fun onKeyguardVisibilityChanged_userSwitcherFlagEnabled_updatesUserSwitcherVisibility() {
+        controller.onViewAttached()
+        runAllScheduled()
+        Mockito.verify(keyguardUpdateMonitor).registerCallback(keyguardCallbackCaptor.capture())
+        Mockito.clearInvocations(userManager)
+        Mockito.clearInvocations(keyguardStatusBarView)
+
+        keyguardCallbackCaptor.value.onKeyguardVisibilityChanged(true)
+
+        runAllScheduled()
+        Mockito.verify(userManager).isUserSwitcherEnabled(ArgumentMatchers.anyBoolean())
+        Mockito.verify(keyguardStatusBarView).setUserSwitcherEnabled(ArgumentMatchers.anyBoolean())
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+    fun onKeyguardVisibilityChanged_userSwitcherFlagDisabled_updatesUserSwitcherVisibility() {
+        controller.onViewAttached()
+        Mockito.verify(keyguardUpdateMonitor).registerCallback(keyguardCallbackCaptor.capture())
+        Mockito.clearInvocations(userManager)
+        Mockito.clearInvocations(keyguardStatusBarView)
+
+        keyguardCallbackCaptor.value.onKeyguardVisibilityChanged(true)
+        Mockito.verify(userManager).isUserSwitcherEnabled(ArgumentMatchers.anyBoolean())
+        Mockito.verify(keyguardStatusBarView).setUserSwitcherEnabled(ArgumentMatchers.anyBoolean())
+    }
+
+    @Test
+    fun onViewDetached_callbacksUnregistered() {
+        // Set everything up first.
+        controller.onViewAttached()
+
+        controller.onViewDetached()
+
+        Mockito.verify(configurationController).removeCallback(ArgumentMatchers.any())
+        Mockito.verify(animationScheduler).removeCallback(ArgumentMatchers.any())
+        Mockito.verify(userInfoController).removeCallback(ArgumentMatchers.any())
+        Mockito.verify(commandQueue).removeCallback(ArgumentMatchers.any())
+        Mockito.verify(statusBarIconController).removeIconGroup(ArgumentMatchers.any())
+    }
+
+    @Test
+    @DisableSceneContainer
+    fun onViewReAttached_flagOff_iconManagerNotReRegistered() {
+        controller.onViewAttached()
+        controller.onViewDetached()
+        Mockito.reset(statusBarIconController)
+
+        controller.onViewAttached()
+
+        Mockito.verify(statusBarIconController, Mockito.never())
+            .addIconGroup(ArgumentMatchers.any())
+    }
+
+    @Test
+    @EnableSceneContainer
+    fun onViewReAttached_flagOn_iconManagerReRegistered() {
+        controller.onViewAttached()
+        controller.onViewDetached()
+        Mockito.reset(statusBarIconController)
+
+        controller.onViewAttached()
+
+        Mockito.verify(statusBarIconController).addIconGroup(ArgumentMatchers.any())
+    }
+
+    @Test
+    @DisableSceneContainer
+    fun setBatteryListening_true_callbackAdded() {
+        controller.setBatteryListening(true)
+
+        Mockito.verify(batteryController).addCallback(ArgumentMatchers.any())
+    }
+
+    @Test
+    @DisableSceneContainer
+    fun setBatteryListening_false_callbackRemoved() {
+        // First set to true so that we know setting to false is a change in state.
+        controller.setBatteryListening(true)
+
+        controller.setBatteryListening(false)
+
+        Mockito.verify(batteryController).removeCallback(ArgumentMatchers.any())
+    }
+
+    @Test
+    @DisableSceneContainer
+    fun setBatteryListening_trueThenTrue_callbackAddedOnce() {
+        controller.setBatteryListening(true)
+        controller.setBatteryListening(true)
+
+        Mockito.verify(batteryController).addCallback(ArgumentMatchers.any())
+    }
+
+    @Test
+    @EnableSceneContainer
+    fun setBatteryListening_true_flagOn_callbackNotAdded() {
+        controller.setBatteryListening(true)
+
+        Mockito.verify(batteryController, Mockito.never()).addCallback(ArgumentMatchers.any())
+    }
+
+    @Test
+    fun updateTopClipping_viewClippingUpdated() {
+        val viewTop = 20
+        keyguardStatusBarView.top = viewTop
+        val notificationPanelTop = 30
+
+        controller.updateTopClipping(notificationPanelTop)
+
+        Truth.assertThat(keyguardStatusBarView.clipBounds.top)
+            .isEqualTo(notificationPanelTop - viewTop)
+    }
+
+    @Test
+    fun setNotTopClipping_viewClippingUpdatedToZero() {
+        // Start out with some amount of top clipping.
+        controller.updateTopClipping(50)
+        Truth.assertThat(keyguardStatusBarView.clipBounds.top).isGreaterThan(0)
+
+        controller.setNoTopClipping()
+
+        Truth.assertThat(keyguardStatusBarView.clipBounds.top).isEqualTo(0)
+    }
+
+    @Test
+    @DisableSceneContainer
+    fun updateViewState_alphaAndVisibilityGiven_viewUpdated() {
+        // Verify the initial values so we know the method triggers changes.
+        Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(1f)
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+
+        val newAlpha = 0.5f
+        val newVisibility = View.INVISIBLE
+        controller.updateViewState(newAlpha, newVisibility)
+
+        Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(newAlpha)
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(newVisibility)
+    }
+
+    @Test
+    @DisableSceneContainer
+    fun updateViewState_paramVisibleButIsDisabled_viewIsInvisible() {
+        controller.onViewAttached()
+        setDisableSystemIcons(true)
+
+        controller.updateViewState(1f, View.VISIBLE)
+
+        // Since we're disabled, we stay invisible
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+    }
+
+    @Test
+    @DisableSceneContainer
+    fun updateViewState_notKeyguardState_nothingUpdated() {
+        controller.onViewAttached()
+        updateStateToNotKeyguard()
+
+        val oldAlpha = keyguardStatusBarView.alpha
+
+        controller.updateViewState()
+
+        Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(oldAlpha)
+    }
+
+    @Test
+    @DisableSceneContainer
+    fun updateViewState_bypassEnabledAndShouldListenForFace_viewHidden() {
+        controller.onViewAttached()
+        updateStateToKeyguard()
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+
+        Mockito.`when`(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(true)
+        Mockito.`when`(keyguardBypassController.bypassEnabled).thenReturn(true)
+        onFinishedGoingToSleep()
+
+        controller.updateViewState()
+
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+    }
+
+    @Test
+    @DisableSceneContainer
+    fun updateViewState_bypassNotEnabled_viewShown() {
+        controller.onViewAttached()
+        updateStateToKeyguard()
+
+        Mockito.`when`(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(true)
+        Mockito.`when`(keyguardBypassController.bypassEnabled).thenReturn(false)
+        onFinishedGoingToSleep()
+
+        controller.updateViewState()
+
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+    }
+
+    @Test
+    @DisableSceneContainer
+    fun updateViewState_shouldNotListenForFace_viewShown() {
+        controller.onViewAttached()
+        updateStateToKeyguard()
+
+        Mockito.`when`(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(false)
+        Mockito.`when`(keyguardBypassController.bypassEnabled).thenReturn(true)
+        onFinishedGoingToSleep()
+
+        controller.updateViewState()
+
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+    }
+
+    @Test
+    @DisableSceneContainer
+    fun updateViewState_panelExpandedHeightZero_viewHidden() {
+        controller.onViewAttached()
+        updateStateToKeyguard()
+
+        shadeViewStateProvider.panelViewExpandedHeight = 0f
+
+        controller.updateViewState()
+
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+    }
+
+    @Test
+    @DisableSceneContainer
+    fun updateViewState_dragProgressOne_viewHidden() {
+        controller.onViewAttached()
+        updateStateToKeyguard()
+
+        shadeViewStateProvider.lockscreenShadeDragProgress = 1f
+
+        controller.updateViewState()
+
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+    }
+
+    @Test
+    @DisableSceneContainer
+    fun updateViewState_disableSystemInfoFalse_viewShown() {
+        controller.onViewAttached()
+        updateStateToKeyguard()
+        setDisableSystemInfo(false)
+
+        controller.updateViewState()
+
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+    }
+
+    @Test
+    @DisableSceneContainer
+    fun updateViewState_disableSystemInfoTrue_viewHidden() {
+        controller.onViewAttached()
+        updateStateToKeyguard()
+        setDisableSystemInfo(true)
+
+        controller.updateViewState()
+
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+    }
+
+    @Test
+    @DisableSceneContainer
+    fun updateViewState_disableSystemIconsFalse_viewShown() {
+        controller.onViewAttached()
+        updateStateToKeyguard()
+        setDisableSystemIcons(false)
+
+        controller.updateViewState()
+
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+    }
+
+    @Test
+    @DisableSceneContainer
+    fun updateViewState_disableSystemIconsTrue_viewHidden() {
+        controller.onViewAttached()
+        updateStateToKeyguard()
+        setDisableSystemIcons(true)
+
+        controller.updateViewState()
+
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+    }
+
+    @Test
+    @DisableSceneContainer
+    fun updateViewState_dozingTrue_flagOff_viewHidden() {
+        controller.init()
+        controller.onViewAttached()
+        updateStateToKeyguard()
+
+        controller.setDozing(true)
+        controller.updateViewState()
+
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+    }
+
+    @Test
+    @DisableSceneContainer
+    fun updateViewState_dozingFalse_flagOff_viewShown() {
+        controller.init()
+        controller.onViewAttached()
+        updateStateToKeyguard()
+
+        controller.setDozing(false)
+        controller.updateViewState()
+
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+    }
+
+    @Test
+    @EnableSceneContainer
+    fun updateViewState_flagOn_doesNothing() {
+        controller.init()
+        controller.onViewAttached()
+        updateStateToKeyguard()
+
+        keyguardStatusBarView.visibility = View.GONE
+        keyguardStatusBarView.alpha = 0.456f
+
+        controller.updateViewState()
+
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.GONE)
+        Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(0.456f)
+    }
+
+    @Test
+    @EnableSceneContainer
+    fun updateViewStateWithAlphaAndVis_flagOn_doesNothing() {
+        controller.init()
+        controller.onViewAttached()
+        updateStateToKeyguard()
+
+        keyguardStatusBarView.visibility = View.GONE
+        keyguardStatusBarView.alpha = 0.456f
+
+        controller.updateViewState(0.789f, View.VISIBLE)
+
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.GONE)
+        Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(0.456f)
+    }
+
+    @Test
+    @EnableSceneContainer
+    fun setAlpha_flagOn_doesNothing() {
+        controller.init()
+        controller.onViewAttached()
+        updateStateToKeyguard()
+
+        keyguardStatusBarView.alpha = 0.456f
+
+        controller.setAlpha(0.123f)
+
+        Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(0.456f)
+    }
+
+    @Test
+    @EnableSceneContainer
+    fun setDozing_flagOn_doesNothing() {
+        controller.init()
+        controller.onViewAttached()
+        updateStateToKeyguard()
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+
+        controller.setDozing(true)
+        controller.updateViewState()
+
+        // setDozing(true) should typically cause the view to hide. But since the flag is on, we
+        // should ignore these set dozing calls and stay the same visibility.
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+    }
+
+    @Test
+    @DisableSceneContainer
+    fun setAlpha_explicitAlpha_setsExplicitAlpha() {
+        controller.onViewAttached()
+        updateStateToKeyguard()
+
+        controller.setAlpha(0.5f)
+
+        Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(0.5f)
+    }
+
+    @Test
+    @DisableSceneContainer
+    fun setAlpha_explicitAlpha_thenMinusOneAlpha_setsAlphaBasedOnDefaultCriteria() {
+        controller.onViewAttached()
+        updateStateToKeyguard()
+
+        controller.setAlpha(0.5f)
+        controller.setAlpha(-1f)
+
+        Truth.assertThat(keyguardStatusBarView.alpha).isGreaterThan(0)
+        Truth.assertThat(keyguardStatusBarView.alpha).isNotEqualTo(0.5f)
+    }
+
+    // TODO(b/195442899): Add more tests for #updateViewState once CLs are finalized.
+    @Test
+    @DisableSceneContainer
+    fun updateForHeadsUp_headsUpShouldBeVisible_viewHidden() {
+        controller.onViewAttached()
+        updateStateToKeyguard()
+        keyguardStatusBarView.visibility = View.VISIBLE
+
+        shadeViewStateProvider.setShouldHeadsUpBeVisible(true)
+        controller.updateForHeadsUp(/* animate= */ false)
+
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+    }
+
+    @Test
+    @DisableSceneContainer
+    fun updateForHeadsUp_headsUpShouldNotBeVisible_viewShown() {
+        controller.onViewAttached()
+        updateStateToKeyguard()
+
+        // Start with the opposite state.
+        shadeViewStateProvider.setShouldHeadsUpBeVisible(true)
+        controller.updateForHeadsUp(/* animate= */ false)
+
+        shadeViewStateProvider.setShouldHeadsUpBeVisible(false)
+        controller.updateForHeadsUp(/* animate= */ false)
+
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+    }
+
+    @Test
+    fun testNewUserSwitcherDisablesAvatar_newUiOn() =
+        testScope.runTest {
+            // GIVEN the status bar user switcher chip is enabled
+            kosmos.fakeUserRepository.isStatusBarUserChipEnabled = true
+
+            // WHEN the controller is created
+            controller = createController()
+
+            // THEN keyguard status bar view avatar is disabled
+            Truth.assertThat(keyguardStatusBarView.isKeyguardUserAvatarEnabled).isFalse()
+        }
+
+    @Test
+    fun testNewUserSwitcherDisablesAvatar_newUiOff() {
+        // GIVEN the status bar user switcher chip is disabled
+        kosmos.fakeUserRepository.isStatusBarUserChipEnabled = false
+
+        // WHEN the controller is created
+        controller = createController()
+
+        // THEN keyguard status bar view avatar is enabled
+        Truth.assertThat(keyguardStatusBarView.isKeyguardUserAvatarEnabled).isTrue()
+    }
+
+    @Test
+    fun testBlockedIcons_obeysSettingForVibrateIcon_settingOff() {
+        val str = mContext.getString(com.android.internal.R.string.status_bar_volume)
+
+        // GIVEN the setting is off
+        Mockito.`when`(secureSettings.getInt(Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON, 0))
+            .thenReturn(0)
+
+        // WHEN CollapsedStatusBarFragment builds the blocklist
+        controller.updateBlockedIcons()
+
+        // THEN status_bar_volume SHOULD be present in the list
+        val contains = controller.blockedIcons.contains(str)
+        Assert.assertTrue(contains)
+    }
+
+    @Test
+    fun testBlockedIcons_obeysSettingForVibrateIcon_settingOn() {
+        val str = mContext.getString(com.android.internal.R.string.status_bar_volume)
+
+        // GIVEN the setting is ON
+        Mockito.`when`(
+                secureSettings.getIntForUser(
+                    Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON,
+                    0,
+                    UserHandle.USER_CURRENT,
+                )
+            )
+            .thenReturn(1)
+
+        // WHEN CollapsedStatusBarFragment builds the blocklist
+        controller.updateBlockedIcons()
+
+        // THEN status_bar_volume SHOULD NOT be present in the list
+        val contains = controller.blockedIcons.contains(str)
+        Assert.assertFalse(contains)
+    }
+
+    private fun updateStateToNotKeyguard() {
+        updateStatusBarState(StatusBarState.SHADE)
+    }
+
+    private fun updateStateToKeyguard() {
+        updateStatusBarState(StatusBarState.KEYGUARD)
+    }
+
+    private fun updateStatusBarState(state: Int) {
+        kosmos.statusBarStateController.setState(state)
+    }
+
+    @Test
+    @DisableSceneContainer
+    fun animateKeyguardStatusBarIn_isDisabled_viewStillHidden() {
+        controller.onViewAttached()
+        updateStateToKeyguard()
+        setDisableSystemInfo(true)
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+
+        controller.animateKeyguardStatusBarIn()
+
+        // Since we're disabled, we don't actually animate in and stay invisible
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+    }
+
+    @Test
+    fun animateToGlanceableHub_affectsAlpha() =
+        testScope.runTest {
+            controller.init()
+            val transitionAlphaAmount = .5f
+            ViewUtils.attachView(keyguardStatusBarView)
+            looper.processAllMessages()
+            updateStateToKeyguard()
+            kosmos.fakeCommunalSceneRepository.snapToScene(CommunalScenes.Communal)
+            runCurrent()
+            controller.updateCommunalAlphaTransition(transitionAlphaAmount)
+            Truth.assertThat(keyguardStatusBarView.getAlpha()).isEqualTo(transitionAlphaAmount)
+        }
+
+    @Test
+    fun animateToGlanceableHub_alphaResetOnCommunalNotShowing() =
+        testScope.runTest {
+            controller.init()
+            val transitionAlphaAmount = .5f
+            ViewUtils.attachView(keyguardStatusBarView)
+            looper.processAllMessages()
+            updateStateToKeyguard()
+            kosmos.fakeCommunalSceneRepository.snapToScene(CommunalScenes.Communal)
+            runCurrent()
+            controller.updateCommunalAlphaTransition(transitionAlphaAmount)
+            kosmos.fakeCommunalSceneRepository.snapToScene(CommunalScenes.Blank)
+            runCurrent()
+            Truth.assertThat(keyguardStatusBarView.getAlpha()).isNotEqualTo(transitionAlphaAmount)
+        }
+
+    /**
+     * Calls [com.android.keyguard.KeyguardUpdateMonitorCallback.onFinishedGoingToSleep] to ensure
+     * values are updated properly.
+     */
+    private fun onFinishedGoingToSleep() {
+        val keyguardUpdateCallbackCaptor =
+            ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
+        Mockito.verify(keyguardUpdateMonitor)
+            .registerCallback(keyguardUpdateCallbackCaptor.capture())
+        val callback = keyguardUpdateCallbackCaptor.value
+
+        callback.onFinishedGoingToSleep(0)
+    }
+
+    private fun setDisableSystemInfo(disabled: Boolean) {
+        val callback = commandQueueCallback
+        val disabled1 = if (disabled) StatusBarManager.DISABLE_SYSTEM_INFO else 0
+        callback.disable(mContext.displayId, disabled1, 0, false)
+    }
+
+    private fun setDisableSystemIcons(disabled: Boolean) {
+        val callback = commandQueueCallback
+        val disabled2 = if (disabled) StatusBarManager.DISABLE2_SYSTEM_ICONS else 0
+        callback.disable(mContext.displayId, 0, disabled2, false)
+    }
+
+    private val commandQueueCallback: CommandQueue.Callbacks
+        get() {
+            val captor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
+            Mockito.verify(commandQueue).addCallback(captor.capture())
+            return captor.value
+        }
+
+    private fun runAllScheduled() {
+        backgroundExecutor.runAllReady()
+        fakeExecutor.runAllReady()
+    }
+
+    private class TestShadeViewStateProvider : ShadeViewStateProvider {
+        override var panelViewExpandedHeight: Float = 100f
+        private var mShouldHeadsUpBeVisible = false
+        override var lockscreenShadeDragProgress: Float = 0f
+
+        override fun shouldHeadsUpBeVisible(): Boolean {
+            return mShouldHeadsUpBeVisible
+        }
+
+        fun setShouldHeadsUpBeVisible(shouldHeadsUpBeVisible: Boolean) {
+            this.mShouldHeadsUpBeVisible = shouldHeadsUpBeVisible
+        }
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
index 88ec18d..9099334 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
@@ -48,12 +48,10 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.navigationbar.NavigationModeController;
-import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.statusbar.data.model.StatusBarAppearance;
 import com.android.systemui.statusbar.data.model.StatusBarMode;
-import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository;
+import com.android.systemui.statusbar.data.repository.FakeStatusBarModePerDisplayRepository;
 import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.util.kotlin.JavaAdapter;
 
 import kotlinx.coroutines.test.TestScope;
 
@@ -81,8 +79,8 @@
     private SysuiDarkIconDispatcher mStatusBarIconController;
     private LightBarController mLightBarController;
     private final TestScope mTestScope = TestScopeProvider.getTestScope();
-    private final FakeStatusBarModeRepository mStatusBarModeRepository =
-            new FakeStatusBarModeRepository();
+    private final FakeStatusBarModePerDisplayRepository mStatusBarModeRepository =
+            new FakeStatusBarModePerDisplayRepository();
 
     @Before
     public void setup() {
@@ -92,15 +90,16 @@
         mLightBarTransitionsController = mock(LightBarTransitionsController.class);
         when(mStatusBarIconController.getTransitionsController()).thenReturn(
                 mLightBarTransitionsController);
-        mLightBarController = new LightBarController(
-                mContext,
-                new JavaAdapter(mTestScope),
+        mLightBarController = new LightBarControllerImpl(
+                mContext.getDisplayId(),
+                mTestScope,
                 mStatusBarIconController,
                 mock(BatteryController.class),
                 mock(NavigationModeController.class),
                 mStatusBarModeRepository,
                 mock(DumpManager.class),
-                new FakeDisplayTracker(mContext));
+                mTestScope.getCoroutineContext(),
+                mock(BiometricUnlockController.class));
         mLightBarController.start();
     }
 
@@ -121,7 +120,7 @@
                 new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, secondBounds)
         );
 
-        mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+        mStatusBarModeRepository.getStatusBarAppearance().setValue(
                 new StatusBarAppearance(
                         StatusBarMode.TRANSPARENT,
                         STATUS_BAR_BOUNDS,
@@ -142,7 +141,7 @@
                 new AppearanceRegion(0 /* appearance */, secondBounds)
         );
 
-        mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+        mStatusBarModeRepository.getStatusBarAppearance().setValue(
                 new StatusBarAppearance(
                         StatusBarMode.TRANSPARENT,
                         STATUS_BAR_BOUNDS,
@@ -165,7 +164,7 @@
                 new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, secondBounds)
         );
 
-        mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+        mStatusBarModeRepository.getStatusBarAppearance().setValue(
                 new StatusBarAppearance(
                         StatusBarMode.TRANSPARENT,
                         STATUS_BAR_BOUNDS,
@@ -190,7 +189,7 @@
                 new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, thirdBounds)
         );
 
-        mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+        mStatusBarModeRepository.getStatusBarAppearance().setValue(
                 new StatusBarAppearance(
                         StatusBarMode.TRANSPARENT,
                         STATUS_BAR_BOUNDS,
@@ -214,7 +213,7 @@
                 new AppearanceRegion(0 /* appearance */, secondBounds)
         );
 
-        mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+        mStatusBarModeRepository.getStatusBarAppearance().setValue(
                 new StatusBarAppearance(
                         StatusBarMode.TRANSPARENT,
                         STATUS_BAR_BOUNDS,
@@ -231,7 +230,7 @@
                 new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, new Rect(0, 0, 1, 1))
         );
 
-        mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+        mStatusBarModeRepository.getStatusBarAppearance().setValue(
                 new StatusBarAppearance(
                         StatusBarMode.TRANSPARENT,
                         STATUS_BAR_BOUNDS,
@@ -249,7 +248,7 @@
                 new AppearanceRegion(0, new Rect(0, 0, 1, 1))
         );
 
-        mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+        mStatusBarModeRepository.getStatusBarAppearance().setValue(
                 new StatusBarAppearance(
                         StatusBarMode.TRANSPARENT,
                         STATUS_BAR_BOUNDS,
@@ -266,7 +265,7 @@
                 new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, new Rect(0, 0, 1, 1))
         );
 
-        mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+        mStatusBarModeRepository.getStatusBarAppearance().setValue(
                 new StatusBarAppearance(
                         StatusBarMode.TRANSPARENT,
                         STATUS_BAR_BOUNDS,
@@ -276,7 +275,7 @@
         reset(mStatusBarIconController);
 
         // WHEN the same appearance regions but different status bar mode is sent
-        mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+        mStatusBarModeRepository.getStatusBarAppearance().setValue(
                 new StatusBarAppearance(
                         StatusBarMode.LIGHTS_OUT_TRANSPARENT,
                         STATUS_BAR_BOUNDS,
@@ -298,7 +297,7 @@
                 /* start= */ new Rect(0, 0, 10, 10),
                 /* end= */ new Rect(0, 0, 20, 20));
 
-        mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+        mStatusBarModeRepository.getStatusBarAppearance().setValue(
                 new StatusBarAppearance(
                         StatusBarMode.TRANSPARENT,
                         startingBounds,
@@ -311,7 +310,7 @@
         BoundsPair newBounds = new BoundsPair(
                 /* start= */ new Rect(0, 0, 30, 30),
                 /* end= */ new Rect(0, 0, 40, 40));
-        mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+        mStatusBarModeRepository.getStatusBarAppearance().setValue(
                 new StatusBarAppearance(
                         StatusBarMode.TRANSPARENT,
                         newBounds,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt
index e0d9fac..110dec6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt
@@ -36,7 +36,6 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testScope
 import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.res.R
@@ -58,7 +57,6 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
-import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -144,6 +142,7 @@
         controller.start()
         controller.addCallback(mockOngoingCallListener)
         controller.setChipView(chipView)
+        onTeardown { controller.tearDownChipView() }
 
         val collectionListenerCaptor = ArgumentCaptor.forClass(NotifCollectionListener::class.java)
         verify(notificationCollection).addCollectionListener(collectionListenerCaptor.capture())
@@ -153,11 +152,6 @@
             .thenReturn(PROC_STATE_INVISIBLE)
     }
 
-    @After
-    fun tearDown() {
-        controller.tearDownChipView()
-    }
-
     @Test
     fun onEntryUpdated_isOngoingCallNotif_listenerAndRepoNotified() {
         val notification = NotificationEntryBuilder(createOngoingCallNotifEntry())
@@ -224,7 +218,7 @@
         notifCollectionListener.onEntryUpdated(notification.build())
         chipView.measure(
             View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
-            View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
+            View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
         )
 
         assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth)
@@ -241,7 +235,7 @@
         notifCollectionListener.onEntryUpdated(notification.build())
         chipView.measure(
             View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
-            View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
+            View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
         )
 
         assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth)
@@ -257,7 +251,7 @@
         notifCollectionListener.onEntryUpdated(notification.build())
         chipView.measure(
             View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
-            View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
+            View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
         )
 
         assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth)
@@ -668,7 +662,7 @@
 
     private fun createCallNotifEntry(
         callStyle: Notification.CallStyle,
-        nullContentIntent: Boolean = false
+        nullContentIntent: Boolean = false,
     ): NotificationEntry {
         val notificationEntryBuilder = NotificationEntryBuilder()
         notificationEntryBuilder.modifyNotification(context).style = callStyle
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt
index fc1ea22..19d5a16 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt
@@ -66,6 +66,7 @@
             logBuffer = FakeLogBuffer.Factory.create(),
             verboseLogBuffer = FakeLogBuffer.Factory.create(),
             systemClock,
+            context.resources,
         )
     private val demoDataSource =
         mock<DemoDeviceBasedSatelliteDataSource>().also {
@@ -80,7 +81,11 @@
                 )
         }
     private val demoImpl =
-        DemoDeviceBasedSatelliteRepository(demoDataSource, testScope.backgroundScope)
+        DemoDeviceBasedSatelliteRepository(
+            demoDataSource,
+            testScope.backgroundScope,
+            context.resources,
+        )
 
     private val underTest =
         DeviceBasedSatelliteRepositorySwitcher(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt
index 8769389..a70881a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt
@@ -58,7 +58,12 @@
                 whenever(it.satelliteEvents).thenReturn(fakeSatelliteEvents)
             }
 
-        underTest = DemoDeviceBasedSatelliteRepository(dataSource, testScope.backgroundScope)
+        underTest =
+            DemoDeviceBasedSatelliteRepository(
+                dataSource,
+                testScope.backgroundScope,
+                context.resources,
+            )
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/FakeDeviceBasedSatelliteRepository.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/FakeDeviceBasedSatelliteRepository.kt
index 55460bd..41fa9e7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/FakeDeviceBasedSatelliteRepository.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/FakeDeviceBasedSatelliteRepository.kt
@@ -28,4 +28,6 @@
     override val signalStrength = MutableStateFlow(0)
 
     override val isSatelliteAllowedForCurrentLocation = MutableStateFlow(false)
+
+    override var isOpportunisticSatelliteIconEnabled: Boolean = true
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
index e7e4969..509aa7a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
@@ -172,6 +172,26 @@
         }
 
     @Test
+    fun icon_null_allOosAndConfigIsFalse() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.icon)
+
+            // GIVEN config for opportunistic icon is false
+            repo.isOpportunisticSatelliteIconEnabled = false
+
+            // GIVEN all icons are OOS
+            val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+            i1.isInService.value = false
+            i1.isEmergencyOnly.value = false
+
+            // GIVEN apm is disabled
+            airplaneModeRepository.setIsAirplaneMode(false)
+
+            // THEN icon is null because it is not allowed
+            assertThat(latest).isNull()
+        }
+
+    @Test
     fun icon_null_isEmergencyOnly() =
         testScope.runTest {
             val latest by collectLastValue(underTest.icon)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index 53e033e..e7ca1dd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -51,6 +51,8 @@
 import android.os.Handler;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.provider.Settings;
 
 import androidx.annotation.VisibleForTesting;
@@ -561,6 +563,113 @@
     }
 
     @Test
+    @EnableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI)
+    public void onWallpaperColorsChanged_homeWallpaper_shouldUpdateTheme() {
+        // Should ask for a new theme when the colors of the last applied wallpaper change
+        WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
+                Color.valueOf(Color.BLUE), null);
+
+        String jsonString =
+                "{\"android.theme.customization.color_source\":\"home_wallpaper\","
+                        + "\"android.theme.customization.system_palette\":\"A16B00\","
+                        + "\"android.theme.customization.accent_color\":\"A16B00\","
+                        + "\"android.theme.customization.color_index\":\"2\"}";
+
+        when(mSecureSettings.getStringForUser(
+                eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
+                .thenReturn(jsonString);
+        when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, USER_SYSTEM))
+                .thenReturn(1);
+        // Set LOCK wallpaper as the last applied one to verify that theme is no longer based on
+        // latest wallpaper
+        when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, USER_SYSTEM))
+                .thenReturn(2);
+
+        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM,
+                USER_SYSTEM);
+
+        ArgumentCaptor<String> updatedSetting = ArgumentCaptor.forClass(String.class);
+        verify(mSecureSettings).putStringForUser(
+                eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), updatedSetting.capture(),
+                anyInt());
+
+        verify(mThemeOverlayApplier)
+                .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
+    }
+
+
+
+    @Test
+    @EnableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI)
+    public void onWallpaperColorsChanged_homeWallpaperWithSameColor_shouldKeepThemeAndReapply() {
+        // Shouldn't ask for a new theme when the colors of the last applied wallpaper change
+        // with the same specified system palette one.
+        WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
+                Color.valueOf(0xffa16b00), null);
+
+        String jsonString =
+                "{\"android.theme.customization.color_source\":\"home_wallpaper\","
+                        + "\"android.theme.customization.system_palette\":\"A16B00\","
+                        + "\"android.theme.customization.accent_color\":\"A16B00\","
+                        + "\"android.theme.customization.color_index\":\"2\"}";
+
+        when(mSecureSettings.getStringForUser(
+                eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
+                .thenReturn(jsonString);
+        when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, USER_SYSTEM))
+                .thenReturn(1);
+        // Set LOCK wallpaper as the last applied one to verify that theme is no longer based on
+        // latest wallpaper
+        when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, USER_SYSTEM))
+                .thenReturn(2);
+
+        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM,
+                USER_SYSTEM);
+
+        ArgumentCaptor<String> updatedSetting = ArgumentCaptor.forClass(String.class);
+        verify(mSecureSettings, never()).putString(
+                eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), updatedSetting.capture());
+
+        // Apply overlay by existing theme from secure setting
+        verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
+    }
+
+    @Test
+    @EnableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI)
+    public void onWallpaperColorsChanged_lockWallpaper_shouldKeepTheme() {
+        // Should ask for a new theme when the colors of the last applied wallpaper change
+        WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
+                Color.valueOf(Color.BLUE), null);
+
+        String jsonString =
+                "{\"android.theme.customization.color_source\":\"home_wallpaper\","
+                        + "\"android.theme.customization.system_palette\":\"A16B00\","
+                        + "\"android.theme.customization.accent_color\":\"A16B00\","
+                        + "\"android.theme.customization.color_index\":\"2\"}";
+
+        when(mSecureSettings.getStringForUser(
+                eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
+                .thenReturn(jsonString);
+        when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, USER_SYSTEM))
+                .thenReturn(1);
+        // Set LOCK wallpaper as the last applied one to verify that theme is no longer based on
+        // latest wallpaper
+        when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, USER_SYSTEM))
+                .thenReturn(2);
+
+        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_LOCK,
+                USER_SYSTEM);
+
+        ArgumentCaptor<String> updatedSetting = ArgumentCaptor.forClass(String.class);
+        verify(mSecureSettings, never()).putString(
+                eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), updatedSetting.capture());
+
+        verify(mThemeOverlayApplier, never())
+                .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
+    }
+
+    @Test
+    @DisableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI)
     public void onWallpaperColorsChanged_resetThemeWhenFromLatestWallpaper() {
         // Should ask for a new theme when the colors of the last applied wallpaper change
         WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
@@ -594,6 +703,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI)
     public void onWallpaperColorsChanged_keepThemeWhenFromLatestWallpaperAndSpecifiedColor() {
         // Shouldn't ask for a new theme when the colors of the last applied wallpaper change
         // with the same specified system palette one.
@@ -627,6 +737,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI)
     public void onWallpaperColorsChanged_keepThemeIfNotLatestWallpaper() {
         // Shouldn't ask for a new theme when the colors of the wallpaper that is not the last
         // applied one change
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt
index f101539..09be93d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt
@@ -24,7 +24,7 @@
 import com.android.internal.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImpl
-import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl
 import com.android.systemui.defaultDeviceState
 import com.android.systemui.deviceStateManager
 import com.android.systemui.display.data.repository.DeviceStateRepository
@@ -107,9 +107,9 @@
             configurationController,
             context,
             testScope.backgroundScope,
-            mock()
+            mock(),
         )
-    private val configurationInteractor = ConfigurationInteractor(configurationRepository)
+    private val configurationInteractor = ConfigurationInteractorImpl(configurationRepository)
     private val unfoldTransitionProgressProvider = FakeUnfoldTransitionProvider()
     private val unfoldTransitionRepository =
         UnfoldTransitionRepositoryImpl(Optional.of(unfoldTransitionProgressProvider))
@@ -145,7 +145,7 @@
                 testScope.backgroundScope,
                 displaySwitchLatencyLogger,
                 systemClock,
-                deviceStateManager
+                deviceStateManager,
             )
     }
 
@@ -174,7 +174,7 @@
                 DisplaySwitchLatencyEvent(
                     latencyMs = 250,
                     fromFoldableDeviceState = FOLDABLE_DEVICE_STATE_CLOSED,
-                    toFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN
+                    toFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN,
                 )
             assertThat(loggedEvent).isEqualTo(expectedLoggedEvent)
         }
@@ -200,7 +200,7 @@
                     testScope.backgroundScope,
                     displaySwitchLatencyLogger,
                     systemClock,
-                    deviceStateManager
+                    deviceStateManager,
                 )
             areAnimationEnabled.emit(true)
 
@@ -226,7 +226,7 @@
                 DisplaySwitchLatencyEvent(
                     latencyMs = 50,
                     fromFoldableDeviceState = FOLDABLE_DEVICE_STATE_CLOSED,
-                    toFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN
+                    toFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN,
                 )
             assertThat(loggedEvent).isEqualTo(expectedLoggedEvent)
         }
@@ -259,7 +259,7 @@
                 DisplaySwitchLatencyEvent(
                     latencyMs = 50,
                     fromFoldableDeviceState = FOLDABLE_DEVICE_STATE_CLOSED,
-                    toFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN
+                    toFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN,
                 )
             assertThat(loggedEvent).isEqualTo(expectedLoggedEvent)
         }
@@ -289,7 +289,7 @@
                 DisplaySwitchLatencyEvent(
                     latencyMs = 200,
                     fromFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN,
-                    toFoldableDeviceState = FOLDABLE_DEVICE_STATE_CLOSED
+                    toFoldableDeviceState = FOLDABLE_DEVICE_STATE_CLOSED,
                 )
             assertThat(loggedEvent).isEqualTo(expectedLoggedEvent)
         }
@@ -310,7 +310,7 @@
             lastWakefulnessEvent.emit(
                 WakefulnessModel(
                     internalWakefulnessState = WakefulnessState.ASLEEP,
-                    lastSleepReason = WakeSleepReason.FOLD
+                    lastSleepReason = WakeSleepReason.FOLD,
                 )
             )
             screenPowerState.emit(ScreenPowerState.SCREEN_OFF)
@@ -326,7 +326,7 @@
                     latencyMs = 200,
                     fromFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN,
                     toFoldableDeviceState = FOLDABLE_DEVICE_STATE_CLOSED,
-                    toState = SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__TO_STATE__AOD
+                    toState = SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__TO_STATE__AOD,
                 )
             assertThat(loggedEvent).isEqualTo(expectedLoggedEvent)
         }
@@ -372,7 +372,7 @@
             lastWakefulnessEvent.emit(
                 WakefulnessModel(
                     internalWakefulnessState = WakefulnessState.ASLEEP,
-                    lastSleepReason = WakeSleepReason.FOLD
+                    lastSleepReason = WakeSleepReason.FOLD,
                 )
             )
             screenPowerState.emit(ScreenPowerState.SCREEN_OFF)
@@ -385,7 +385,7 @@
                     latencyMs = 0,
                     fromFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN,
                     toFoldableDeviceState = FOLDABLE_DEVICE_STATE_CLOSED,
-                    toState = SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__TO_STATE__SCREEN_OFF
+                    toState = SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__TO_STATE__SCREEN_OFF,
                 )
             assertThat(loggedEvent).isEqualTo(expectedLoggedEvent)
         }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt
index a0cfab4d..e88dbd2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt
@@ -22,11 +22,10 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.testKosmos
 import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.settings.data.repository.userAwareSecureSettingsRepository
 import com.android.systemui.util.settings.fakeSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -42,70 +41,127 @@
 class UserAwareSecureSettingsRepositoryTest : SysuiTestCase() {
 
     private val kosmos = testKosmos()
-    private val dispatcher = kosmos.testDispatcher
     private val testScope = kosmos.testScope
     private val secureSettings = kosmos.fakeSettings
-    private val userRepository = Kosmos().fakeUserRepository
-    private lateinit var repository: UserAwareSecureSettingsRepository
+    private val userRepository = kosmos.fakeUserRepository
+    private lateinit var underTest: UserAwareSecureSettingsRepository
 
     @Before
     fun setup() {
-        repository =
-            UserAwareSecureSettingsRepositoryImpl(
-                secureSettings,
-                userRepository,
-                dispatcher,
-            )
+        underTest = kosmos.userAwareSecureSettingsRepository
+
         userRepository.setUserInfos(USER_INFOS)
-        setSettingValueForUser(enabled = true, userInfo = SETTING_ENABLED_USER)
-        setSettingValueForUser(enabled = false, userInfo = SETTING_DISABLED_USER)
+
+        secureSettings.putBoolForUser(BOOL_SETTING_NAME, true, USER_1.id)
+        secureSettings.putBoolForUser(BOOL_SETTING_NAME, false, USER_2.id)
+        secureSettings.putIntForUser(INT_SETTING_NAME, 1337, USER_1.id)
+        secureSettings.putIntForUser(INT_SETTING_NAME, 818, USER_2.id)
     }
 
     @Test
-    fun settingEnabledEmitsValueForCurrentUser() {
+    fun boolSetting_emitsInitialValue() {
         testScope.runTest {
-            userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
+            userRepository.setSelectedUserInfo(USER_1)
 
-            val enabled by collectLastValue(repository.boolSettingForActiveUser(SETTING_NAME))
+            val enabled by collectLastValue(underTest.boolSetting(BOOL_SETTING_NAME, false))
 
             assertThat(enabled).isTrue()
         }
     }
 
     @Test
-    fun settingEnabledEmitsNewValueWhenSettingChanges() {
+    fun boolSetting_whenSettingChanges_emitsNewValue() {
         testScope.runTest {
-            userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
-            val enabled by collectValues(repository.boolSettingForActiveUser(SETTING_NAME))
+            userRepository.setSelectedUserInfo(USER_1)
+            val enabled by collectValues(underTest.boolSetting(BOOL_SETTING_NAME, false))
             runCurrent()
 
-            setSettingValueForUser(enabled = false, userInfo = SETTING_ENABLED_USER)
+            secureSettings.putBoolForUser(BOOL_SETTING_NAME, false, USER_1.id)
 
             assertThat(enabled).containsExactly(true, false).inOrder()
         }
     }
 
     @Test
-    fun settingEnabledEmitsValueForNewUserWhenUserChanges() {
+    fun boolSetting_whenWhenUserChanges_emitsNewValue() {
         testScope.runTest {
-            userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
-            val enabled by collectLastValue(repository.boolSettingForActiveUser(SETTING_NAME))
+            userRepository.setSelectedUserInfo(USER_1)
+            val enabled by collectLastValue(underTest.boolSetting(BOOL_SETTING_NAME, false))
             runCurrent()
 
-            userRepository.setSelectedUserInfo(SETTING_DISABLED_USER)
+            userRepository.setSelectedUserInfo(USER_2)
 
             assertThat(enabled).isFalse()
         }
     }
 
-    private fun setSettingValueForUser(enabled: Boolean, userInfo: UserInfo) {
-        secureSettings.putBoolForUser(SETTING_NAME, enabled, userInfo.id)
+    @Test
+    fun intSetting_emitsInitialValue() {
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(USER_1)
+
+            val number by collectLastValue(underTest.intSetting(INT_SETTING_NAME, 0))
+
+            assertThat(number).isEqualTo(1337)
+        }
     }
 
+    @Test
+    fun intSetting_whenSettingChanges_emitsNewValue() {
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(USER_1)
+            val number by collectValues(underTest.intSetting(INT_SETTING_NAME, 0))
+            runCurrent()
+
+            secureSettings.putIntForUser(INT_SETTING_NAME, 1338, USER_1.id)
+
+            assertThat(number).containsExactly(1337, 1338).inOrder()
+        }
+    }
+
+    @Test
+    fun intSetting_whenWhenUserChanges_emitsNewValue() {
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(USER_1)
+            val number by collectLastValue(underTest.intSetting(INT_SETTING_NAME, 0))
+            runCurrent()
+
+            userRepository.setSelectedUserInfo(USER_2)
+
+            assertThat(number).isEqualTo(818)
+        }
+    }
+
+    @Test
+    fun getInt_returnsInitialValue() =
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(USER_1)
+
+            assertThat(underTest.getInt(INT_SETTING_NAME, 0)).isEqualTo(1337)
+        }
+
+    @Test
+    fun getInt_whenSettingChanges_returnsNewValue() =
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(USER_1)
+            secureSettings.putIntForUser(INT_SETTING_NAME, 999, USER_1.id)
+
+            assertThat(underTest.getInt(INT_SETTING_NAME, 0)).isEqualTo(999)
+        }
+
+    @Test
+    fun getInt_whenUserChanges_returnsThatUserValue() =
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(USER_2)
+
+            assertThat(underTest.getInt(INT_SETTING_NAME, 0)).isEqualTo(818)
+        }
+
     private companion object {
-        const val SETTING_NAME = "SETTING_NAME"
-        val SETTING_ENABLED_USER = UserInfo(/* id= */ 0, "user1", /* flags= */ 0)
-        val SETTING_DISABLED_USER = UserInfo(/* id= */ 1, "user2", /* flags= */ 0)
-        val USER_INFOS = listOf(SETTING_ENABLED_USER, SETTING_DISABLED_USER)
+        const val BOOL_SETTING_NAME = "BOOL_SETTING_NAME"
+        const val INT_SETTING_NAME = "INT_SETTING_NAME"
+        val USER_1 = UserInfo(/* id= */ 0, "user1", /* flags= */ 0)
+        val USER_2 = UserInfo(/* id= */ 1, "user2", /* flags= */ 0)
+        val USER_INFOS = listOf(USER_1, USER_2)
     }
 }
diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml
index 65005f8..572f063 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay.xml
@@ -32,6 +32,34 @@
         android:id="@+id/min_edge_guideline"
         app:layout_constraintGuide_begin="@dimen/overlay_action_container_minimum_edge_spacing"
         android:orientation="vertical"/>
+    <!-- This toast-like indication layout was forked from text_toast.xml and will have the same
+         appearance as system toast. -->
+    <FrameLayout
+        android:id="@+id/indication_container"
+        android:visibility="gone"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        android:maxWidth="@*android:dimen/toast_width"
+        android:background="@android:drawable/toast_frame"
+        android:elevation="@*android:dimen/toast_elevation"
+        android:paddingStart="16dp"
+        android:paddingEnd="16dp"
+        android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal"
+        android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
+        android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent">
+        <TextView
+            android:id="@+id/indication_text"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:ellipsize="end"
+            android:maxLines="2"
+            android:paddingTop="12dp"
+            android:paddingBottom="12dp"
+            android:textAppearance="@*android:style/TextAppearance.Toast"/>
+    </FrameLayout>
     <!-- Negative horizontal margin because this container background must render beyond the thing
          it's constrained by (the actions themselves). -->
     <FrameLayout
@@ -47,7 +75,7 @@
         app:layout_constraintStart_toStartOf="@id/min_edge_guideline"
         app:layout_constraintTop_toTopOf="@id/actions_container"
         app:layout_constraintEnd_toEndOf="@id/actions_container"
-        app:layout_constraintBottom_toBottomOf="parent"/>
+        app:layout_constraintBottom_toTopOf="@id/indication_container"/>
     <HorizontalScrollView
         android:id="@+id/actions_container"
         android:layout_width="0dp"
@@ -144,7 +172,7 @@
         android:visibility="gone"
         android:elevation="7dp"
         android:padding="8dp"
-        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintBottom_toTopOf="@id/indication_container"
         app:layout_constraintStart_toStartOf="parent"
         android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
         android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom"
diff --git a/packages/SystemUI/res/layout/volume_ringer_drawer.xml b/packages/SystemUI/res/layout/volume_ringer_drawer.xml
index 9b3af52..7c266e6 100644
--- a/packages/SystemUI/res/layout/volume_ringer_drawer.xml
+++ b/packages/SystemUI/res/layout/volume_ringer_drawer.xml
@@ -65,7 +65,7 @@
         android:background="@drawable/volume_drawer_selection_bg"
         android:contentDescription="@string/volume_ringer_change"
         android:gravity="center"
-        android:padding="10dp"
+        android:padding="@dimen/volume_dialog_ringer_horizontal_padding"
         android:src="@drawable/ic_volume_media"
         android:tint="?androidprv:attr/materialColorOnPrimary" />
 
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index d4a52c3..c8ef093 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -383,6 +383,10 @@
     <!-- Whether to show activity indicators in the status bar -->
     <bool name="config_showActivity">false</bool>
 
+    <!-- Whether to show the opportunistic satellite icon. When true, an icon will show to indicate
+         satellite capabilities when all other connections are out of service. -->
+    <bool name="config_showOpportunisticSatelliteIcon">true</bool>
+
     <!-- Whether or not to show the notification shelf that houses the icons of notifications that
      have been scrolled off-screen. -->
     <bool name="config_showNotificationShelf">true</bool>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 1766cdf..7b50582 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1518,7 +1518,7 @@
     <string name="no_unseen_notif_text">No new notifications</string>
 
     <!-- Title of heads up notification for adaptive notifications user education. [CHAR LIMIT=60] -->
-    <string name="adaptive_notification_edu_hun_title">Notification cooldown is on</string>
+    <string name="adaptive_notification_edu_hun_title">Notification cooldown is now on</string>
 
     <!-- Text of heads up notification for adaptive notifications user education. [CHAR LIMIT=100] -->
     <string name="adaptive_notification_edu_hun_text">Your device volume and alerts are reduced automatically for up to 2 minutes when you get too many notifications at once.</string>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
index 7ec977a..9e8cabf 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
@@ -243,10 +243,6 @@
 
     public Rect appBounds;
 
-    // Last snapshot data, only used for recent tasks
-    public ActivityManager.RecentTaskInfo.PersistedTaskSnapshotData lastSnapshotData =
-            new ActivityManager.RecentTaskInfo.PersistedTaskSnapshotData();
-
     @ViewDebug.ExportedProperty(category="recents")
     public boolean isVisible;
 
@@ -283,7 +279,6 @@
     public Task(Task other) {
         this(other.key, other.colorPrimary, other.colorBackground, other.isDockable,
                 other.isLocked, other.taskDescription, other.topActivity);
-        lastSnapshotData.set(other.lastSnapshotData);
         positionInParent = other.positionInParent;
         appBounds = other.appBounds;
         isVisible = other.isVisible;
@@ -315,33 +310,10 @@
                 : key.baseIntent.getComponent();
     }
 
-    public void setLastSnapshotData(ActivityManager.RecentTaskInfo rawTask) {
-        lastSnapshotData.set(rawTask.lastSnapshotData);
-    }
-
     public TaskKey getKey() {
         return key;
     }
 
-    /**
-     * Returns the visible width to height ratio. Returns 0f if snapshot data is not available.
-     */
-    public float getVisibleThumbnailRatio(boolean clipInsets) {
-        if (lastSnapshotData.taskSize == null || lastSnapshotData.contentInsets == null) {
-            return 0f;
-        }
-
-        float availableWidth = lastSnapshotData.taskSize.x;
-        float availableHeight = lastSnapshotData.taskSize.y;
-        if (clipInsets) {
-            availableWidth -=
-                    (lastSnapshotData.contentInsets.left + lastSnapshotData.contentInsets.right);
-            availableHeight -=
-                    (lastSnapshotData.contentInsets.top + lastSnapshotData.contentInsets.bottom);
-        }
-        return availableWidth / availableHeight;
-    }
-
     @Override
     public boolean equals(Object o) {
         if (o == this) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 8b59370..f98890e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -121,6 +121,7 @@
 import com.android.settingslib.fuelgauge.BatteryStatus;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.Dumpable;
+import com.android.systemui.Flags;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
@@ -473,6 +474,7 @@
         }
     }
 
+    @Deprecated
     private final SparseBooleanArray mUserIsUnlocked = new SparseBooleanArray();
     private final SparseBooleanArray mUserHasTrust = new SparseBooleanArray();
     private final SparseBooleanArray mUserTrustIsManaged = new SparseBooleanArray();
@@ -2688,7 +2690,11 @@
      * @see Intent#ACTION_USER_UNLOCKED
      */
     public boolean isUserUnlocked(int userId) {
-        return mUserIsUnlocked.get(userId);
+        if (Flags.userEncryptedSource()) {
+            return mUserManager.isUserUnlocked(userId);
+        } else {
+            return mUserIsUnlocked.get(userId);
+        }
     }
 
     /**
@@ -4213,7 +4219,7 @@
         pw.println("    strongAuthFlags=" + Integer.toHexString(strongAuthFlags));
         pw.println("ActiveUnlockRunning="
                 + mTrustManager.isActiveUnlockRunning(mSelectedUserInteractor.getSelectedUserId()));
-        pw.println("userUnlockedCache[userid=" + userId + "]=" + isUserUnlocked(userId));
+        pw.println("userUnlockedCache[userid=" + userId + "]=" + mUserIsUnlocked.get(userId));
         pw.println("actualUserUnlocked[userid=" + userId + "]="
                 + mUserManager.isUserUnlocked(userId));
         new DumpsysTableLogger(
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
index bebfd85..cd19aaac 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
@@ -116,7 +116,7 @@
 
     fun logUpdateLockScreenUserLockedMsg(
         userId: Int,
-        userUnlocked: Boolean,
+        userStorageUnlocked: Boolean,
         encryptedOrLockdown: Boolean,
     ) {
         buffer.log(
@@ -124,12 +124,12 @@
             LogLevel.DEBUG,
             {
                 int1 = userId
-                bool1 = userUnlocked
+                bool1 = userStorageUnlocked
                 bool2 = encryptedOrLockdown
             },
             {
                 "updateLockScreenUserLockedMsg userId=$int1 " +
-                    "userUnlocked:$bool1 encryptedOrLockdown:$bool2"
+                    "userStorageUnlocked:$bool1 encryptedOrLockdown:$bool2"
             }
         )
     }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
index f8b445b..3cf400a 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
@@ -38,7 +38,6 @@
 import android.view.SurfaceControl;
 import android.view.SurfaceControlViewHost;
 import android.view.WindowManager;
-import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.IMagnificationConnection;
 import android.view.accessibility.IRemoteMagnificationAnimationCallback;
@@ -138,10 +137,7 @@
                     mWindowMagnifierCallback,
                     mSysUiState,
                     mSecureSettings,
-                    scvhSupplier,
-                    new SfVsyncFrameCallbackProvider(),
-                    WindowManagerGlobal::getWindowSession,
-                    mViewCaptureAwareWindowManager);
+                    scvhSupplier);
         }
     }
 
@@ -407,7 +403,8 @@
         }
     }
 
-    boolean isMagnificationSettingsPanelShowing(int displayId) {
+    @MainThread
+    private boolean isMagnificationSettingsPanelShowing(int displayId) {
         final MagnificationSettingsController magnificationSettingsController =
                 mMagnificationSettingsSupplier.get(displayId);
         if (magnificationSettingsController != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 0883a06..7d5cf23 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -54,11 +54,8 @@
 import android.util.Size;
 import android.util.SparseArray;
 import android.util.TypedValue;
-import android.view.Choreographer;
 import android.view.Display;
 import android.view.Gravity;
-import android.view.IWindow;
-import android.view.IWindowSession;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.Surface;
@@ -80,10 +77,8 @@
 import androidx.annotation.UiThread;
 import androidx.core.math.MathUtils;
 
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.internal.accessibility.common.MagnificationConstants;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.systemui.Flags;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.res.R;
@@ -127,7 +122,6 @@
     private final SurfaceControl.Transaction mTransaction;
 
     private final WindowManager mWm;
-    private final ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
 
     private float mScale;
     private int mSettingsButtonIndex = MagnificationSize.DEFAULT;
@@ -219,11 +213,8 @@
     private int mMinWindowSize;
 
     private final WindowMagnificationAnimationController mAnimationController;
-    private final Supplier<IWindowSession> mGlobalWindowSessionSupplier;
-    private final SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
     private final MagnificationGestureDetector mGestureDetector;
     private int mBounceEffectDuration;
-    private final Choreographer.FrameCallback mMirrorViewGeometryVsyncCallback;
     private Locale mLocale;
     private NumberFormat mPercentFormat;
     private float mBounceEffectAnimationScale;
@@ -258,18 +249,11 @@
             @NonNull WindowMagnifierCallback callback,
             SysUiState sysUiState,
             SecureSettings secureSettings,
-            Supplier<SurfaceControlViewHost> scvhSupplier,
-            SfVsyncFrameCallbackProvider sfVsyncFrameProvider,
-            Supplier<IWindowSession> globalWindowSessionSupplier,
-            ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
+            Supplier<SurfaceControlViewHost> scvhSupplier) {
         mContext = context;
         mHandler = handler;
         mAnimationController = animationController;
-        mAnimationController.setOnAnimationEndRunnable(() -> {
-            if (Flags.createWindowlessWindowMagnifier()) {
-                notifySourceBoundsChanged();
-            }
-        });
+        mAnimationController.setOnAnimationEndRunnable(this::notifySourceBoundsChanged);
         mAnimationController.setWindowMagnificationController(this);
         mWindowMagnifierCallback = callback;
         mSysUiState = sysUiState;
@@ -283,7 +267,6 @@
 
         mWm = context.getSystemService(WindowManager.class);
         mWindowBounds = new Rect(mWm.getCurrentWindowMetrics().getBounds());
-        mViewCaptureAwareWindowManager = viewCaptureAwareWindowManager;
 
         mResources = mContext.getResources();
         mScale = secureSettings.getFloatForUser(
@@ -313,76 +296,31 @@
         mGestureDetector =
                 new MagnificationGestureDetector(mContext, handler, this);
         mWindowInsetChangeRunnable = this::onWindowInsetChanged;
-        mGlobalWindowSessionSupplier = globalWindowSessionSupplier;
-        mSfVsyncFrameProvider = sfVsyncFrameProvider;
 
         // Initialize listeners.
-        if (Flags.createWindowlessWindowMagnifier()) {
-            mMirrorViewRunnable = new Runnable() {
-                final Rect mPreviousBounds = new Rect();
+        mMirrorViewRunnable = new Runnable() {
+            final Rect mPreviousBounds = new Rect();
 
-                @Override
-                public void run() {
-                    if (mMirrorView != null) {
-                        if (mPreviousBounds.width() != mMirrorViewBounds.width()
-                                || mPreviousBounds.height() != mMirrorViewBounds.height()) {
-                            mMirrorView.setSystemGestureExclusionRects(Collections.singletonList(
-                                    new Rect(0, 0, mMirrorViewBounds.width(),
-                                            mMirrorViewBounds.height())));
-                            mPreviousBounds.set(mMirrorViewBounds);
-                        }
-                        updateSystemUIStateIfNeeded();
-                        mWindowMagnifierCallback.onWindowMagnifierBoundsChanged(
-                                mDisplayId, mMirrorViewBounds);
-                    }
-                }
-            };
-
-            mMirrorSurfaceViewLayoutChangeListener =
-                    (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
-                            mMirrorView.post(this::applyTapExcludeRegion);
-
-            mMirrorViewGeometryVsyncCallback = null;
-        } else {
-            mMirrorViewRunnable = () -> {
+            @Override
+            public void run() {
                 if (mMirrorView != null) {
-                    final Rect oldViewBounds = new Rect(mMirrorViewBounds);
-                    mMirrorView.getBoundsOnScreen(mMirrorViewBounds);
-                    if (oldViewBounds.width() != mMirrorViewBounds.width()
-                            || oldViewBounds.height() != mMirrorViewBounds.height()) {
+                    if (mPreviousBounds.width() != mMirrorViewBounds.width()
+                            || mPreviousBounds.height() != mMirrorViewBounds.height()) {
                         mMirrorView.setSystemGestureExclusionRects(Collections.singletonList(
-                                new Rect(0, 0,
-                                        mMirrorViewBounds.width(), mMirrorViewBounds.height())));
+                                new Rect(0, 0, mMirrorViewBounds.width(),
+                                        mMirrorViewBounds.height())));
+                        mPreviousBounds.set(mMirrorViewBounds);
                     }
                     updateSystemUIStateIfNeeded();
                     mWindowMagnifierCallback.onWindowMagnifierBoundsChanged(
                             mDisplayId, mMirrorViewBounds);
                 }
-            };
+            }
+        };
 
-            mMirrorSurfaceViewLayoutChangeListener =
-                    (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
-                            mMirrorView.post(this::applyTapExcludeRegion);
-
-            mMirrorViewGeometryVsyncCallback =
-                    l -> {
-                        if (isActivated() && mMirrorSurface != null && calculateSourceBounds(
-                                mMagnificationFrame, mScale)) {
-                            // The final destination for the magnification surface should be at 0,0
-                            // since the ViewRootImpl's position will change
-                            mTmpRect.set(0, 0, mMagnificationFrame.width(),
-                                    mMagnificationFrame.height());
-                            mTransaction.setGeometry(mMirrorSurface, mSourceBounds, mTmpRect,
-                                    Surface.ROTATION_0).apply();
-
-                            // Notify source bounds change when the magnifier is not animating.
-                            if (!mAnimationController.isAnimating()) {
-                                mWindowMagnifierCallback.onSourceBoundsChanged(mDisplayId,
-                                        mSourceBounds);
-                            }
-                        }
-                    };
-        }
+        mMirrorSurfaceViewLayoutChangeListener =
+                (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
+                        mMirrorView.post(this::applyTouchableRegion);
 
         mMirrorViewLayoutChangeListener =
                 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
@@ -463,7 +401,7 @@
 
         if (isActivated()) {
             updateDimensions();
-            applyTapExcludeRegion();
+            applyTouchableRegion();
         }
 
         if (!enable) {
@@ -513,9 +451,6 @@
         if (mMirrorView != null) {
             mHandler.removeCallbacks(mMirrorViewRunnable);
             mMirrorView.removeOnLayoutChangeListener(mMirrorViewLayoutChangeListener);
-            if (!Flags.createWindowlessWindowMagnifier()) {
-                mViewCaptureAwareWindowManager.removeView(mMirrorView);
-            }
             mMirrorView = null;
         }
 
@@ -624,11 +559,7 @@
         if (!isActivated()) return;
         LayoutParams params = (LayoutParams) mMirrorView.getLayoutParams();
         params.accessibilityTitle = getAccessibilityWindowTitle();
-        if (Flags.createWindowlessWindowMagnifier()) {
-            mSurfaceControlViewHost.relayout(params);
-        } else {
-            mWm.updateViewLayout(mMirrorView, params);
-        }
+        mSurfaceControlViewHost.relayout(params);
     }
 
     /**
@@ -678,62 +609,6 @@
         return (oldRotation - newRotation + 4) % 4 * 90;
     }
 
-    private void createMirrorWindow() {
-        if (Flags.createWindowlessWindowMagnifier()) {
-            createWindowlessMirrorWindow();
-            return;
-        }
-
-        // The window should be the size the mirrored surface will be but also add room for the
-        // border and the drag handle.
-        int windowWidth = mMagnificationFrame.width() + 2 * mMirrorSurfaceMargin;
-        int windowHeight = mMagnificationFrame.height() + 2 * mMirrorSurfaceMargin;
-
-        LayoutParams params = new LayoutParams(
-                windowWidth, windowHeight,
-                LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY,
-                LayoutParams.FLAG_NOT_TOUCH_MODAL
-                        | LayoutParams.FLAG_NOT_FOCUSABLE,
-                PixelFormat.TRANSPARENT);
-        params.gravity = Gravity.TOP | Gravity.LEFT;
-        params.x = mMagnificationFrame.left - mMirrorSurfaceMargin;
-        params.y = mMagnificationFrame.top - mMirrorSurfaceMargin;
-        params.layoutInDisplayCutoutMode = LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
-        params.receiveInsetsIgnoringZOrder = true;
-        params.setTitle(mContext.getString(R.string.magnification_window_title));
-        params.accessibilityTitle = getAccessibilityWindowTitle();
-
-        mMirrorView = LayoutInflater.from(mContext).inflate(R.layout.window_magnifier_view, null);
-        mMirrorSurfaceView = mMirrorView.findViewById(R.id.surface_view);
-
-        mMirrorBorderView = mMirrorView.findViewById(R.id.magnification_inner_border);
-
-        // Allow taps to go through to the mirror SurfaceView below.
-        mMirrorSurfaceView.addOnLayoutChangeListener(mMirrorSurfaceViewLayoutChangeListener);
-
-        mMirrorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
-                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
-                | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
-                | View.SYSTEM_UI_FLAG_FULLSCREEN
-                | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
-                | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
-        mMirrorView.addOnLayoutChangeListener(mMirrorViewLayoutChangeListener);
-        mMirrorView.setAccessibilityDelegate(new MirrorWindowA11yDelegate());
-        mMirrorView.setOnApplyWindowInsetsListener((v, insets) -> {
-            if (!mHandler.hasCallbacks(mWindowInsetChangeRunnable)) {
-                mHandler.post(mWindowInsetChangeRunnable);
-            }
-            return v.onApplyWindowInsets(insets);
-        });
-
-        mViewCaptureAwareWindowManager.addView(mMirrorView, params);
-
-        SurfaceHolder holder = mMirrorSurfaceView.getHolder();
-        holder.addCallback(this);
-        holder.setFormat(PixelFormat.RGBA_8888);
-        addDragTouchListeners();
-    }
-
     private void createWindowlessMirrorWindow() {
         // The window should be the size the mirrored surface will be but also add room for the
         // border and the drag handle.
@@ -802,62 +677,6 @@
         }
     }
 
-    private void applyTapExcludeRegion() {
-        if (Flags.createWindowlessWindowMagnifier()) {
-            applyTouchableRegion();
-            return;
-        }
-
-        // Sometimes this can get posted and run after deleteWindowMagnification() is called.
-        if (mMirrorView == null) return;
-
-        final Region tapExcludeRegion = calculateTapExclude();
-        final IWindow window = IWindow.Stub.asInterface(mMirrorView.getWindowToken());
-        try {
-            IWindowSession session = mGlobalWindowSessionSupplier.get();
-            session.updateTapExcludeRegion(window, tapExcludeRegion);
-        } catch (RemoteException e) {
-        }
-    }
-
-    private Region calculateTapExclude() {
-        Region regionInsideDragBorder = new Region(mBorderDragSize, mBorderDragSize,
-                mMirrorView.getWidth() - mBorderDragSize,
-                mMirrorView.getHeight() - mBorderDragSize);
-
-        Region tapExcludeRegion = new Region();
-
-        Rect dragArea = new Rect();
-        mDragView.getHitRect(dragArea);
-
-        Rect topLeftArea = new Rect();
-        mTopLeftCornerView.getHitRect(topLeftArea);
-
-        Rect topRightArea = new Rect();
-        mTopRightCornerView.getHitRect(topRightArea);
-
-        Rect bottomLeftArea = new Rect();
-        mBottomLeftCornerView.getHitRect(bottomLeftArea);
-
-        Rect bottomRightArea = new Rect();
-        mBottomRightCornerView.getHitRect(bottomRightArea);
-
-        Rect closeArea = new Rect();
-        mCloseView.getHitRect(closeArea);
-
-        // add tapExcludeRegion for Drag or close
-        tapExcludeRegion.op(dragArea, Region.Op.UNION);
-        tapExcludeRegion.op(topLeftArea, Region.Op.UNION);
-        tapExcludeRegion.op(topRightArea, Region.Op.UNION);
-        tapExcludeRegion.op(bottomLeftArea, Region.Op.UNION);
-        tapExcludeRegion.op(bottomRightArea, Region.Op.UNION);
-        tapExcludeRegion.op(closeArea, Region.Op.UNION);
-
-        regionInsideDragBorder.op(tapExcludeRegion, Region.Op.DIFFERENCE);
-
-        return regionInsideDragBorder;
-    }
-
     private void applyTouchableRegion() {
         // Sometimes this can get posted and run after deleteWindowMagnification() is called.
         if (mMirrorView == null) return;
@@ -1085,13 +904,8 @@
      * {@link #mMagnificationFrame}.
      */
     private void modifyWindowMagnification(boolean computeWindowSize) {
-        if (Flags.createWindowlessWindowMagnifier()) {
-            updateMirrorSurfaceGeometry();
-            updateWindowlessMirrorViewLayout(computeWindowSize);
-        } else {
-            mSfVsyncFrameProvider.postFrameCallback(mMirrorViewGeometryVsyncCallback);
-            updateMirrorViewLayout(computeWindowSize);
-        }
+        updateMirrorSurfaceGeometry();
+        updateWindowlessMirrorViewLayout(computeWindowSize);
     }
 
     /**
@@ -1169,58 +983,6 @@
         mMirrorViewRunnable.run();
     }
 
-    /**
-     * Updates the layout params of MirrorView based on the size of {@link #mMagnificationFrame}
-     * and translates MirrorView position when the view is moved close to the screen edges;
-     *
-     * @param computeWindowSize set to {@code true} to compute window size with
-     * {@link #mMagnificationFrame}.
-     */
-    private void updateMirrorViewLayout(boolean computeWindowSize) {
-        if (!isActivated()) {
-            return;
-        }
-        final int maxMirrorViewX = mWindowBounds.width() - mMirrorView.getWidth();
-        final int maxMirrorViewY = mWindowBounds.height() - mMirrorView.getHeight();
-
-        LayoutParams params =
-                (LayoutParams) mMirrorView.getLayoutParams();
-        params.x = mMagnificationFrame.left - mMirrorSurfaceMargin;
-        params.y = mMagnificationFrame.top - mMirrorSurfaceMargin;
-        if (computeWindowSize) {
-            params.width = mMagnificationFrame.width() + 2 * mMirrorSurfaceMargin;
-            params.height = mMagnificationFrame.height() + 2 * mMirrorSurfaceMargin;
-        }
-
-        // Translates MirrorView position to make MirrorSurfaceView that is inside MirrorView
-        // able to move close to the screen edges.
-        final float translationX;
-        final float translationY;
-        if (params.x < 0) {
-            translationX = Math.max(params.x, -mOuterBorderSize);
-        } else if (params.x > maxMirrorViewX) {
-            translationX = Math.min(params.x - maxMirrorViewX, mOuterBorderSize);
-        } else {
-            translationX = 0;
-        }
-        if (params.y < 0) {
-            translationY = Math.max(params.y, -mOuterBorderSize);
-        } else if (params.y > maxMirrorViewY) {
-            translationY = Math.min(params.y - maxMirrorViewY, mOuterBorderSize);
-        } else {
-            translationY = 0;
-        }
-        mMirrorView.setTranslationX(translationX);
-        mMirrorView.setTranslationY(translationY);
-        mWm.updateViewLayout(mMirrorView, params);
-
-        // If they are not dragging the handle, we can move the drag handle immediately without
-        // disruption. But if they are dragging it, we avoid moving until the end of the drag.
-        if (!mIsDragging) {
-            mMirrorView.post(this::maybeRepositionButton);
-        }
-    }
-
     @Override
     public boolean onTouch(View v, MotionEvent event) {
         if (v == mDragView
@@ -1474,7 +1236,7 @@
         calculateMagnificationFrameBoundary();
         updateMagnificationFramePosition((int) offsetX, (int) offsetY);
         if (!isActivated()) {
-            createMirrorWindow();
+            createWindowlessMirrorWindow();
             showControls();
             applyResourcesValues();
         } else {
@@ -1766,7 +1528,7 @@
         if (newGravity != layoutParams.gravity) {
             layoutParams.gravity = newGravity;
             mDragView.setLayoutParams(layoutParams);
-            mDragView.post(this::applyTapExcludeRegion);
+            mDragView.post(this::applyTouchableRegion);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/CaptioningRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/CaptioningRepository.kt
index 5414b62..39fd44a 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/CaptioningRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/CaptioningRepository.kt
@@ -63,6 +63,7 @@
     private val captioningManager: StateFlow<CaptioningManager?> =
         userRepository.selectedUser
             .map { userScopedCaptioningManagerProvider.forUser(it.userInfo.userHandle) }
+            .flowOn(backgroundCoroutineContext)
             .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), null)
 
     override val captioningModel: StateFlow<CaptioningModel?> =
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 12b5fc0..b491c94 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -27,6 +27,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AlertDialog;
+import android.app.KeyguardManager;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.PixelFormat;
@@ -315,6 +316,16 @@
         mBiometricCallback = new BiometricCallback();
         mMSDLPlayer = msdlPlayer;
 
+        // Listener for when device locks from adaptive auth, dismiss prompt
+        getContext().getSystemService(KeyguardManager.class).addKeyguardLockedStateListener(
+                getContext().getMainExecutor(),
+                isKeyguardLocked -> {
+                    if (isKeyguardLocked) {
+                        onStartedGoingToSleep();
+                    }
+                }
+        );
+
         final BiometricModalities biometricModalities = new BiometricModalities(
                 Utils.findFirstSensorProperties(fpProps, mConfig.mSensorIds),
                 Utils.findFirstSensorProperties(faceProps, mConfig.mSensorIds));
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
index ba51d02..68ec0f2d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
@@ -25,6 +25,7 @@
 import android.hardware.fingerprint.FingerprintSensorProperties
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
 import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback
+import android.util.Log
 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
 import com.android.systemui.biometrics.shared.model.SensorStrength
 import com.android.systemui.biometrics.shared.model.toSensorStrength
@@ -91,13 +92,14 @@
                                 trySendWithFailureLogging(
                                     DEFAULT_PROPS,
                                     TAG,
-                                    "no registered sensors, use default props"
+                                    "no registered sensors, use default props",
                                 )
                             } else {
+                                Log.d(TAG, "onAllAuthenticatorsRegistered $sensors")
                                 trySendWithFailureLogging(
                                     sensors[0],
                                     TAG,
-                                    "update properties on authenticators registered"
+                                    "update properties on authenticators registered",
                                 )
                             }
                         }
@@ -160,7 +162,7 @@
                 FingerprintSensorProperties.TYPE_UNKNOWN,
                 false /* halControlsIllumination */,
                 true /* resetLockoutRequiresHardwareAuthToken */,
-                listOf<SensorLocationInternal>(SensorLocationInternal.DEFAULT)
+                listOf<SensorLocationInternal>(SensorLocationInternal.DEFAULT),
             )
         private val DEFAULT_PROPS =
             FingerprintSensorPropertiesInternal(
@@ -171,7 +173,7 @@
                 FingerprintSensorProperties.TYPE_UNKNOWN,
                 false /* halControlsIllumination */,
                 true /* resetLockoutRequiresHardwareAuthToken */,
-                listOf<SensorLocationInternal>(SensorLocationInternal.DEFAULT)
+                listOf<SensorLocationInternal>(SensorLocationInternal.DEFAULT),
             )
     }
 }
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 18a7739..abbbd73 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
@@ -48,14 +48,14 @@
     private val authController: AuthController,
     private val selectedUserInteractor: SelectedUserInteractor,
     private val fingerprintManager: FingerprintManager?,
-    @Application scope: CoroutineScope
+    @Application scope: CoroutineScope,
 ) {
     private fun calculateIconSize(): Int {
         val pixelPitch = context.resources.getFloat(R.dimen.pixel_pitch)
         if (pixelPitch <= 0) {
             Log.e(
                 "UdfpsOverlayInteractor",
-                "invalid pixelPitch: $pixelPitch. Pixel pitch must be updated per device."
+                "invalid pixelPitch: $pixelPitch. Pixel pitch must be updated per device.",
             )
         }
         return (context.resources.getFloat(R.dimen.udfps_icon_size) / pixelPitch).toInt()
@@ -83,12 +83,11 @@
 
     /** Sets whether Udfps overlay should handle touches */
     fun setHandleTouches(shouldHandle: Boolean = true) {
-        if (authController.isUdfpsSupported
-                && shouldHandle != _shouldHandleTouches.value) {
+        if (authController.isUdfpsSupported && shouldHandle != _shouldHandleTouches.value) {
             fingerprintManager?.setIgnoreDisplayTouches(
                 requestId.value,
                 authController.udfpsProps!!.get(0).sensorId,
-                !shouldHandle
+                !shouldHandle,
             )
         }
         _shouldHandleTouches.value = shouldHandle
@@ -107,10 +106,11 @@
                         override fun onUdfpsLocationChanged(
                             udfpsOverlayParams: UdfpsOverlayParams
                         ) {
+                            Log.d(TAG, "udfpsOverlayParams updated $udfpsOverlayParams")
                             trySendWithFailureLogging(
                                 udfpsOverlayParams,
                                 TAG,
-                                "update udfpsOverlayParams"
+                                "update udfpsOverlayParams",
                             )
                         }
                     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationCallback.kt
similarity index 68%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationCallback.kt
index 94b2bdf..ddd6bc9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationCallback.kt
@@ -14,9 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util.settings
+package com.android.systemui.clipboardoverlay
 
-import com.android.systemui.kosmos.Kosmos
+/** Interface for listening to indication text changed from [ClipboardIndicationProvider]. */
+interface ClipboardIndicationCallback {
 
-val Kosmos.userAwareSecureSettingsRepository by
-    Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+    /** Notifies the indication text changed. */
+    fun onIndicationTextChanged(text: CharSequence)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProvider.kt
similarity index 62%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProvider.kt
index 94b2bdf..be32723 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProvider.kt
@@ -14,9 +14,15 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util.settings
+package com.android.systemui.clipboardoverlay
 
-import com.android.systemui.kosmos.Kosmos
+/** Interface to provide the clipboard indication to be shown under the overlay. */
+interface ClipboardIndicationProvider {
 
-val Kosmos.userAwareSecureSettingsRepository by
-    Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+    /**
+     * Gets the indication text async.
+     *
+     * @param callback callback for getting the indication text.
+     */
+    fun getIndicationText(callback: ClipboardIndicationCallback)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProviderImpl.kt
similarity index 65%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProviderImpl.kt
index 94b2bdf..da94d5b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProviderImpl.kt
@@ -14,9 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util.settings
+package com.android.systemui.clipboardoverlay
 
-import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
 
-val Kosmos.userAwareSecureSettingsRepository by
-    Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+@SysUISingleton
+open class ClipboardIndicationProviderImpl @Inject constructor() : ClipboardIndicationProvider {
+
+    override fun getIndicationText(callback: ClipboardIndicationCallback) {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index 65c01ed..ac74784 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -21,6 +21,7 @@
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_ACTIONS;
 import static com.android.systemui.Flags.clipboardImageTimeout;
 import static com.android.systemui.Flags.clipboardSharedTransitions;
+import static com.android.systemui.Flags.showClipboardIndication;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_SHOWN;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_TAPPED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISSED_OTHER;
@@ -99,6 +100,7 @@
     private final ClipboardTransitionExecutor mTransitionExecutor;
 
     private final ClipboardOverlayView mView;
+    private final ClipboardIndicationProvider mClipboardIndicationProvider;
 
     private Runnable mOnSessionCompleteListener;
     private Runnable mOnRemoteCopyTapped;
@@ -173,6 +175,13 @@
                 }
             };
 
+    private ClipboardIndicationCallback mIndicationCallback = new ClipboardIndicationCallback() {
+        @Override
+        public void onIndicationTextChanged(@NonNull CharSequence text) {
+            mView.setIndicationText(text);
+        }
+    };
+
     @Inject
     public ClipboardOverlayController(@OverlayWindowContext Context context,
             ClipboardOverlayView clipboardOverlayView,
@@ -185,11 +194,13 @@
             @Background Executor bgExecutor,
             ClipboardImageLoader clipboardImageLoader,
             ClipboardTransitionExecutor transitionExecutor,
+            ClipboardIndicationProvider clipboardIndicationProvider,
             UiEventLogger uiEventLogger) {
         mContext = context;
         mBroadcastDispatcher = broadcastDispatcher;
         mClipboardImageLoader = clipboardImageLoader;
         mTransitionExecutor = transitionExecutor;
+        mClipboardIndicationProvider = clipboardIndicationProvider;
 
         mClipboardLogger = new ClipboardLogger(uiEventLogger);
 
@@ -288,6 +299,9 @@
         boolean shouldAnimate = !model.dataMatches(mClipboardModel) || wasExiting;
         mClipboardModel = model;
         mClipboardLogger.setClipSource(mClipboardModel.getSource());
+        if (showClipboardIndication()) {
+            mClipboardIndicationProvider.getIndicationText(mIndicationCallback);
+        }
         if (clipboardImageTimeout()) {
             if (shouldAnimate) {
                 reset();
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
index 1762d82..7e4d762 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
@@ -18,6 +18,8 @@
 
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 
+import static com.android.systemui.Flags.showClipboardIndication;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
@@ -53,6 +55,7 @@
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
+import androidx.constraintlayout.widget.ConstraintLayout;
 import androidx.core.view.ViewCompat;
 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
 
@@ -103,6 +106,8 @@
     private View mShareChip;
     private View mRemoteCopyChip;
     private View mActionContainerBackground;
+    private View mIndicationContainer;
+    private TextView mIndicationText;
     private View mDismissButton;
     private LinearLayout mActionContainer;
     private ClipboardOverlayCallbacks mClipboardCallbacks;
@@ -136,6 +141,8 @@
         mShareChip = requireViewById(R.id.share_chip);
         mRemoteCopyChip = requireViewById(R.id.remote_copy_chip);
         mDismissButton = requireViewById(R.id.dismiss_button);
+        mIndicationContainer = requireViewById(R.id.indication_container);
+        mIndicationText = mIndicationContainer.findViewById(R.id.indication_text);
 
         bindDefaultActionChips();
 
@@ -208,6 +215,14 @@
         }
     }
 
+    void setIndicationText(CharSequence text) {
+        mIndicationText.setText(text);
+
+        // Set the visibility of clipboard indication based on the text is empty or not.
+        int visibility = text.isEmpty() ? View.GONE : View.VISIBLE;
+        mIndicationContainer.setVisibility(visibility);
+    }
+
     void setMinimized(boolean minimized) {
         if (minimized) {
             mMinimizedPreview.setVisibility(View.VISIBLE);
@@ -221,6 +236,18 @@
             mPreviewBorder.setVisibility(View.VISIBLE);
             mActionContainer.setVisibility(View.VISIBLE);
         }
+
+        if (showClipboardIndication()) {
+            // Adjust the margin of clipboard indication based on the minimized state.
+            int marginStart = minimized ? getResources().getDimensionPixelSize(
+                    R.dimen.overlay_action_container_margin_horizontal)
+                    : getResources().getDimensionPixelSize(
+                            R.dimen.overlay_action_container_minimum_edge_spacing);
+            ConstraintLayout.LayoutParams params =
+                    (ConstraintLayout.LayoutParams) mIndicationContainer.getLayoutParams();
+            params.setMarginStart(marginStart);
+            mIndicationContainer.setLayoutParams(params);
+        }
     }
 
     void setInsets(WindowInsets insets, int orientation) {
@@ -313,6 +340,7 @@
         setTranslationX(0);
         setAlpha(0);
         mActionContainerBackground.setVisibility(View.GONE);
+        mIndicationContainer.setVisibility(View.GONE);
         mDismissButton.setVisibility(View.GONE);
         mShareChip.setVisibility(View.GONE);
         mRemoteCopyChip.setVisibility(View.GONE);
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlaySuppressionModule.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayOverrideModule.kt
similarity index 69%
rename from packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlaySuppressionModule.kt
rename to packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayOverrideModule.kt
index 527819c..c81f0d9 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlaySuppressionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayOverrideModule.kt
@@ -15,18 +15,26 @@
  */
 package com.android.systemui.clipboardoverlay.dagger
 
+import com.android.systemui.clipboardoverlay.ClipboardIndicationProvider
+import com.android.systemui.clipboardoverlay.ClipboardIndicationProviderImpl
 import com.android.systemui.clipboardoverlay.ClipboardOverlaySuppressionController
 import com.android.systemui.clipboardoverlay.ClipboardOverlaySuppressionControllerImpl
 import dagger.Binds
 import dagger.Module
 
-/** Dagger Module for code in the clipboard overlay package. */
+/** Dagger Module to provide default implementations which could be overridden. */
 @Module
-interface ClipboardOverlaySuppressionModule {
+interface ClipboardOverlayOverrideModule {
 
     /** Provides implementation for [ClipboardOverlaySuppressionController]. */
     @Binds
     fun provideClipboardOverlaySuppressionController(
         clipboardOverlaySuppressionControllerImpl: ClipboardOverlaySuppressionControllerImpl
     ): ClipboardOverlaySuppressionController
+
+    /** Provides implementation for [ClipboardIndicationProvider]. */
+    @Binds
+    fun provideClipboardIndicationProvider(
+        clipboardIndicationProviderImpl: ClipboardIndicationProviderImpl
+    ): ClipboardIndicationProvider
 }
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt
index d1c728c..1923880 100644
--- a/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt
@@ -18,19 +18,12 @@
 
 import com.android.systemui.common.data.repository.PackageChangeRepository
 import com.android.systemui.common.data.repository.PackageChangeRepositoryImpl
-import com.android.systemui.common.ui.data.repository.ConfigurationRepository
-import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImpl
 import dagger.Binds
 import dagger.Module
 
 @Module
 abstract class CommonDataLayerModule {
     @Binds
-    abstract fun bindConfigurationRepository(
-        impl: ConfigurationRepositoryImpl
-    ): ConfigurationRepository
-
-    @Binds
     abstract fun bindPackageChangeRepository(
         impl: PackageChangeRepositoryImpl
     ): PackageChangeRepository
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationStateModule.kt b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationModule.kt
similarity index 67%
rename from packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationStateModule.kt
rename to packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationModule.kt
index b36da3b..7f50e4a 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationStateModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationModule.kt
@@ -17,6 +17,9 @@
 package com.android.systemui.common.ui
 
 import android.content.Context
+import com.android.systemui.common.ui.data.repository.ConfigurationRepository
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.statusbar.policy.ConfigurationController
@@ -28,24 +31,31 @@
 /**
  * Annotates elements that provide information from the global configuration.
  *
- * The global configuration is the one associted with the main display. Secondary displays will
+ * The global configuration is the one associated with the main display. Secondary displays will
  * apply override to the global configuration. Elements annotated with this shouldn't be used for
  * secondary displays.
  */
 @Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class GlobalConfig
 
 @Module
-interface ConfigurationStateModule {
+interface ConfigurationModule {
 
     /**
      * Deprecated: [ConfigurationState] should be injected only with the correct annotation. For
      * now, without annotation the global config associated state is provided.
      */
     @Binds
+    @Deprecated("Use the @GlobalConfig annotated one instead of this.")
     fun provideGlobalConfigurationState(
         @GlobalConfig configurationState: ConfigurationState
     ): ConfigurationState
 
+    @Binds
+    @Deprecated("Use the @GlobalConfig annotated one instead of this.")
+    fun provideDefaultConfigurationState(
+        @GlobalConfig configurationState: ConfigurationInteractor
+    ): ConfigurationInteractor
+
     companion object {
         @SysUISingleton
         @Provides
@@ -57,5 +67,14 @@
         ): ConfigurationState {
             return configStateFactory.create(context, configurationController)
         }
+
+        @SysUISingleton
+        @Provides
+        @GlobalConfig
+        fun provideGlobalConfigurationInteractor(
+            configurationRepository: ConfigurationRepository
+        ): ConfigurationInteractor {
+            return ConfigurationInteractorImpl(configurationRepository)
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
index 2052c70..df89152 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
@@ -23,13 +23,17 @@
 import androidx.annotation.DimenRes
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.common.ui.GlobalConfig
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.wrapper.DisplayUtilsWrapper
 import dagger.Binds
 import dagger.Module
-import javax.inject.Inject
+import dagger.Provides
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.channels.awaitClose
@@ -57,66 +61,62 @@
     fun getDimensionPixelSize(id: Int): Int
 }
 
-@SysUISingleton
 class ConfigurationRepositoryImpl
-@Inject
+@AssistedInject
 constructor(
-    private val configurationController: ConfigurationController,
-    private val context: Context,
+    @Assisted private val configurationController: ConfigurationController,
+    @Assisted private val context: Context,
     @Application private val scope: CoroutineScope,
     private val displayUtils: DisplayUtilsWrapper,
 ) : ConfigurationRepository {
     private val displayInfo = MutableStateFlow(DisplayInfo())
 
-    override val onAnyConfigurationChange: Flow<Unit> =
-        conflatedCallbackFlow {
-            val callback =
-                object : ConfigurationController.ConfigurationListener {
-                    override fun onUiModeChanged() {
-                        sendUpdate("ConfigurationRepository#onUiModeChanged")
-                    }
-
-                    override fun onThemeChanged() {
-                        sendUpdate("ConfigurationRepository#onThemeChanged")
-                    }
-
-                    override fun onConfigChanged(newConfig: Configuration) {
-                        sendUpdate("ConfigurationRepository#onConfigChanged")
-                    }
-
-                    fun sendUpdate(reason: String) {
-                        trySendWithFailureLogging(Unit, reason)
-                    }
+    override val onAnyConfigurationChange: Flow<Unit> = conflatedCallbackFlow {
+        val callback =
+            object : ConfigurationController.ConfigurationListener {
+                override fun onUiModeChanged() {
+                    sendUpdate("ConfigurationRepository#onUiModeChanged")
                 }
-            configurationController.addCallback(callback)
-            awaitClose { configurationController.removeCallback(callback) }
-        }
 
-    override val onConfigurationChange: Flow<Unit> =
-        conflatedCallbackFlow {
-            val callback =
-                object : ConfigurationController.ConfigurationListener {
-                    override fun onConfigChanged(newConfig: Configuration) {
-                        trySendWithFailureLogging(Unit, "ConfigurationRepository#onConfigChanged")
-                    }
+                override fun onThemeChanged() {
+                    sendUpdate("ConfigurationRepository#onThemeChanged")
                 }
-            configurationController.addCallback(callback)
-            awaitClose { configurationController.removeCallback(callback) }
-        }
 
-    override val configurationValues: Flow<Configuration> =
-            conflatedCallbackFlow {
-                val callback =
-                        object : ConfigurationController.ConfigurationListener {
-                            override fun onConfigChanged(newConfig: Configuration) {
-                                trySend(newConfig)
-                            }
-                        }
+                override fun onConfigChanged(newConfig: Configuration) {
+                    sendUpdate("ConfigurationRepository#onConfigChanged")
+                }
 
-                trySend(context.resources.configuration)
-                configurationController.addCallback(callback)
-                awaitClose { configurationController.removeCallback(callback) }
+                fun sendUpdate(reason: String) {
+                    trySendWithFailureLogging(Unit, reason)
+                }
             }
+        configurationController.addCallback(callback)
+        awaitClose { configurationController.removeCallback(callback) }
+    }
+
+    override val onConfigurationChange: Flow<Unit> = conflatedCallbackFlow {
+        val callback =
+            object : ConfigurationController.ConfigurationListener {
+                override fun onConfigChanged(newConfig: Configuration) {
+                    trySendWithFailureLogging(Unit, "ConfigurationRepository#onConfigChanged")
+                }
+            }
+        configurationController.addCallback(callback)
+        awaitClose { configurationController.removeCallback(callback) }
+    }
+
+    override val configurationValues: Flow<Configuration> = conflatedCallbackFlow {
+        val callback =
+            object : ConfigurationController.ConfigurationListener {
+                override fun onConfigChanged(newConfig: Configuration) {
+                    trySend(newConfig)
+                }
+            }
+
+        trySend(context.resources.configuration)
+        configurationController.addCallback(callback)
+        awaitClose { configurationController.removeCallback(callback) }
+    }
 
     override val scaleForResolution: StateFlow<Float> =
         onConfigurationChange
@@ -134,7 +134,7 @@
                     maxDisplayMode.physicalWidth,
                     maxDisplayMode.physicalHeight,
                     displayInfo.value.naturalWidth,
-                    displayInfo.value.naturalHeight
+                    displayInfo.value.naturalHeight,
                 )
             return if (scaleFactor == Float.POSITIVE_INFINITY) 1f else scaleFactor
         }
@@ -144,9 +144,40 @@
     override fun getDimensionPixelSize(@DimenRes id: Int): Int {
         return context.resources.getDimensionPixelSize(id)
     }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(
+            context: Context,
+            configurationController: ConfigurationController,
+        ): ConfigurationRepositoryImpl
+    }
 }
 
 @Module
-interface ConfigurationRepositoryModule {
-    @Binds fun bindImpl(impl: ConfigurationRepositoryImpl): ConfigurationRepository
+abstract class ConfigurationRepositoryModule {
+
+    /**
+     * For compatibility reasons. Ideally, only the an annotated [ConfigurationRepository] should be
+     * injected.
+     */
+    @Binds
+    @Deprecated("Use the ConfigurationRepository annotated with @GlobalConfig instead.")
+    @SysUISingleton
+    abstract fun provideDefaultConfigRepository(
+        @GlobalConfig configurationRepository: ConfigurationRepository
+    ): ConfigurationRepository
+
+    companion object {
+        @Provides
+        @GlobalConfig
+        @SysUISingleton
+        fun provideGlobalConfigRepository(
+            context: Context,
+            @GlobalConfig configurationController: ConfigurationController,
+            factory: ConfigurationRepositoryImpl.Factory,
+        ): ConfigurationRepository {
+            return factory.create(context, configurationController)
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt b/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
index adb1ee2..97a23e1 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
@@ -21,8 +21,6 @@
 import android.graphics.Rect
 import android.view.Surface
 import com.android.systemui.common.ui.data.repository.ConfigurationRepository
-import com.android.systemui.dagger.SysUISingleton
-import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
@@ -32,13 +30,52 @@
 import kotlinx.coroutines.flow.onStart
 
 /** Business logic related to configuration changes. */
-@SysUISingleton
-class ConfigurationInteractor @Inject constructor(private val repository: ConfigurationRepository) {
+interface ConfigurationInteractor {
     /**
      * Returns screen size adjusted to rotation, so returned screen size is stable across all
      * rotations
      */
-    private val Configuration.naturalScreenBounds: Rect
+    val Configuration.naturalScreenBounds: Rect
+
+    /** Returns the unadjusted screen size. */
+    val maxBounds: Flow<Rect>
+
+    /**
+     * Returns screen size adjusted to rotation, so returned screen sizes are stable across all
+     * rotations, could be useful if you need to react to screen resize (e.g. fold/unfold on
+     * foldable devices)
+     */
+    val naturalMaxBounds: Flow<Rect>
+
+    /**
+     * The layout direction. Will be either `View#LAYOUT_DIRECTION_LTR` or
+     * `View#LAYOUT_DIRECTION_RTL`.
+     */
+    val layoutDirection: Flow<Int>
+
+    /** Emit an event on any config change */
+    val onAnyConfigurationChange: Flow<Unit>
+
+    /** Emits the new configuration on any configuration change */
+    val configurationValues: Flow<Configuration>
+
+    /** Emits the current resolution scaling factor */
+    val scaleForResolution: Flow<Float>
+
+    /** Given [resourceId], emit the dimension pixel size on config change */
+    fun dimensionPixelSize(resourceId: Int): Flow<Int>
+
+    /** Emits the dimensional pixel size of the given resource, inverting it for RTL if necessary */
+    fun directionalDimensionPixelSize(originLayoutDirection: Int, resourceId: Int): Flow<Int>
+
+    /** Given a set of [resourceId]s, emit Map<ResourceId, DimensionPixelSize> on config change */
+    fun dimensionPixelSize(resourceIds: Set<Int>): Flow<Map<Int, Int>>
+}
+
+class ConfigurationInteractorImpl(private val repository: ConfigurationRepository) :
+    ConfigurationInteractor {
+
+    override val Configuration.naturalScreenBounds: Rect
         get() {
             val rotation = windowConfiguration.displayRotation
             val maxBounds = windowConfiguration.maxBounds
@@ -49,53 +86,40 @@
             }
         }
 
-    /** Returns the unadjusted screen size. */
-    val maxBounds: Flow<Rect> =
+    override val maxBounds: Flow<Rect> =
         repository.configurationValues
             .map { Rect(it.windowConfiguration.maxBounds) }
             .distinctUntilChanged()
 
-    /**
-     * Returns screen size adjusted to rotation, so returned screen sizes are stable across all
-     * rotations, could be useful if you need to react to screen resize (e.g. fold/unfold on
-     * foldable devices)
-     */
-    val naturalMaxBounds: Flow<Rect> =
+    override val naturalMaxBounds: Flow<Rect> =
         repository.configurationValues.map { it.naturalScreenBounds }.distinctUntilChanged()
 
-    /**
-     * The layout direction. Will be either `View#LAYOUT_DIRECTION_LTR` or
-     * `View#LAYOUT_DIRECTION_RTL`.
-     */
-    val layoutDirection: Flow<Int> =
+    override val layoutDirection: Flow<Int> =
         repository.configurationValues.map { it.layoutDirection }.distinctUntilChanged()
 
-    /** Given [resourceId], emit the dimension pixel size on config change */
-    fun dimensionPixelSize(resourceId: Int): Flow<Int> {
+    override fun dimensionPixelSize(resourceId: Int): Flow<Int> {
         return onAnyConfigurationChange.mapLatest { repository.getDimensionPixelSize(resourceId) }
     }
 
-    /** Emits the dimensional pixel size of the given resource, inverting it for RTL if necessary */
-    fun directionalDimensionPixelSize(originLayoutDirection: Int, resourceId: Int): Flow<Int> {
+    override fun directionalDimensionPixelSize(
+        originLayoutDirection: Int,
+        resourceId: Int,
+    ): Flow<Int> {
         return dimensionPixelSize(resourceId).combine(layoutDirection) { size, direction ->
             if (originLayoutDirection == direction) size else -size
         }
     }
 
-    /** Given a set of [resourceId]s, emit Map<ResourceId, DimensionPixelSize> on config change */
-    fun dimensionPixelSize(resourceIds: Set<Int>): Flow<Map<Int, Int>> {
+    override fun dimensionPixelSize(resourceIds: Set<Int>): Flow<Map<Int, Int>> {
         return onAnyConfigurationChange.mapLatest {
             resourceIds.associateWith { repository.getDimensionPixelSize(it) }
         }
     }
 
-    /** Emit an event on any config change */
-    val onAnyConfigurationChange: Flow<Unit> =
+    override val onAnyConfigurationChange: Flow<Unit> =
         repository.onAnyConfigurationChange.onStart { emit(Unit) }
 
-    /** Emits the new configuration on any configuration change */
-    val configurationValues: Flow<Configuration> = repository.configurationValues
+    override val configurationValues: Flow<Configuration> = repository.configurationValues
 
-    /** Emits the current resolution scaling factor */
-    val scaleForResolution: Flow<Float> = repository.scaleForResolution
+    override val scaleForResolution: Flow<Float> = repository.scaleForResolution
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
index 012c844..b80e77c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
@@ -19,13 +19,13 @@
 import android.app.smartspace.SmartspaceConfig
 import android.app.smartspace.SmartspaceManager
 import android.app.smartspace.SmartspaceSession
-import android.content.Context
 import android.util.Log
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.BcSmartspaceDataPlugin
 import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener
 import com.android.systemui.plugins.BcSmartspaceDataPlugin.UI_SURFACE_GLANCEABLE_HUB
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.smartspace.SmartspacePrecondition
 import com.android.systemui.smartspace.SmartspaceTargetFilter
 import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.GLANCEABLE_HUB_SMARTSPACE_DATA_PLUGIN
@@ -42,8 +42,7 @@
 class CommunalSmartspaceController
 @Inject
 constructor(
-    private val context: Context,
-    private val smartspaceManager: SmartspaceManager?,
+    private val userTracker: UserTracker,
     private val execution: Execution,
     @Main private val uiExecutor: Executor,
     @Named(LOCKSCREEN_SMARTSPACE_PRECONDITION) private val precondition: SmartspacePrecondition,
@@ -55,6 +54,7 @@
         private const val TAG = "CommunalSmartspaceCtrlr"
     }
 
+    private var userSmartspaceManager: SmartspaceManager? = null
     private var session: SmartspaceSession? = null
     private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null)
     private var targetFilter: SmartspaceTargetFilter? = optionalTargetFilter.orElse(null)
@@ -104,7 +104,11 @@
     }
 
     private fun connectSession() {
-        if (smartspaceManager == null) {
+        if (userSmartspaceManager == null) {
+            userSmartspaceManager =
+                userTracker.userContext.getSystemService(SmartspaceManager::class.java)
+        }
+        if (userSmartspaceManager == null) {
             return
         }
         if (plugin == null) {
@@ -119,11 +123,11 @@
         }
 
         val newSession =
-            smartspaceManager.createSmartspaceSession(
-                SmartspaceConfig.Builder(context, UI_SURFACE_GLANCEABLE_HUB).build()
+            userSmartspaceManager?.createSmartspaceSession(
+                SmartspaceConfig.Builder(userTracker.userContext, UI_SURFACE_GLANCEABLE_HUB).build()
             )
         Log.d(TAG, "Starting smartspace session for communal")
-        newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener)
+        newSession?.addOnTargetsAvailableListener(uiExecutor, sessionListener)
         this.session = newSession
 
         plugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
@@ -163,7 +167,7 @@
 
     private fun addAndRegisterListener(
         listener: SmartspaceTargetListener,
-        smartspaceDataPlugin: BcSmartspaceDataPlugin?
+        smartspaceDataPlugin: BcSmartspaceDataPlugin?,
     ) {
         execution.assertIsMainThread()
         smartspaceDataPlugin?.registerListener(listener)
@@ -174,7 +178,7 @@
 
     private fun removeAndUnregisterListener(
         listener: SmartspaceTargetListener,
-        smartspaceDataPlugin: BcSmartspaceDataPlugin?
+        smartspaceDataPlugin: BcSmartspaceDataPlugin?,
     ) {
         execution.assertIsMainThread()
         smartspaceDataPlugin?.unregisterListener(listener)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index ca4bbc0..00c6209 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -33,6 +33,7 @@
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.ui.Modifier
 import androidx.lifecycle.lifecycleScope
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.compose.theme.PlatformTheme
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.Flags.communalEditWidgetsActivityFinishFix
@@ -44,6 +45,7 @@
 import com.android.systemui.communal.ui.view.layout.sections.CommunalAppWidgetSection
 import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
 import com.android.systemui.communal.util.WidgetPickerIntentUtils.getWidgetExtraFromIntent
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.Logger
@@ -52,13 +54,13 @@
 import com.android.systemui.settings.UserTracker
 import javax.inject.Inject
 import kotlinx.coroutines.flow.first
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /** An Activity for editing the widgets that appear in hub mode. */
 class EditWidgetsActivity
 @Inject
 constructor(
     private val communalViewModel: CommunalEditModeViewModel,
+    private val keyguardInteractor: KeyguardInteractor,
     private var windowManagerService: IWindowManager? = null,
     private val uiEventLogger: UiEventLogger,
     private val widgetConfiguratorFactory: WidgetConfigurationController.Factory,
@@ -223,6 +225,9 @@
                     communalViewModel.currentScene.first { it == CommunalScenes.Blank }
                 }
 
+                // Wait for dream to exit, if we were previously dreaming.
+                keyguardInteractor.isDreaming.first { !it }
+
                 communalViewModel.setEditModeState(EditModeState.SHOWING)
 
                 // Inform the ActivityController that we are now fully visible.
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManager.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManager.kt
index 202edf7..2f686fd 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManager.kt
@@ -19,7 +19,10 @@
 import android.appwidget.AppWidgetHost.AppWidgetHostListener
 import android.appwidget.AppWidgetProviderInfo
 import android.content.ComponentName
+import android.content.IntentSender
 import android.os.IBinder
+import android.os.OutcomeReceiver
+import android.os.RemoteException
 import android.os.UserHandle
 import android.widget.RemoteViews
 import com.android.server.servicewatcher.ServiceWatcher
@@ -27,14 +30,19 @@
 import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
 import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper
 import com.android.systemui.communal.widgets.IGlanceableHubWidgetManagerService.IAppWidgetHostListener
+import com.android.systemui.communal.widgets.IGlanceableHubWidgetManagerService.IConfigureWidgetCallback
 import com.android.systemui.communal.widgets.IGlanceableHubWidgetManagerService.IGlanceableHubWidgetsListener
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.Logger
 import com.android.systemui.log.dagger.CommunalLog
 import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import java.util.concurrent.Executor
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.launch
 
 /**
  * Manages updates to Glanceable Hub widgets and requests to edit them from the headless system
@@ -47,6 +55,8 @@
 class GlanceableHubWidgetManager
 @Inject
 constructor(
+    @Background private val bgExecutor: Executor,
+    @Background private val bgScope: CoroutineScope,
     glanceableHubMultiUserHelper: GlanceableHubMultiUserHelper,
     @CommunalLog logBuffer: LogBuffer,
     serviceWatcherFactory: ServiceWatcherFactory<GlanceableHubWidgetManagerServiceInfo?>,
@@ -101,8 +111,7 @@
         rank: Int?,
         configurator: WidgetConfigurator?,
     ) = runOnService { service ->
-        // TODO(b/375036327): Add support for widget configuration
-        service.addWidget(provider, user, rank ?: -1)
+        service.addWidget(provider, user, rank ?: -1, createIConfigureWidgetCallback(configurator))
     }
 
     /** Requests the foreground user to delete a widget. */
@@ -129,18 +138,54 @@
             )
         }
 
-    private fun runOnService(block: (IGlanceableHubWidgetManagerService) -> Unit) {
-        serviceWatcher.runOnBinder(
-            object : ServiceWatcher.BinderOperation {
-                override fun run(binder: IBinder?) {
-                    block(IGlanceableHubWidgetManagerService.Stub.asInterface(binder))
-                }
+    /**
+     * Requests the foreground user for the [IntentSender] to start a configuration activity for the
+     * widget.
+     *
+     * @param appWidgetId Id of the widget to configure.
+     * @param outcomeReceiver Callback for receiving the result or error.
+     * @param executor Executor to run the callback on.
+     */
+    fun getIntentSenderForConfigureActivity(
+        appWidgetId: Int,
+        outcomeReceiver: OutcomeReceiver<IntentSender?, Throwable>,
+        executor: Executor,
+    ) {
+        bgExecutor.execute {
+            serviceWatcher.runOnBinder(
+                object : ServiceWatcher.BinderOperation {
+                    override fun run(binder: IBinder?) {
+                        val service = IGlanceableHubWidgetManagerService.Stub.asInterface(binder)
+                        try {
+                            val result = service.getIntentSenderForConfigureActivity(appWidgetId)
+                            executor.execute { outcomeReceiver.onResult(result) }
+                        } catch (e: RemoteException) {
+                            executor.execute { outcomeReceiver.onError(e) }
+                        }
+                    }
 
-                override fun onError(t: Throwable?) {
-                    // TODO(b/375236794): handle failure in case service is unbound
+                    override fun onError(t: Throwable?) {
+                        t?.let { executor.execute { outcomeReceiver.onError(t) } }
+                    }
                 }
-            }
-        )
+            )
+        }
+    }
+
+    private fun runOnService(block: (IGlanceableHubWidgetManagerService) -> Unit) {
+        bgExecutor.execute {
+            serviceWatcher.runOnBinder(
+                object : ServiceWatcher.BinderOperation {
+                    override fun run(binder: IBinder?) {
+                        block(IGlanceableHubWidgetManagerService.Stub.asInterface(binder))
+                    }
+
+                    override fun onError(t: Throwable?) {
+                        // TODO(b/375236794): handle failure in case service is unbound
+                    }
+                }
+            )
+        }
     }
 
     private fun createIAppWidgetHostListener(
@@ -165,6 +210,30 @@
         }
     }
 
+    private fun createIConfigureWidgetCallback(
+        configurator: WidgetConfigurator?
+    ): IConfigureWidgetCallback? {
+        return configurator?.let {
+            object : IConfigureWidgetCallback.Stub() {
+                override fun onConfigureWidget(
+                    appWidgetId: Int,
+                    resultReceiver: IConfigureWidgetCallback.IResultReceiver?,
+                ) {
+                    bgScope.launch {
+                        val success = configurator.configureWidget(appWidgetId)
+                        try {
+                            resultReceiver?.onResult(success)
+                        } catch (e: RemoteException) {
+                            logger.e({ "Error reporting widget configuration result: $str1" }) {
+                                str1 = e.localizedMessage
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
     companion object {
         private const val TAG = "GlanceableHubWidgetManager"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerService.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerService.kt
index 4d042fc..0e43e2a 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerService.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerService.kt
@@ -20,8 +20,10 @@
 import android.appwidget.AppWidgetProviderInfo
 import android.content.ComponentName
 import android.content.Intent
+import android.content.IntentSender
 import android.os.IBinder
 import android.os.RemoteCallbackList
+import android.os.RemoteException
 import android.os.UserHandle
 import android.widget.RemoteViews
 import androidx.lifecycle.LifecycleService
@@ -29,11 +31,13 @@
 import com.android.systemui.communal.data.repository.CommunalWidgetRepository
 import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper
 import com.android.systemui.communal.widgets.IGlanceableHubWidgetManagerService.IAppWidgetHostListener
+import com.android.systemui.communal.widgets.IGlanceableHubWidgetManagerService.IConfigureWidgetCallback
 import com.android.systemui.communal.widgets.IGlanceableHubWidgetManagerService.IGlanceableHubWidgetsListener
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.Logger
 import com.android.systemui.log.dagger.CommunalLog
 import javax.inject.Inject
+import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
@@ -98,7 +102,15 @@
 
         val job =
             widgetRepository.communalWidgets
-                .onEach { widgets -> listener.onWidgetsUpdated(widgets) }
+                .onEach { widgets ->
+                    try {
+                        listener.onWidgetsUpdated(widgets)
+                    } catch (e: RemoteException) {
+                        logger.e({ "Error pushing widget update: $str1" }) {
+                            str1 = e.localizedMessage
+                        }
+                    }
+                }
                 .launchIn(lifecycleScope)
         widgetListenersRegistry.register(listener, job)
     }
@@ -122,7 +134,12 @@
         appWidgetHost.setListener(appWidgetId, createListener(listener))
     }
 
-    private fun addWidgetInternal(provider: ComponentName?, user: UserHandle?, rank: Int) {
+    private fun addWidgetInternal(
+        provider: ComponentName?,
+        user: UserHandle?,
+        rank: Int,
+        callback: IConfigureWidgetCallback?,
+    ) {
         if (provider == null) {
             throw IllegalStateException("Provider cannot be null")
         }
@@ -131,8 +148,29 @@
             throw IllegalStateException("User cannot be null")
         }
 
-        // TODO(b/375036327): Add support for widget configuration
-        widgetRepository.addWidget(provider, user, rank, configurator = null)
+        val configurator =
+            callback?.let {
+                WidgetConfigurator { appWidgetId ->
+                    try {
+                        val result = CompletableDeferred<Boolean>()
+                        val resultReceiver =
+                            object : IConfigureWidgetCallback.IResultReceiver.Stub() {
+                                override fun onResult(success: Boolean) {
+                                    result.complete(success)
+                                }
+                            }
+
+                        callback.onConfigureWidget(appWidgetId, resultReceiver)
+                        result.await()
+                    } catch (e: RemoteException) {
+                        logger.e({ "Error configuring widget: $str1" }) {
+                            str1 = e.localizedMessage
+                        }
+                        false
+                    }
+                }
+            }
+        widgetRepository.addWidget(provider, user, rank, configurator)
     }
 
     private fun deleteWidgetInternal(appWidgetId: Int) {
@@ -168,22 +206,55 @@
         widgetRepository.resizeWidget(appWidgetId, spanY, appWidgetIds.zip(ranks).toMap())
     }
 
+    private fun getIntentSenderForConfigureActivityInternal(appWidgetId: Int): IntentSender? {
+        return try {
+            appWidgetHost.getIntentSenderForConfigureActivity(appWidgetId, /* intentFlags= */ 0)
+        } catch (e: IntentSender.SendIntentException) {
+            logger.e({ "Error getting intent sender for configure activity" }) {
+                str1 = e.localizedMessage
+            }
+            null
+        }
+    }
+
     private fun createListener(listener: IAppWidgetHostListener): AppWidgetHostListener {
         return object : AppWidgetHostListener {
             override fun onUpdateProviderInfo(appWidget: AppWidgetProviderInfo?) {
-                listener.onUpdateProviderInfo(appWidget)
+                try {
+                    listener.onUpdateProviderInfo(appWidget)
+                } catch (e: RemoteException) {
+                    logger.e({ "Error pushing on update provider info: $str1" }) {
+                        str1 = e.localizedMessage
+                    }
+                }
             }
 
             override fun updateAppWidget(views: RemoteViews?) {
-                listener.updateAppWidget(views)
+                try {
+                    listener.updateAppWidget(views)
+                } catch (e: RemoteException) {
+                    logger.e({ "Error updating app widget: $str1" }) { str1 = e.localizedMessage }
+                }
             }
 
             override fun updateAppWidgetDeferred(packageName: String?, appWidgetId: Int) {
-                listener.updateAppWidgetDeferred(packageName, appWidgetId)
+                try {
+                    listener.updateAppWidgetDeferred(packageName, appWidgetId)
+                } catch (e: RemoteException) {
+                    logger.e({ "Error updating app widget deferred: $str1" }) {
+                        str1 = e.localizedMessage
+                    }
+                }
             }
 
             override fun onViewDataChanged(viewId: Int) {
-                listener.onViewDataChanged(viewId)
+                try {
+                    listener.onViewDataChanged(viewId)
+                } catch (e: RemoteException) {
+                    logger.e({ "Error pushing on view data changed: $str1" }) {
+                        str1 = e.localizedMessage
+                    }
+                }
             }
         }
     }
@@ -219,11 +290,16 @@
             }
         }
 
-        override fun addWidget(provider: ComponentName?, user: UserHandle?, rank: Int) {
+        override fun addWidget(
+            provider: ComponentName?,
+            user: UserHandle?,
+            rank: Int,
+            callback: IConfigureWidgetCallback?,
+        ) {
             val iden = clearCallingIdentity()
 
             try {
-                addWidgetInternal(provider, user, rank)
+                addWidgetInternal(provider, user, rank, callback)
             } finally {
                 restoreCallingIdentity(iden)
             }
@@ -263,6 +339,16 @@
                 restoreCallingIdentity(iden)
             }
         }
+
+        override fun getIntentSenderForConfigureActivity(appWidgetId: Int): IntentSender? {
+            val iden = clearCallingIdentity()
+
+            try {
+                return getIntentSenderForConfigureActivityInternal(appWidgetId)
+            } finally {
+                restoreCallingIdentity(iden)
+            }
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/IGlanceableHubWidgetManagerService.aidl b/packages/SystemUI/src/com/android/systemui/communal/widgets/IGlanceableHubWidgetManagerService.aidl
index e556472..d71b230 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/IGlanceableHubWidgetManagerService.aidl
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/IGlanceableHubWidgetManagerService.aidl
@@ -2,6 +2,7 @@
 
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.ComponentName;
+import android.content.IntentSender;
 import android.os.UserHandle;
 import android.widget.RemoteViews;
 import com.android.systemui.communal.shared.model.CommunalWidgetContentModel;
@@ -21,7 +22,8 @@
     oneway void setAppWidgetHostListener(int appWidgetId, in IAppWidgetHostListener listener);
 
     // Requests to add a widget in the Glanceable Hub.
-    oneway void addWidget(in ComponentName provider, in UserHandle user, int rank);
+    oneway void addWidget(in ComponentName provider, in UserHandle user, int rank,
+        in IConfigureWidgetCallback callback);
 
     // Requests to delete a widget from the Glanceable Hub.
     oneway void deleteWidget(int appWidgetId);
@@ -32,6 +34,9 @@
     // Requests to resize a widget in the Glanceable Hub.
     oneway void resizeWidget(int appWidgetId, int spanY, in int[] appWidgetIds, in int[] ranks);
 
+    // Returns the [IntentSender] for launching the configuration activity of the given widget.
+    IntentSender getIntentSenderForConfigureActivity(int appWidgetId);
+
     // Listener for Glanceable Hub widget updates
     oneway interface IGlanceableHubWidgetsListener {
         // Called when widgets have updated.
@@ -48,4 +53,15 @@
 
         void onViewDataChanged(int viewId);
     }
+
+    oneway interface IConfigureWidgetCallback {
+        // Called when the given widget should launch its configuration activity. The caller should
+        // report the result through the [IResultReceiver].
+        void onConfigureWidget(int appWidgetId, in IResultReceiver resultReceiver);
+
+        interface IResultReceiver {
+            // Called when the widget configuration operation returns a result.
+            void onResult(boolean success);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetConfigurationController.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetConfigurationController.kt
index d157cd7..fddec56 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetConfigurationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetConfigurationController.kt
@@ -18,16 +18,19 @@
 
 import android.app.Activity
 import android.app.ActivityOptions
-import android.content.ActivityNotFoundException
+import android.content.IntentSender
+import android.os.OutcomeReceiver
 import android.window.SplashScreen
 import androidx.activity.ComponentActivity
 import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.util.nullableAtomicReference
 import dagger.Lazy
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
+import java.util.concurrent.Executor
 import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.withContext
@@ -43,12 +46,22 @@
     private val appWidgetHostLazy: Lazy<CommunalAppWidgetHost>,
     @Background private val bgDispatcher: CoroutineDispatcher,
     private val glanceableHubMultiUserHelper: GlanceableHubMultiUserHelper,
-) : WidgetConfigurator {
+    private val glanceableHubWidgetManagerLazy: Lazy<GlanceableHubWidgetManager>,
+    @Main private val mainExecutor: Executor,
+) : WidgetConfigurator, OutcomeReceiver<IntentSender?, Throwable> {
     @AssistedFactory
     fun interface Factory {
         fun create(activity: ComponentActivity): WidgetConfigurationController
     }
 
+    private val activityOptions: ActivityOptions
+        get() =
+            ActivityOptions.makeBasic().apply {
+                pendingIntentBackgroundActivityStartMode =
+                    ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+                splashScreenStyle = SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR
+            }
+
     private var result: CompletableDeferred<Boolean>? by nullableAtomicReference()
 
     override suspend fun configureWidget(appWidgetId: Int): Boolean =
@@ -57,37 +70,64 @@
                 throw IllegalStateException("There is already a pending configuration")
             }
             result = CompletableDeferred()
-            val options =
-                ActivityOptions.makeBasic().apply {
-                    pendingIntentBackgroundActivityStartMode =
-                        ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
-                    splashScreenStyle = SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR
-                }
 
             try {
-                // TODO(b/375036327): Add support for widget configuration
                 if (
                     !glanceableHubMultiUserHelper.glanceableHubHsumFlagEnabled ||
                         !glanceableHubMultiUserHelper.isInHeadlessSystemUser()
                 ) {
+                    // Start configuration activity directly if we're running in a foreground user
                     with(appWidgetHostLazy.get()) {
                         startAppWidgetConfigureActivityForResult(
                             activity,
                             appWidgetId,
                             0,
                             REQUEST_CODE,
-                            options.toBundle(),
+                            activityOptions.toBundle(),
+                        )
+                    }
+                } else {
+                    with(glanceableHubWidgetManagerLazy.get()) {
+                        // Use service to get intent sender and start configuration activity
+                        // locally if running in a headless system user
+                        getIntentSenderForConfigureActivity(
+                            appWidgetId,
+                            outcomeReceiver = this@WidgetConfigurationController,
+                            mainExecutor,
                         )
                     }
                 }
-            } catch (e: ActivityNotFoundException) {
+            } catch (_: Exception) {
                 setConfigurationResult(Activity.RESULT_CANCELED)
             }
-            val value = result?.await() ?: false
+            val value = result?.await() == true
             result = null
             return@withContext value
         }
 
+    // Called when an intent sender is returned, and the configuration activity should be started.
+    override fun onResult(intentSender: IntentSender?) {
+        if (intentSender == null) {
+            setConfigurationResult(Activity.RESULT_CANCELED)
+            return
+        }
+
+        activity.startIntentSenderForResult(
+            intentSender,
+            REQUEST_CODE,
+            /* fillInIntent = */ null,
+            /* flagsMask = */ 0,
+            /* flagsValues = */ 0,
+            /* extraFlags = */ 0,
+            activityOptions.toBundle(),
+        )
+    }
+
+    // Called when there is an error getting the intent sender.
+    override fun onError(e: Throwable) {
+        setConfigurationResult(Activity.RESULT_CANCELED)
+    }
+
     fun setConfigurationResult(resultCode: Int) {
         result?.complete(resultCode == Activity.RESULT_OK)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index 609b733..2e323d4 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -29,7 +29,7 @@
 import com.android.systemui.accessibility.SystemActionsModule;
 import com.android.systemui.accessibility.data.repository.AccessibilityRepositoryModule;
 import com.android.systemui.battery.BatterySaverModule;
-import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlaySuppressionModule;
+import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayOverrideModule;
 import com.android.systemui.display.ui.viewmodel.ConnectingDisplayViewModel;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dock.DockManagerImpl;
@@ -125,7 +125,7 @@
         AospPolicyModule.class,
         BatterySaverModule.class,
         CentralSurfacesModule.class,
-        ClipboardOverlaySuppressionModule.class,
+        ClipboardOverlayOverrideModule.class,
         CollapsedStatusBarFragmentStartableModule.class,
         ConnectingDisplayViewModel.StartableModule.class,
         DefaultBlueprintModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index cb649f2..4447dff 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -48,7 +48,8 @@
 import com.android.systemui.classifier.FalsingModule;
 import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule;
 import com.android.systemui.common.data.CommonDataLayerModule;
-import com.android.systemui.common.ui.ConfigurationStateModule;
+import com.android.systemui.common.ui.ConfigurationModule;
+import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryModule;
 import com.android.systemui.common.usagestats.data.CommonUsageStatsDataLayerModule;
 import com.android.systemui.communal.dagger.CommunalModule;
 import com.android.systemui.complication.dagger.ComplicationComponent;
@@ -211,7 +212,8 @@
         ClockRegistryModule.class,
         CommunalModule.class,
         CommonDataLayerModule.class,
-        ConfigurationStateModule.class,
+        ConfigurationModule.class,
+        ConfigurationRepositoryModule.class,
         CommonUsageStatsDataLayerModule.class,
         ConfigurationControllerModule.class,
         ConnectivityModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/service/HomeControlsRemoteProxy.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/service/HomeControlsRemoteProxy.kt
index 2bcfea8..45a5901 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/service/HomeControlsRemoteProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/service/HomeControlsRemoteProxy.kt
@@ -16,20 +16,16 @@
 
 package com.android.systemui.dreams.homecontrols.service
 
-import android.content.ComponentName
-import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dreams.homecontrols.shared.IHomeControlsRemoteProxy
-import com.android.systemui.dreams.homecontrols.shared.IOnControlsSettingsChangeListener
+import com.android.systemui.dreams.homecontrols.shared.controlsSettings
 import com.android.systemui.dreams.homecontrols.shared.model.HomeControlsComponentInfo
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.util.kotlin.FlowDumperImpl
-import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.distinctUntilChanged
@@ -45,30 +41,8 @@
     @Assisted private val proxy: IHomeControlsRemoteProxy,
 ) : FlowDumperImpl(dumpManager) {
 
-    private companion object {
-        const val TAG = "HomeControlsRemoteProxy"
-    }
-
     val componentInfo: Flow<HomeControlsComponentInfo> =
-        conflatedCallbackFlow {
-                val listener =
-                    object : IOnControlsSettingsChangeListener.Stub() {
-                        override fun onControlsSettingsChanged(
-                            panelComponent: ComponentName?,
-                            allowTrivialControlsOnLockscreen: Boolean,
-                        ) {
-                            trySendWithFailureLogging(
-                                HomeControlsComponentInfo(
-                                    panelComponent,
-                                    allowTrivialControlsOnLockscreen,
-                                ),
-                                TAG,
-                            )
-                        }
-                    }
-                proxy.registerListenerForCurrentUser(listener)
-                awaitClose { proxy.unregisterListenerForCurrentUser(listener) }
-            }
+        proxy.controlsSettings
             .distinctUntilChanged()
             .stateIn(bgScope, SharingStarted.WhileSubscribed(), null)
             .dumpValue("componentInfo")
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/shared/IHomeControlsRemoteProxyExt.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/shared/IHomeControlsRemoteProxyExt.kt
new file mode 100644
index 0000000..2993ab5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/shared/IHomeControlsRemoteProxyExt.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 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.dreams.homecontrols.shared
+
+import android.content.ComponentName
+import com.android.systemui.dreams.homecontrols.shared.model.HomeControlsComponentInfo
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+val IHomeControlsRemoteProxy.controlsSettings: Flow<HomeControlsComponentInfo>
+    get() = conflatedCallbackFlow {
+        val listener =
+            object : IOnControlsSettingsChangeListener.Stub() {
+                override fun onControlsSettingsChanged(
+                    panelComponent: ComponentName?,
+                    allowTrivialControlsOnLockscreen: Boolean,
+                ) {
+                    trySend(
+                        HomeControlsComponentInfo(panelComponent, allowTrivialControlsOnLockscreen)
+                    )
+                }
+            }
+        registerListenerForCurrentUser(listener)
+        awaitClose { unregisterListenerForCurrentUser(listener) }
+    }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteService.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteService.kt
index a65d216..6d1fd4d 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteService.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteService.kt
@@ -57,6 +57,11 @@
         super.onBind(intent)
         return binder
     }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        binder.onDestroy()
+    }
 }
 
 class HomeControlsRemoteServiceBinder
@@ -148,6 +153,14 @@
         }
     }
 
+    fun onDestroy() {
+        logger.d("Service destroyed")
+        callbacks.kill()
+        callbackCount.set(0)
+        collectionJob?.cancel()
+        collectionJob = null
+    }
+
     @AssistedFactory
     interface Factory {
         fun create(lifecycleOwner: LifecycleOwner): HomeControlsRemoteServiceBinder
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
index 4a8c040..fd91389 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
@@ -20,7 +20,6 @@
 import android.app.smartspace.SmartspaceManager
 import android.app.smartspace.SmartspaceSession
 import android.app.smartspace.SmartspaceTarget
-import android.content.Context
 import android.graphics.Color
 import android.util.Log
 import android.view.View
@@ -31,6 +30,7 @@
 import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener
 import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
 import com.android.systemui.plugins.BcSmartspaceDataPlugin.UI_SURFACE_DREAM
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.smartspace.SmartspacePrecondition
 import com.android.systemui.smartspace.SmartspaceTargetFilter
 import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_DATA_PLUGIN
@@ -44,13 +44,12 @@
 import javax.inject.Inject
 import javax.inject.Named
 
-/**
- * Controller for managing the smartspace view on the dream
- */
+/** Controller for managing the smartspace view on the dream */
 @SysUISingleton
-class DreamSmartspaceController @Inject constructor(
-    private val context: Context,
-    private val smartspaceManager: SmartspaceManager?,
+class DreamSmartspaceController
+@Inject
+constructor(
+    private val userTracker: UserTracker,
     private val execution: Execution,
     @Main private val uiExecutor: Executor,
     private val smartspaceViewComponentFactory: SmartspaceViewComponent.Factory,
@@ -65,6 +64,7 @@
         private const val TAG = "DreamSmartspaceCtrlr"
     }
 
+    private var userSmartspaceManager: SmartspaceManager? = null
     private var session: SmartspaceSession? = null
     private val weatherPlugin: BcSmartspaceDataPlugin? = optionalWeatherPlugin.orElse(null)
     private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null)
@@ -78,66 +78,68 @@
     // Smartspace can be used on multiple displays, such as when the user casts their screen
     private var smartspaceViews = mutableSetOf<SmartspaceView>()
 
-    var preconditionListener = object : SmartspacePrecondition.Listener {
-        override fun onCriteriaChanged() {
-            reloadSmartspace()
+    var preconditionListener =
+        object : SmartspacePrecondition.Listener {
+            override fun onCriteriaChanged() {
+                reloadSmartspace()
+            }
         }
-    }
 
     init {
         precondition.addListener(preconditionListener)
     }
 
-    var filterListener = object : SmartspaceTargetFilter.Listener {
-        override fun onCriteriaChanged() {
-            reloadSmartspace()
+    var filterListener =
+        object : SmartspaceTargetFilter.Listener {
+            override fun onCriteriaChanged() {
+                reloadSmartspace()
+            }
         }
-    }
 
     init {
         targetFilter?.addListener(filterListener)
     }
 
-    var stateChangeListener = object : View.OnAttachStateChangeListener {
-        override fun onViewAttachedToWindow(v: View) {
-            val view = v as SmartspaceView
-            // Until there is dream color matching
-            view.setPrimaryTextColor(Color.WHITE)
-            smartspaceViews.add(view)
-            connectSession()
-            view.setDozeAmount(0f)
-        }
+    var stateChangeListener =
+        object : View.OnAttachStateChangeListener {
+            override fun onViewAttachedToWindow(v: View) {
+                val view = v as SmartspaceView
+                // Until there is dream color matching
+                view.setPrimaryTextColor(Color.WHITE)
+                smartspaceViews.add(view)
+                connectSession()
+                view.setDozeAmount(0f)
+            }
 
-        override fun onViewDetachedFromWindow(v: View) {
-            smartspaceViews.remove(v as SmartspaceView)
+            override fun onViewDetachedFromWindow(v: View) {
+                smartspaceViews.remove(v as SmartspaceView)
 
-            if (smartspaceViews.isEmpty()) {
-                disconnect()
+                if (smartspaceViews.isEmpty()) {
+                    disconnect()
+                }
             }
         }
-    }
 
-    private val sessionListener = SmartspaceSession.OnTargetsAvailableListener { targets ->
-        execution.assertIsMainThread()
+    private val sessionListener =
+        SmartspaceSession.OnTargetsAvailableListener { targets ->
+            execution.assertIsMainThread()
 
-        // The weather data plugin takes unfiltered targets and performs the filtering internally.
-        weatherPlugin?.onTargetsAvailable(targets)
+            // The weather data plugin takes unfiltered targets and performs the filtering
+            // internally.
+            weatherPlugin?.onTargetsAvailable(targets)
 
-        onTargetsAvailableUnfiltered(targets)
-        val filteredTargets = targets.filter { targetFilter?.filterSmartspaceTarget(it) ?: true }
-        plugin?.onTargetsAvailable(filteredTargets)
-    }
+            onTargetsAvailableUnfiltered(targets)
+            val filteredTargets =
+                targets.filter { targetFilter?.filterSmartspaceTarget(it) ?: true }
+            plugin?.onTargetsAvailable(filteredTargets)
+        }
 
-    /**
-     * Constructs the weather view with custom layout and connects it to the weather plugin.
-     */
+    /** Constructs the weather view with custom layout and connects it to the weather plugin. */
     fun buildAndConnectWeatherView(parent: ViewGroup, customView: View?): View? {
         return buildAndConnectViewWithPlugin(parent, weatherPlugin, customView)
     }
 
-    /**
-     * Constructs the smartspace view and connects it to the smartspace service.
-     */
+    /** Constructs the smartspace view and connects it to the smartspace service. */
     fun buildAndConnectView(parent: ViewGroup): View? {
         return buildAndConnectViewWithPlugin(parent, plugin, null)
     }
@@ -145,7 +147,7 @@
     private fun buildAndConnectViewWithPlugin(
         parent: ViewGroup,
         smartspaceDataPlugin: BcSmartspaceDataPlugin?,
-        customView: View?
+        customView: View?,
     ): View? {
         execution.assertIsMainThread()
 
@@ -163,12 +165,13 @@
     private fun buildView(
         parent: ViewGroup,
         smartspaceDataPlugin: BcSmartspaceDataPlugin?,
-        customView: View?
+        customView: View?,
     ): View? {
         return if (smartspaceDataPlugin != null) {
-            val view = smartspaceViewComponentFactory.create(parent, smartspaceDataPlugin,
-                stateChangeListener, customView)
-                .getView()
+            val view =
+                smartspaceViewComponentFactory
+                    .create(parent, smartspaceDataPlugin, stateChangeListener, customView)
+                    .getView()
             if (view !is View) {
                 return null
             }
@@ -179,12 +182,17 @@
     }
 
     private fun hasActiveSessionListeners(): Boolean {
-        return smartspaceViews.isNotEmpty() || listeners.isNotEmpty() ||
+        return smartspaceViews.isNotEmpty() ||
+            listeners.isNotEmpty() ||
             unfilteredListeners.isNotEmpty()
     }
 
     private fun connectSession() {
-        if (smartspaceManager == null) {
+        if (userSmartspaceManager == null) {
+            userSmartspaceManager =
+                userTracker.userContext.getSystemService(SmartspaceManager::class.java)
+        }
+        if (userSmartspaceManager == null) {
             return
         }
         if (plugin == null && weatherPlugin == null) {
@@ -198,25 +206,21 @@
             return
         }
 
-        val newSession = smartspaceManager.createSmartspaceSession(
-            SmartspaceConfig.Builder(context, UI_SURFACE_DREAM).build()
-        )
+        val newSession =
+            userSmartspaceManager?.createSmartspaceSession(
+                SmartspaceConfig.Builder(userTracker.userContext, UI_SURFACE_DREAM).build()
+            )
         Log.d(TAG, "Starting smartspace session for dream")
-        newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener)
+        newSession?.addOnTargetsAvailableListener(uiExecutor, sessionListener)
         this.session = newSession
 
         weatherPlugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
-        plugin?.registerSmartspaceEventNotifier {
-                e ->
-            session?.notifySmartspaceEvent(e)
-        }
+        plugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
 
         reloadSmartspace()
     }
 
-    /**
-     * Disconnects the smartspace view from the smartspace service and cleans up any resources.
-     */
+    /** Disconnects the smartspace view from the smartspace service and cleans up any resources. */
     private fun disconnect() {
         if (hasActiveSessionListeners()) return
 
@@ -259,7 +263,7 @@
 
     private fun addAndRegisterListener(
         listener: SmartspaceTargetListener,
-        smartspaceDataPlugin: BcSmartspaceDataPlugin?
+        smartspaceDataPlugin: BcSmartspaceDataPlugin?,
     ) {
         execution.assertIsMainThread()
         smartspaceDataPlugin?.registerListener(listener)
@@ -270,7 +274,7 @@
 
     private fun removeAndUnregisterListener(
         listener: SmartspaceTargetListener,
-        smartspaceDataPlugin: BcSmartspaceDataPlugin?
+        smartspaceDataPlugin: BcSmartspaceDataPlugin?,
     ) {
         execution.assertIsMainThread()
         smartspaceDataPlugin?.unregisterListener(listener)
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
index 3492365..b2fcc43 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
@@ -45,7 +45,7 @@
 
     /** See [registerCriticalDumpable]. */
     fun registerCriticalDumpable(module: Dumpable) {
-        registerCriticalDumpable(module::class.java.canonicalName, module)
+        registerCriticalDumpable(module::class.java.name, module)
     }
 
     /**
@@ -62,7 +62,7 @@
 
     /** See [registerNormalDumpable]. */
     fun registerNormalDumpable(module: Dumpable) {
-        registerNormalDumpable(module::class.java.canonicalName, module)
+        registerNormalDumpable(module::class.java.name, module)
     }
 
     /**
@@ -104,13 +104,10 @@
         dumpables[name] = DumpableEntry(module, name, priority)
     }
 
-    /**
-     * Same as the above override, but automatically uses the canonical class name as the dumpable
-     * name.
-     */
+    /** Same as the above override, but automatically uses the class name as the dumpable name. */
     @Synchronized
     fun registerDumpable(module: Dumpable) {
-        registerDumpable(module::class.java.canonicalName, module)
+        registerDumpable(module::class.java.name, module)
     }
 
     /** Unregisters a previously-registered dumpable. */
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java
index 303f916..911331b 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java
@@ -42,7 +42,7 @@
 
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.settings.UserContextProvider;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.util.settings.GlobalSettings;
 
 import java.io.PrintWriter;
@@ -51,6 +51,7 @@
 import java.util.Objects;
 import java.util.TreeMap;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
@@ -74,7 +75,6 @@
     static final String TAG = "SysUIFlags";
 
     private final FlagManager mFlagManager;
-    private final Context mContext;
     private final GlobalSettings mGlobalSettings;
     private final Resources mResources;
     private final SystemPropertiesHelper mSystemProperties;
@@ -84,6 +84,8 @@
     private final Map<String, String> mStringFlagCache = new ConcurrentHashMap<>();
     private final Map<String, Integer> mIntFlagCache = new ConcurrentHashMap<>();
     private final Restarter mRestarter;
+    private final UserTracker mUserTracker;
+    private final Executor mMainExecutor;
 
     private final ServerFlagReader.ChangeListener mOnPropertiesChanged =
             new ServerFlagReader.ChangeListener() {
@@ -118,6 +120,18 @@
                 }
             };
 
+    private final UserTracker.Callback mUserChangedCallback =
+            new UserTracker.Callback() {
+                @Override
+                public void onUserChanged(int newUser, @NonNull Context userContext) {
+                    mContext.unregisterReceiver(mReceiver);
+                    mContext = userContext;
+                    registerReceiver();
+                }
+            };
+
+    private Context mContext;
+
     @Inject
     public FeatureFlagsClassicDebug(
             FlagManager flagManager,
@@ -128,10 +142,11 @@
             ServerFlagReader serverFlagReader,
             @Named(ALL_FLAGS) Map<String, Flag<?>> allFlags,
             Restarter restarter,
-            UserContextProvider userContextProvider) {
+            UserTracker userTracker,
+            @Main Executor executor) {
         mFlagManager = flagManager;
         if (classicFlagsMultiUser()) {
-            mContext = userContextProvider.createCurrentUserContext(context);
+            mContext = userTracker.createCurrentUserContext(context);
         } else {
             mContext = context;
         }
@@ -141,19 +156,28 @@
         mServerFlagReader = serverFlagReader;
         mAllFlags = allFlags;
         mRestarter = restarter;
+        mUserTracker = userTracker;
+        mMainExecutor = executor;
     }
 
     /** Call after construction to setup listeners. */
     void init() {
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(ACTION_SET_FLAG);
-        filter.addAction(ACTION_GET_FLAGS);
         mFlagManager.setOnSettingsChangedAction(
                 suppressRestart -> restartSystemUI(suppressRestart, "Settings changed"));
         mFlagManager.setClearCacheAction(this::removeFromCache);
+        registerReceiver();
+        if (classicFlagsMultiUser()) {
+            mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
+        }
+        mServerFlagReader.listenForChanges(mAllFlags.values(), mOnPropertiesChanged);
+    }
+
+    void registerReceiver() {
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(ACTION_SET_FLAG);
+        filter.addAction(ACTION_GET_FLAGS);
         mContext.registerReceiver(mReceiver, filter, null, null,
                 Context.RECEIVER_EXPORTED_UNAUDITED);
-        mServerFlagReader.listenForChanges(mAllFlags.values(), mOnPropertiesChanged);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt
index 89cdd25..585f7ed 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt
@@ -33,13 +33,13 @@
 import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.META
 import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.SHIFT
 import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository
+import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
-import javax.inject.Inject
 
 interface StickyKeysRepository {
     val stickyKeys: Flow<LinkedHashMap<ModifierKey, Locked>>
@@ -71,7 +71,7 @@
 
     override val settingEnabled: Flow<Boolean> =
         secureSettingsRepository
-            .boolSettingForActiveUser(SETTING_KEY, defaultValue = false)
+            .boolSetting(SETTING_KEY, defaultValue = false)
             .onEach { stickyKeysLogger.logNewSettingValue(it) }
             .flowOn(backgroundDispatcher)
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
index 032af94..2914cb9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
@@ -44,7 +44,7 @@
     private val keyguardStateController: KeyguardStateController,
     private val keyguardSurfaceBehindAnimator: KeyguardSurfaceBehindParamsApplier,
     private val keyguardDismissTransitionInteractor: KeyguardDismissTransitionInteractor,
-    private val keyguardTransitions: KeyguardTransitions
+    private val keyguardTransitions: KeyguardTransitions,
 ) {
 
     /**
@@ -108,27 +108,28 @@
      * Manager to effect the change.
      */
     fun setSurfaceBehindVisibility(visible: Boolean) {
-        if (isKeyguardGoingAway == visible) {
-            Log.d(TAG, "WmLockscreenVisibilityManager#setVisibility -> already visible=$visible")
+        if (isKeyguardGoingAway && visible) {
+            Log.d(TAG, "#setSurfaceBehindVisibility: already visible, ignoring")
             return
         }
 
         // The surface behind is always visible if the lockscreen is not showing, so we're already
         // visible.
         if (visible && isLockscreenShowing != true) {
-            Log.d(TAG, "#setVisibility -> already visible since the lockscreen isn't showing")
+            Log.d(TAG, "#setSurfaceBehindVisibility: ignoring since the lockscreen isn't showing")
             return
         }
 
-
-
         if (visible) {
             if (enableNewKeyguardShellTransitions) {
-                keyguardTransitions.startKeyguardTransition(false /* keyguardShowing */, false /* aodShowing */)
+                keyguardTransitions.startKeyguardTransition(
+                    false /* keyguardShowing */,
+                    false, /* aodShowing */
+                )
                 isKeyguardGoingAway = true
                 return
             }
-            // Make the surface visible behind the keyguard by calling keyguardGoingAway. The
+            // Make the surface behind the keyguard visible by calling keyguardGoingAway. The
             // lockscreen is still showing as well, allowing us to animate unlocked.
             Log.d(TAG, "ActivityTaskManagerService#keyguardGoingAway()")
             activityTaskManagerService.keyguardGoingAway(0)
@@ -153,7 +154,7 @@
         apps: Array<RemoteAnimationTarget>,
         wallpapers: Array<RemoteAnimationTarget>,
         nonApps: Array<RemoteAnimationTarget>,
-        finishedCallback: IRemoteAnimationFinishedCallback
+        finishedCallback: IRemoteAnimationFinishedCallback,
     ) {
         // Ensure that we've started a dismiss keyguard transition. WindowManager can start the
         // going away animation on its own, if an activity launches and then requests dismissing the
@@ -203,27 +204,25 @@
      */
     private fun setWmLockscreenState(
         lockscreenShowing: Boolean? = this.isLockscreenShowing,
-        aodVisible: Boolean = this.isAodVisible
+        aodVisible: Boolean = this.isAodVisible,
     ) {
-        Log.d(
-            TAG,
-            "#setWmLockscreenState(" +
-                "isLockscreenShowing=$lockscreenShowing, " +
-                "aodVisible=$aodVisible)."
-        )
-
         if (lockscreenShowing == null) {
             Log.d(
                 TAG,
                 "isAodVisible=$aodVisible, but lockscreenShowing=null. Waiting for" +
                     "non-null lockscreenShowing before calling ATMS#setLockScreenShown, which" +
-                    "will happen once KeyguardTransitionBootInteractor starts the boot transition."
+                    "will happen once KeyguardTransitionBootInteractor starts the boot transition.",
             )
             this.isAodVisible = aodVisible
             return
         }
 
         if (this.isLockscreenShowing == lockscreenShowing && this.isAodVisible == aodVisible) {
+            Log.d(
+                TAG,
+                "#setWmLockscreenState: lockscreenShowing=$lockscreenShowing and " +
+                    "isAodVisible=$aodVisible were both unchanged, not forwarding to ATMS.",
+            )
             return
         }
 
@@ -231,7 +230,7 @@
             TAG,
             "ATMS#setLockScreenShown(" +
                 "isLockscreenShowing=$lockscreenShowing, " +
-                "aodVisible=$aodVisible)."
+                "aodVisible=$aodVisible).",
         )
         if (enableNewKeyguardShellTransitions) {
             keyguardTransitions.startKeyguardTransition(lockscreenShowing, aodVisible)
@@ -247,7 +246,7 @@
             Log.d(
                 TAG,
                 "#endKeyguardGoingAwayAnimation() called when isKeyguardGoingAway=false. " +
-                    "Short-circuiting."
+                    "Short-circuiting.",
             )
             return
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index 6ac0a3f..021cce6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -20,6 +20,7 @@
 import android.annotation.SuppressLint
 import android.app.DreamManager
 import com.android.app.animation.Interpolators
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.Flags.communalSceneKtfRefactor
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
@@ -41,7 +42,6 @@
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.debounce
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 @SysUISingleton
 class FromDozingTransitionInteractor
@@ -135,11 +135,22 @@
 
                     if (!deviceEntryInteractor.isLockscreenEnabled()) {
                         if (!SceneContainerFlag.isEnabled) {
-                            startTransitionTo(KeyguardState.GONE)
+                            startTransitionTo(
+                                KeyguardState.GONE,
+                                ownerReason = "lockscreen not enabled",
+                            )
                         }
                     } else if (canDismissLockscreen() || isKeyguardGoingAway) {
                         if (!SceneContainerFlag.isEnabled) {
-                            startTransitionTo(KeyguardState.GONE)
+                            startTransitionTo(
+                                KeyguardState.GONE,
+                                ownerReason =
+                                    if (canDismissLockscreen()) {
+                                        "canDismissLockscreen()"
+                                    } else {
+                                        "isKeyguardGoingAway"
+                                    },
+                            )
                         }
                     } else if (primaryBouncerShowing) {
                         if (!SceneContainerFlag.isEnabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
index 0dae17c..cd62d5f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
@@ -16,9 +16,11 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.keyguard.logging.KeyguardLogger
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
 import com.android.systemui.log.core.LogLevel.VERBOSE
@@ -29,7 +31,6 @@
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.debounce
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 private val TAG = KeyguardTransitionAuditLogger::class.simpleName!!
 
@@ -48,6 +49,7 @@
     private val aodBurnInViewModel: AodBurnInViewModel,
     private val shadeInteractor: ShadeInteractor,
     private val keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
+    private val deviceEntryInteractor: DeviceEntryInteractor,
 ) {
 
     fun start() {
@@ -84,6 +86,18 @@
         }
 
         scope.launch {
+            deviceEntryInteractor.isUnlocked.collect {
+                logger.log(TAG, VERBOSE, "DeviceEntry isUnlocked", it)
+            }
+        }
+
+        scope.launch {
+            deviceEntryInteractor.isLockscreenEnabled.collect {
+                logger.log(TAG, VERBOSE, "DeviceEntry isLockscreenEnabled", it)
+            }
+        }
+
+        scope.launch {
             keyguardInteractor.primaryBouncerShowing.collect {
                 logger.log(TAG, VERBOSE, "Primary bouncer showing", it)
             }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
index abd7f90..7d4d377 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -18,6 +18,7 @@
 
 import android.animation.ValueAnimator
 import android.util.Log
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.keyguard.KeyguardWmStateRefactor
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -33,7 +34,6 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /**
  * Each TransitionInteractor is responsible for determining under which conditions to notify
@@ -201,9 +201,18 @@
             scope.launch {
                 keyguardInteractor.onCameraLaunchDetected.filterRelevantKeyguardState().collect {
                     if (!maybeHandleInsecurePowerGesture()) {
+                        val lastStep = transitionInteractor.transitionState.value
+                        val modeOnCanceled =
+                            if (lastStep.to == KeyguardState.AOD) {
+                                // Enabled smooth transition when double-tap camera cancels
+                                // transition to AOD
+                                TransitionModeOnCanceled.REVERSE
+                            } else {
+                                TransitionModeOnCanceled.RESET
+                            }
                         startTransitionTo(
                             toState = KeyguardState.OCCLUDED,
-                            modeOnCanceled = TransitionModeOnCanceled.RESET,
+                            modeOnCanceled = modeOnCanceled,
                             ownerReason = "keyguardInteractor.onCameraLaunchDetected",
                         )
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
index a1f6067..f473a82 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
@@ -18,7 +18,8 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
-import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.ObservableTransitionState.Idle
+import com.android.compose.animation.scene.ObservableTransitionState.Transition
 import com.android.systemui.Flags.transitionRaceCondition
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
@@ -30,6 +31,7 @@
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Overlays
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
 import com.android.systemui.util.kotlin.Utils.Companion.toTriple
@@ -110,11 +112,8 @@
             }
             .distinctUntilChanged()
 
-    private val isDeviceEntered: Flow<Boolean> by lazy {
-        deviceEntryInteractor.get().isDeviceEntered
-    }
-
-    private val isDeviceNotEntered: Flow<Boolean> by lazy { isDeviceEntered.map { !it } }
+    private val isDeviceEntered by lazy { deviceEntryInteractor.get().isDeviceEntered }
+    private val isDeviceNotEntered by lazy { isDeviceEntered.map { !it } }
 
     /**
      * Surface visibility, which is either determined by the default visibility when not
@@ -124,32 +123,17 @@
     @OptIn(ExperimentalCoroutinesApi::class)
     val surfaceBehindVisibility: Flow<Boolean> =
         if (SceneContainerFlag.isEnabled) {
-                sceneInteractor.get().transitionState.flatMapLatestConflated { transitionState ->
-                    when (transitionState) {
-                        is ObservableTransitionState.Transition ->
-                            when {
-                                transitionState.fromContent == Scenes.Lockscreen &&
-                                    transitionState.toContent == Scenes.Gone ->
-                                    sceneInteractor
-                                        .get()
-                                        .isTransitionUserInputOngoing
-                                        .flatMapLatestConflated { isUserInputOngoing ->
-                                            if (isUserInputOngoing) {
-                                                isDeviceEntered
-                                            } else {
-                                                flowOf(true)
-                                            }
-                                        }
-                                transitionState.fromContent == Scenes.Bouncer &&
-                                    transitionState.toContent == Scenes.Gone ->
-                                    transitionState.progress.map { progress ->
-                                        progress >
-                                            FromPrimaryBouncerTransitionInteractor
-                                                .TO_GONE_SURFACE_BEHIND_VISIBLE_THRESHOLD
-                                    }
-                                else -> isDeviceEntered
+                sceneInteractor.get().transitionState.flatMapLatestConflated { state ->
+                    when {
+                        state.isTransitioning(from = Scenes.Lockscreen, to = Scenes.Gone) ->
+                            isDeviceEntered
+                        state.isTransitioning(from = Scenes.Bouncer, to = Scenes.Gone) ->
+                            (state as Transition).progress.map { progress ->
+                                progress >
+                                    FromPrimaryBouncerTransitionInteractor
+                                        .TO_GONE_SURFACE_BEHIND_VISIBLE_THRESHOLD
                             }
-                        is ObservableTransitionState.Idle -> isDeviceEntered
+                        else -> lockscreenVisibilityWithScenes.map { !it }
                     }
                 }
             } else {
@@ -219,6 +203,123 @@
         }
 
     /**
+     * Scenes that are part of the keyguard and are shown when the device is locked or when the
+     * keyguard still needs to be dismissed.
+     */
+    private val keyguardScenes = setOf(Scenes.Lockscreen, Scenes.Bouncer, Scenes.Communal)
+
+    /**
+     * Scenes that don't belong in the keyguard family and cannot show when the device is locked or
+     * when the keyguard still needs to be dismissed.
+     */
+    private val nonKeyguardScenes = setOf(Scenes.Gone)
+
+    /**
+     * Scenes that can show regardless of device lock or keyguard dismissal states. Other sources of
+     * state need to be consulted to know whether the device has been entered or not.
+     */
+    private val keyguardAgnosticScenes =
+        setOf(
+            Scenes.Shade,
+            Scenes.QuickSettings,
+            Overlays.NotificationsShade,
+            Overlays.QuickSettingsShade,
+        )
+
+    private val lockscreenVisibilityWithScenes =
+        combine(
+                sceneInteractor.get().transitionState.flatMapLatestConflated {
+                    when (it) {
+                        is Idle -> {
+                            when (it.currentScene) {
+                                in keyguardScenes -> flowOf(true)
+                                in nonKeyguardScenes -> flowOf(false)
+                                in keyguardAgnosticScenes -> isDeviceNotEntered
+                                else ->
+                                    throw IllegalStateException("Unknown scene: ${it.currentScene}")
+                            }
+                        }
+                        is Transition -> {
+                            when {
+                                it.isTransitioningSets(from = keyguardScenes) -> flowOf(true)
+                                it.isTransitioningSets(from = nonKeyguardScenes) -> flowOf(false)
+                                it.isTransitioningSets(from = keyguardAgnosticScenes) ->
+                                    isDeviceNotEntered
+                                else ->
+                                    throw IllegalStateException("Unknown scene: ${it.fromContent}")
+                            }
+                        }
+                    }
+                },
+                wakeToGoneInteractor.canWakeDirectlyToGone,
+                ::Pair,
+            )
+            .map { (lockscreenVisibilityByTransitionState, canWakeDirectlyToGone) ->
+                lockscreenVisibilityByTransitionState && !canWakeDirectlyToGone
+            }
+
+    private val lockscreenVisibilityLegacy =
+        combine(
+                transitionInteractor.currentKeyguardState,
+                wakeToGoneInteractor.canWakeDirectlyToGone,
+                ::Pair,
+            )
+            .sample(transitionInteractor.startedStepWithPrecedingStep, ::toTriple)
+            .map { (currentState, canWakeDirectlyToGone, startedWithPrev) ->
+                val startedFromStep = startedWithPrev.previousValue
+                val startedStep = startedWithPrev.newValue
+                val returningToGoneAfterCancellation =
+                    startedStep.to == KeyguardState.GONE &&
+                        startedFromStep.transitionState == TransitionState.CANCELED &&
+                        startedFromStep.from == KeyguardState.GONE
+
+                val transitionInfo =
+                    if (transitionRaceCondition()) {
+                        transitionRepository.currentTransitionInfo
+                    } else {
+                        transitionRepository.currentTransitionInfoInternal.value
+                    }
+                val wakingDirectlyToGone =
+                    deviceIsAsleepInState(transitionInfo.from) &&
+                        transitionInfo.to == KeyguardState.GONE
+
+                if (returningToGoneAfterCancellation || wakingDirectlyToGone) {
+                    // GONE -> AOD/DOZING (cancel) -> GONE is the camera launch transition,
+                    // which means we never want to show the lockscreen throughout the
+                    // transition. Same for waking directly to gone, due to the lockscreen being
+                    // disabled or because the device was woken back up before the lock timeout
+                    // duration elapsed.
+                    false
+                } else if (canWakeDirectlyToGone) {
+                    // Never show the lockscreen if we can wake directly to GONE. This means
+                    // that the lock timeout has not yet elapsed, or the keyguard is disabled.
+                    // In either case, we don't show the activity lock screen until one of those
+                    // conditions changes.
+                    false
+                } else if (
+                    currentState == KeyguardState.DREAMING &&
+                        deviceEntryInteractor.get().isUnlocked.value
+                ) {
+                    // Dreams dismiss keyguard and return to GONE if they can.
+                    false
+                } else if (
+                    startedWithPrev.newValue.from == KeyguardState.OCCLUDED &&
+                        startedWithPrev.newValue.to == KeyguardState.GONE
+                ) {
+                    // OCCLUDED -> GONE directly, without transiting a *_BOUNCER state, occurs
+                    // when an app uses intent flags to launch over an insecure keyguard without
+                    // dismissing it, and then manually requests keyguard dismissal while
+                    // OCCLUDED. This transition is not user-visible; the device unlocks in the
+                    // background and the app remains on top, while we're now GONE. In this case
+                    // we should simply tell WM that the lockscreen is no longer visible, and
+                    // *not* play the going away animation or related animations.
+                    false
+                } else {
+                    currentState != KeyguardState.GONE
+                }
+            }
+
+    /**
      * Whether the lockscreen is visible, from the Window Manager (WM) perspective.
      *
      * Note: This may briefly be true even if the lockscreen UI has animated out (alpha = 0f), as we
@@ -227,69 +328,11 @@
      */
     val lockscreenVisibility: Flow<Boolean> =
         if (SceneContainerFlag.isEnabled) {
-            isDeviceNotEntered
-        } else {
-            combine(
-                    transitionInteractor.currentKeyguardState,
-                    wakeToGoneInteractor.canWakeDirectlyToGone,
-                    ::Pair,
-                )
-                .sample(transitionInteractor.startedStepWithPrecedingStep, ::toTriple)
-                .map { (currentState, canWakeDirectlyToGone, startedWithPrev) ->
-                    val startedFromStep = startedWithPrev.previousValue
-                    val startedStep = startedWithPrev.newValue
-                    val returningToGoneAfterCancellation =
-                        startedStep.to == KeyguardState.GONE &&
-                            startedFromStep.transitionState == TransitionState.CANCELED &&
-                            startedFromStep.from == KeyguardState.GONE
-
-                    val transitionInfo =
-                        if (transitionRaceCondition()) {
-                            transitionRepository.currentTransitionInfo
-                        } else {
-                            transitionRepository.currentTransitionInfoInternal.value
-                        }
-                    val wakingDirectlyToGone =
-                        deviceIsAsleepInState(transitionInfo.from) &&
-                            transitionInfo.to == KeyguardState.GONE
-
-                    if (returningToGoneAfterCancellation || wakingDirectlyToGone) {
-                        // GONE -> AOD/DOZING (cancel) -> GONE is the camera launch transition,
-                        // which means we never want to show the lockscreen throughout the
-                        // transition. Same for waking directly to gone, due to the lockscreen being
-                        // disabled or because the device was woken back up before the lock timeout
-                        // duration elapsed.
-                        false
-                    } else if (canWakeDirectlyToGone) {
-                        // Never show the lockscreen if we can wake directly to GONE. This means
-                        // that the lock timeout has not yet elapsed, or the keyguard is disabled.
-                        // In either case, we don't show the activity lock screen until one of those
-                        // conditions changes.
-                        false
-                    } else if (
-                        currentState == KeyguardState.DREAMING &&
-                            deviceEntryInteractor.get().isUnlocked.value
-                    ) {
-                        // Dreams dismiss keyguard and return to GONE if they can.
-                        false
-                    } else if (
-                        startedWithPrev.newValue.from == KeyguardState.OCCLUDED &&
-                            startedWithPrev.newValue.to == KeyguardState.GONE
-                    ) {
-                        // OCCLUDED -> GONE directly, without transiting a *_BOUNCER state, occurs
-                        // when an app uses intent flags to launch over an insecure keyguard without
-                        // dismissing it, and then manually requests keyguard dismissal while
-                        // OCCLUDED. This transition is not user-visible; the device unlocks in the
-                        // background and the app remains on top, while we're now GONE. In this case
-                        // we should simply tell WM that the lockscreen is no longer visible, and
-                        // *not* play the going away animation or related animations.
-                        false
-                    } else {
-                        currentState != KeyguardState.GONE
-                    }
-                }
-                .distinctUntilChanged()
-        }
+                lockscreenVisibilityWithScenes
+            } else {
+                lockscreenVisibilityLegacy
+            }
+            .distinctUntilChanged()
 
     /**
      * Whether always-on-display (AOD) is visible when the lockscreen is visible, from window
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
index be4bc23..6985615 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
@@ -182,9 +182,11 @@
             fgIconView.repeatWhenAttached {
                 repeatOnLifecycle(Lifecycle.State.STARTED) {
                     // Start with an empty state
+                    Log.d(TAG, "Initializing device entry fgIconView")
                     fgIconView.setImageState(StateSet.NOTHING, /* merge */ false)
                     launch("$TAG#fpIconView.viewModel") {
                         fgViewModel.viewModel.collect { viewModel ->
+                            Log.d(TAG, "Updating device entry icon image state $viewModel")
                             fgIconView.setImageState(
                                 view.getIconState(viewModel.type, viewModel.useAodVariant),
                                 /* merge */ false,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
index c78e0c9..478372d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.BurnInModel
 import com.android.systemui.keyguard.shared.model.ClockSize
+import com.android.systemui.keyguard.shared.model.Edge
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.ui.StateToValue
 import com.android.systemui.res.R
@@ -42,8 +43,10 @@
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.stateIn
 
@@ -164,9 +167,17 @@
 
     private fun burnIn(params: BurnInParameters): Flow<BurnInModel> {
         return combine(
-            keyguardTransitionInteractor.transitionValue(KeyguardState.AOD).map {
-                Interpolators.FAST_OUT_SLOW_IN.getInterpolation(it)
-            },
+            merge(
+                    keyguardTransitionInteractor.transition(Edge.create(to = KeyguardState.AOD)),
+                    keyguardTransitionInteractor
+                        .transition(Edge.create(from = KeyguardState.AOD))
+                        .map { it.copy(value = 1f - it.value) },
+                    keyguardTransitionInteractor
+                        .transition(Edge.create(to = KeyguardState.LOCKSCREEN))
+                        .filter { it.from != KeyguardState.AOD }
+                        .map { it.copy(value = 0f) },
+                )
+                .map { Interpolators.FAST_OUT_SLOW_IN.getInterpolation(it.value) },
             burnInInteractor.burnIn(
                 xDimenResourceId = R.dimen.burn_in_prevention_offset_x,
                 yDimenResourceId = R.dimen.burn_in_prevention_offset_y,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
index 67b009e..7f3ef61 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
@@ -54,9 +54,7 @@
                 duration = TO_LOCKSCREEN_DURATION,
                 edge = Edge.create(from = Scenes.Communal, to = LOCKSCREEN),
             )
-            .setupWithoutSceneContainer(
-                edge = Edge.create(from = GLANCEABLE_HUB, to = LOCKSCREEN),
-            )
+            .setupWithoutSceneContainer(edge = Edge.create(from = GLANCEABLE_HUB, to = LOCKSCREEN))
 
     val keyguardAlpha: Flow<Float> =
         transitionAnimation.sharedFlow(
@@ -75,7 +73,7 @@
         configurationInteractor
             .directionalDimensionPixelSize(
                 LayoutDirection.LTR,
-                R.dimen.hub_to_lockscreen_transition_lockscreen_translation_x
+                R.dimen.hub_to_lockscreen_transition_lockscreen_translation_x,
             )
             .flatMapLatest { translatePx: Int ->
                 transitionAnimation.sharedFlowWithState(
@@ -87,7 +85,7 @@
                     // is cancelled.
                     onFinish = { 0f },
                     onCancel = { 0f },
-                    name = "GLANCEABLE_HUB->LOCKSCREEN: keyguardTranslationX"
+                    name = "GLANCEABLE_HUB->LOCKSCREEN: keyguardTranslationX",
                 )
             }
 
@@ -95,6 +93,8 @@
 
     val shortcutsAlpha: Flow<Float> = keyguardAlpha
 
+    val statusBarAlpha: Flow<Float> = keyguardAlpha
+
     val notificationTranslationX: Flow<Float> =
         keyguardTranslationX.map { it.value }.filterNotNull()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
index 378374e..dd8ff8c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
@@ -54,9 +54,7 @@
                 duration = FromLockscreenTransitionInteractor.TO_GLANCEABLE_HUB_DURATION,
                 edge = Edge.create(from = LOCKSCREEN, to = Scenes.Communal),
             )
-            .setupWithoutSceneContainer(
-                edge = Edge.create(from = LOCKSCREEN, to = GLANCEABLE_HUB),
-            )
+            .setupWithoutSceneContainer(edge = Edge.create(from = LOCKSCREEN, to = GLANCEABLE_HUB))
 
     val keyguardAlpha: Flow<Float> =
         transitionAnimation.sharedFlow(
@@ -74,7 +72,7 @@
         configurationInteractor
             .directionalDimensionPixelSize(
                 LayoutDirection.LTR,
-                R.dimen.lockscreen_to_hub_transition_lockscreen_translation_x
+                R.dimen.lockscreen_to_hub_transition_lockscreen_translation_x,
             )
             .flatMapLatest { translatePx: Int ->
                 transitionAnimation.sharedFlowWithState(
@@ -86,7 +84,7 @@
                     onFinish = { 0f },
                     onCancel = { 0f },
                     interpolator = EMPHASIZED,
-                    name = "LOCKSCREEN->GLANCEABLE_HUB: keyguardTranslationX"
+                    name = "LOCKSCREEN->GLANCEABLE_HUB: keyguardTranslationX",
                 )
             }
 
@@ -94,6 +92,8 @@
 
     val shortcutsAlpha: Flow<Float> = keyguardAlpha
 
+    val statusBarAlpha: Flow<Float> = keyguardAlpha
+
     val notificationTranslationX: Flow<Float> =
         keyguardTranslationX.map { it.value }.filterNotNull()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt
new file mode 100644
index 0000000..a33685b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.domain.pipeline
+
+import android.content.Context
+import android.graphics.drawable.Animatable
+import android.graphics.drawable.Drawable
+import android.media.session.MediaController
+import android.media.session.MediaSession
+import android.os.Handler
+import android.os.Looper
+import android.util.Log
+import androidx.annotation.WorkerThread
+import androidx.media.utils.MediaConstants
+import androidx.media3.common.Player
+import androidx.media3.session.CommandButton
+import androidx.media3.session.SessionCommand
+import androidx.media3.session.SessionToken
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.graphics.ImageLoader
+import com.android.systemui.media.controls.shared.MediaControlDrawables
+import com.android.systemui.media.controls.shared.MediaLogger
+import com.android.systemui.media.controls.shared.model.MediaAction
+import com.android.systemui.media.controls.shared.model.MediaButton
+import com.android.systemui.media.controls.util.MediaControllerFactory
+import com.android.systemui.media.controls.util.SessionTokenFactory
+import com.android.systemui.res.R
+import com.android.systemui.util.Assert
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+private const val TAG = "Media3ActionFactory"
+
+@SysUISingleton
+class Media3ActionFactory
+@Inject
+constructor(
+    @Application val context: Context,
+    private val imageLoader: ImageLoader,
+    private val controllerFactory: MediaControllerFactory,
+    private val tokenFactory: SessionTokenFactory,
+    private val logger: MediaLogger,
+    @Background private val looper: Looper,
+    @Background private val handler: Handler,
+    @Background private val bgScope: CoroutineScope,
+) {
+
+    /**
+     * Generates action button info for this media session based on the Media3 session info
+     *
+     * @param packageName Package name for the media app
+     * @param controller The framework [MediaController] for the session
+     * @return The media action buttons, or null if the session token is null
+     */
+    suspend fun createActionsFromSession(
+        packageName: String,
+        sessionToken: MediaSession.Token,
+    ): MediaButton? {
+        // Get the Media3 controller using the legacy token
+        val token = tokenFactory.createTokenFromLegacy(sessionToken)
+        val m3controller = controllerFactory.create(token, looper)
+
+        // Build button info
+        val buttons = suspendCancellableCoroutine { continuation ->
+            // Media3Controller methods must always be called from a specific looper
+            handler.post {
+                val result = getMedia3Actions(packageName, m3controller, token)
+                m3controller.release()
+                continuation.resumeWith(Result.success(result))
+            }
+        }
+        return buttons
+    }
+
+    /** This method must be called on the Media3 looper! */
+    @WorkerThread
+    private fun getMedia3Actions(
+        packageName: String,
+        m3controller: androidx.media3.session.MediaController,
+        token: SessionToken,
+    ): MediaButton? {
+        Assert.isNotMainThread()
+
+        // First, get standard actions
+        val playOrPause =
+            if (m3controller.playbackState == Player.STATE_BUFFERING) {
+                // Spinner needs to be animating to render anything. Start it here.
+                val drawable =
+                    context.getDrawable(com.android.internal.R.drawable.progress_small_material)
+                (drawable as Animatable).start()
+                MediaAction(
+                    drawable,
+                    null, // no action to perform when clicked
+                    context.getString(R.string.controls_media_button_connecting),
+                    context.getDrawable(R.drawable.ic_media_connecting_container),
+                    // Specify a rebind id to prevent the spinner from restarting on later binds.
+                    com.android.internal.R.drawable.progress_small_material,
+                )
+            } else {
+                getStandardAction(m3controller, token, Player.COMMAND_PLAY_PAUSE)
+            }
+
+        val prevButton =
+            getStandardAction(
+                m3controller,
+                token,
+                Player.COMMAND_SEEK_TO_PREVIOUS,
+                Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM,
+            )
+        val nextButton =
+            getStandardAction(
+                m3controller,
+                token,
+                Player.COMMAND_SEEK_TO_NEXT,
+                Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM,
+            )
+
+        // Then, get custom actions
+        var customActions =
+            m3controller.customLayout
+                .asSequence()
+                .filter {
+                    it.isEnabled &&
+                        it.sessionCommand?.commandCode == SessionCommand.COMMAND_CODE_CUSTOM &&
+                        m3controller.isSessionCommandAvailable(it.sessionCommand!!)
+                }
+                .map { getCustomAction(packageName, token, it) }
+                .iterator()
+        fun nextCustomAction() = if (customActions.hasNext()) customActions.next() else null
+
+        // Finally, assign the remaining button slots: play/pause A B C D
+        // A = previous, else custom action (if not reserved)
+        // B = next, else custom action (if not reserved)
+        // C and D are always custom actions
+        val reservePrev =
+            m3controller.sessionExtras.getBoolean(
+                MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV,
+                false,
+            )
+        val reserveNext =
+            m3controller.sessionExtras.getBoolean(
+                MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT,
+                false,
+            )
+
+        val prevOrCustom =
+            prevButton
+                ?: if (reservePrev) {
+                    null
+                } else {
+                    nextCustomAction()
+                }
+
+        val nextOrCustom =
+            nextButton
+                ?: if (reserveNext) {
+                    null
+                } else {
+                    nextCustomAction()
+                }
+
+        return MediaButton(
+            playOrPause = playOrPause,
+            nextOrCustom = nextOrCustom,
+            prevOrCustom = prevOrCustom,
+            custom0 = nextCustomAction(),
+            custom1 = nextCustomAction(),
+            reserveNext = reserveNext,
+            reservePrev = reservePrev,
+        )
+    }
+
+    /**
+     * Create a [MediaAction] for a given command, if supported
+     *
+     * @param controller Media3 controller for the session
+     * @param commands Commands to check, in priority order
+     * @return A [MediaAction] representing the first supported command, or null if not supported
+     */
+    private fun getStandardAction(
+        controller: androidx.media3.session.MediaController,
+        token: SessionToken,
+        vararg commands: @Player.Command Int,
+    ): MediaAction? {
+        for (command in commands) {
+            if (!controller.isCommandAvailable(command)) {
+                continue
+            }
+
+            return when (command) {
+                Player.COMMAND_PLAY_PAUSE -> {
+                    if (!controller.isPlaying) {
+                        MediaAction(
+                            context.getDrawable(R.drawable.ic_media_play),
+                            { executeAction(token, Player.COMMAND_PLAY_PAUSE) },
+                            context.getString(R.string.controls_media_button_play),
+                            context.getDrawable(R.drawable.ic_media_play_container),
+                        )
+                    } else {
+                        MediaAction(
+                            context.getDrawable(R.drawable.ic_media_pause),
+                            { executeAction(token, Player.COMMAND_PLAY_PAUSE) },
+                            context.getString(R.string.controls_media_button_pause),
+                            context.getDrawable(R.drawable.ic_media_pause_container),
+                        )
+                    }
+                }
+                else -> {
+                    MediaAction(
+                        icon = getIconForAction(command),
+                        action = { executeAction(token, command) },
+                        contentDescription = getDescriptionForAction(command),
+                        background = null,
+                    )
+                }
+            }
+        }
+        return null
+    }
+
+    /** Get a [MediaAction] representing a [CommandButton] */
+    private fun getCustomAction(
+        packageName: String,
+        token: SessionToken,
+        customAction: CommandButton,
+    ): MediaAction {
+        return MediaAction(
+            getIconForAction(customAction, packageName),
+            { executeAction(token, Player.COMMAND_INVALID, customAction) },
+            customAction.displayName,
+            null,
+        )
+    }
+
+    private fun getIconForAction(command: @Player.Command Int): Drawable? {
+        return when (command) {
+            Player.COMMAND_SEEK_TO_PREVIOUS -> MediaControlDrawables.getPrevIcon(context)
+            Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM -> MediaControlDrawables.getPrevIcon(context)
+            Player.COMMAND_SEEK_TO_NEXT -> MediaControlDrawables.getNextIcon(context)
+            Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM -> MediaControlDrawables.getNextIcon(context)
+            else -> {
+                Log.e(TAG, "Unknown icon for $command")
+                null
+            }
+        }
+    }
+
+    private fun getIconForAction(customAction: CommandButton, packageName: String): Drawable? {
+        val size = context.resources.getDimensionPixelSize(R.dimen.min_clickable_item_size)
+        // TODO(b/360196209): check customAction.icon field to use platform icons
+        if (customAction.iconResId != 0) {
+            val packageContext = context.createPackageContext(packageName, 0)
+            val source = ImageLoader.Res(customAction.iconResId, packageContext)
+            return runBlocking { imageLoader.loadDrawable(source, size, size) }
+        }
+
+        if (customAction.iconUri != null) {
+            val source = ImageLoader.Uri(customAction.iconUri!!)
+            return runBlocking { imageLoader.loadDrawable(source, size, size) }
+        }
+        return null
+    }
+
+    private fun getDescriptionForAction(command: @Player.Command Int): String? {
+        return when (command) {
+            Player.COMMAND_SEEK_TO_PREVIOUS ->
+                context.getString(R.string.controls_media_button_prev)
+            Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM ->
+                context.getString(R.string.controls_media_button_prev)
+            Player.COMMAND_SEEK_TO_NEXT -> context.getString(R.string.controls_media_button_next)
+            Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM ->
+                context.getString(R.string.controls_media_button_next)
+            else -> {
+                Log.e(TAG, "Unknown content description for $command")
+                null
+            }
+        }
+    }
+
+    private fun executeAction(
+        token: SessionToken,
+        command: Int,
+        customAction: CommandButton? = null,
+    ) {
+        bgScope.launch {
+            val controller = controllerFactory.create(token, looper)
+            handler.post {
+                when (command) {
+                    Player.COMMAND_PLAY_PAUSE -> {
+                        if (controller.isPlaying) controller.pause() else controller.play()
+                    }
+
+                    Player.COMMAND_SEEK_TO_PREVIOUS -> controller.seekToPrevious()
+                    Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM ->
+                        controller.seekToPreviousMediaItem()
+
+                    Player.COMMAND_SEEK_TO_NEXT -> controller.seekToNext()
+                    Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM -> controller.seekToNextMediaItem()
+                    Player.COMMAND_INVALID -> {
+                        if (
+                            customAction != null &&
+                                customAction!!.sessionCommand != null &&
+                                controller.isSessionCommandAvailable(
+                                    customAction!!.sessionCommand!!
+                                )
+                        ) {
+                            controller.sendCustomCommand(
+                                customAction!!.sessionCommand!!,
+                                customAction!!.extras,
+                            )
+                        } else {
+                            logger.logMedia3UnsupportedCommand("$command, action $customAction")
+                        }
+                    }
+
+                    else -> logger.logMedia3UnsupportedCommand(command.toString())
+                }
+                controller.release()
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt
index 591a9cc..a176e0c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt
@@ -84,6 +84,7 @@
     private val mediaFlags: MediaFlags,
     private val imageLoader: ImageLoader,
     private val statusBarManager: StatusBarManager,
+    private val media3ActionFactory: Media3ActionFactory,
 ) {
     private val mediaProcessingJobs = ConcurrentHashMap<String, Job>()
 
@@ -364,7 +365,7 @@
             )
         }
 
-    private fun createActionsFromState(
+    private suspend fun createActionsFromState(
         packageName: String,
         controller: MediaController,
         user: UserHandle,
@@ -373,6 +374,12 @@
             return null
         }
 
+        if (mediaFlags.areMedia3ActionsEnabled(packageName, user)) {
+            return media3ActionFactory.createActionsFromSession(
+                packageName,
+                controller.sessionToken,
+            )
+        }
         return createActionsFromState(context, packageName, controller)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaProcessingHelper.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaProcessingHelper.kt
index 2bdee67..beb4d41 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaProcessingHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaProcessingHelper.kt
@@ -141,6 +141,7 @@
     new: MediaData,
     old: MediaData,
 ): Boolean {
+    // TODO(b/360196209): account for actions generated from media3
     val oldState = MediaController(context, old.token!!).playbackState
     return if (
         new.semanticActions == null &&
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt
index 88c47ba..0b598c1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt
@@ -140,6 +140,10 @@
         )
     }
 
+    fun logMedia3UnsupportedCommand(command: String) {
+        buffer.log(TAG, LogLevel.DEBUG, { str1 = command }, { "Unsupported media3 command $str1" })
+    }
+
     companion object {
         private const val TAG = "MediaLog"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.java b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.java
deleted file mode 100644
index 6caf5c2..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.media.controls.util;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.media.session.MediaController;
-import android.media.session.MediaSession;
-
-import javax.inject.Inject;
-
-/**
- * Testable wrapper around {@link MediaController} constructor.
- */
-public class MediaControllerFactory {
-
-    private final Context mContext;
-
-    @Inject
-    public MediaControllerFactory(Context context) {
-        mContext = context;
-    }
-
-    /**
-     * Creates a new MediaController from a session's token.
-     *
-     * @param token The token for the session. This value must never be null.
-     */
-    public MediaController create(@NonNull MediaSession.Token token) {
-        return new MediaController(mContext, token);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.kt
new file mode 100644
index 0000000..741f529
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.media.controls.util
+
+import android.content.Context
+import android.media.session.MediaController
+import android.media.session.MediaSession
+import android.os.Looper
+import androidx.concurrent.futures.await
+import androidx.media3.session.MediaController as Media3Controller
+import androidx.media3.session.SessionToken
+import javax.inject.Inject
+
+/** Testable wrapper for media controller construction */
+open class MediaControllerFactory @Inject constructor(private val context: Context) {
+    /**
+     * Creates a new [MediaController] from the framework session token.
+     *
+     * @param token The token for the session. This value must never be null.
+     */
+    open fun create(token: MediaSession.Token): MediaController {
+        return MediaController(context, token)
+    }
+
+    /** Creates a new [Media3Controller] from a [SessionToken] */
+    open suspend fun create(token: SessionToken, looper: Looper): Media3Controller {
+        return Media3Controller.Builder(context, token)
+            .setApplicationLooper(looper)
+            .buildAsync()
+            .await()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
index d4af1b5..ac60c47 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
@@ -18,9 +18,10 @@
 
 import android.app.StatusBarManager
 import android.os.UserHandle
+import com.android.systemui.Flags
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
+import com.android.systemui.flags.Flags as FlagsClassic
 import javax.inject.Inject
 
 @SysUISingleton
@@ -29,22 +30,29 @@
      * Check whether media control actions should be based on PlaybackState instead of notification
      */
     fun areMediaSessionActionsEnabled(packageName: String, user: UserHandle): Boolean {
-        // Allow global override with flag
         return StatusBarManager.useMediaSessionActionsForApp(packageName, user)
     }
 
+    /** Check whether media control actions should be derived from Media3 controller */
+    fun areMedia3ActionsEnabled(packageName: String, user: UserHandle): Boolean {
+        val compatFlag = StatusBarManager.useMedia3ControllerForApp(packageName, user)
+        val featureFlag = Flags.mediaControlsButtonMedia3()
+        return featureFlag && compatFlag
+    }
+
     /**
      * If true, keep active media controls for the lifetime of the MediaSession, regardless of
      * whether the underlying notification was dismissed
      */
-    fun isRetainingPlayersEnabled() = featureFlags.isEnabled(Flags.MEDIA_RETAIN_SESSIONS)
+    fun isRetainingPlayersEnabled() = featureFlags.isEnabled(FlagsClassic.MEDIA_RETAIN_SESSIONS)
 
     /** Check whether to get progress information for resume players */
-    fun isResumeProgressEnabled() = featureFlags.isEnabled(Flags.MEDIA_RESUME_PROGRESS)
+    fun isResumeProgressEnabled() = featureFlags.isEnabled(FlagsClassic.MEDIA_RESUME_PROGRESS)
 
     /** If true, do not automatically dismiss the recommendation card */
-    fun isPersistentSsCardEnabled() = featureFlags.isEnabled(Flags.MEDIA_RETAIN_RECOMMENDATIONS)
+    fun isPersistentSsCardEnabled() =
+        featureFlags.isEnabled(FlagsClassic.MEDIA_RETAIN_RECOMMENDATIONS)
 
     /** Check whether we allow remote media to generate resume controls */
-    fun isRemoteResumeAllowed() = featureFlags.isEnabled(Flags.MEDIA_REMOTE_RESUME)
+    fun isRemoteResumeAllowed() = featureFlags.isEnabled(FlagsClassic.MEDIA_REMOTE_RESUME)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/SessionTokenFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/SessionTokenFactory.kt
new file mode 100644
index 0000000..b289fd4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/SessionTokenFactory.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.util
+
+import android.content.Context
+import android.media.session.MediaSession
+import androidx.concurrent.futures.await
+import androidx.media3.session.SessionToken
+import javax.inject.Inject
+
+/** Testable wrapper for [SessionToken] creation */
+open class SessionTokenFactory @Inject constructor(private val context: Context) {
+    /** Create a new [SessionToken] from the framework [MediaSession.Token] */
+    open suspend fun createTokenFromLegacy(token: MediaSession.Token): SessionToken {
+        return SessionToken.createSessionToken(context, token).await()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
index bf2aa7e..56885c3 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
@@ -18,7 +18,7 @@
 
 import android.annotation.ColorInt
 import android.annotation.UserIdInt
-import android.app.ActivityManager.RecentTaskInfo
+import android.app.TaskInfo
 import android.content.ComponentName
 import com.android.wm.shell.shared.split.SplitBounds
 
@@ -34,7 +34,7 @@
     val splitBounds: SplitBounds?,
 ) {
     constructor(
-        taskInfo: RecentTaskInfo,
+        taskInfo: TaskInfo,
         isForegroundTask: Boolean,
         userType: UserType,
         splitBounds: SplitBounds? = null
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
index 82e58cc..d94424c 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
@@ -23,7 +23,7 @@
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.kotlin.getOrNull
 import com.android.wm.shell.recents.RecentTasks
-import com.android.wm.shell.shared.GroupedRecentTaskInfo
+import com.android.wm.shell.shared.GroupedTaskInfo
 import java.util.Optional
 import java.util.concurrent.Executor
 import javax.inject.Inject
@@ -51,7 +51,7 @@
 
     override suspend fun loadRecentTasks(): List<RecentTask> =
         withContext(coroutineDispatcher) {
-            val groupedTasks: List<GroupedRecentTaskInfo> = recents?.getTasks() ?: emptyList()
+            val groupedTasks: List<GroupedTaskInfo> = recents?.getTasks() ?: emptyList()
             // Note: the returned task list is from the most-recent to least-recent order.
             // When opening the app selector in full screen, index 0 will be just the app selector
             // activity and a null second task, so the foreground task will be index 1, but when
@@ -86,7 +86,7 @@
             }
         }
 
-    private suspend fun RecentTasks.getTasks(): List<GroupedRecentTaskInfo> =
+    private suspend fun RecentTasks.getTasks(): List<GroupedTaskInfo> =
         suspendCoroutine { continuation ->
             getRecentTasks(
                 Integer.MAX_VALUE,
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
index 96c0cac..40613c0 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
@@ -149,6 +149,7 @@
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.data.repository.LightBarControllerStore;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 import com.android.systemui.statusbar.phone.AutoHideController;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -258,8 +259,7 @@
     private boolean mTransientShownFromGestureOnSystemBar;
     private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
     private LightBarController mLightBarController;
-    private final LightBarController mMainLightBarController;
-    private final LightBarController.Factory mLightBarControllerFactory;
+    private final LightBarControllerStore mLightBarControllerStore;
     private AutoHideController mAutoHideController;
     private final AutoHideController mMainAutoHideController;
     private final AutoHideController.Factory mAutoHideControllerFactory;
@@ -580,8 +580,7 @@
             @Background Executor bgExecutor,
             UiEventLogger uiEventLogger,
             NavBarHelper navBarHelper,
-            LightBarController mainLightBarController,
-            LightBarController.Factory lightBarControllerFactory,
+            LightBarControllerStore lightBarControllerStore,
             AutoHideController mainAutoHideController,
             AutoHideController.Factory autoHideControllerFactory,
             Optional<TelecomManager> telecomManagerOptional,
@@ -628,8 +627,7 @@
         mUiEventLogger = uiEventLogger;
         mNavBarHelper = navBarHelper;
         mNotificationShadeDepthController = notificationShadeDepthController;
-        mMainLightBarController = mainLightBarController;
-        mLightBarControllerFactory = lightBarControllerFactory;
+        mLightBarControllerStore = lightBarControllerStore;
         mMainAutoHideController = mainAutoHideController;
         mAutoHideControllerFactory = autoHideControllerFactory;
         mTelecomManagerOptional = telecomManagerOptional;
@@ -842,8 +840,7 @@
         // Unfortunately, we still need it because status bar needs LightBarController
         // before notifications creation. We cannot directly use getLightBarController()
         // from NavigationBarFragment directly.
-        LightBarController lightBarController = mIsOnDefaultDisplay
-                ? mMainLightBarController : mLightBarControllerFactory.create(mContext);
+        LightBarController lightBarController = mLightBarControllerStore.forDisplay(mDisplayId);
         setLightBarController(lightBarController);
 
         // TODO(b/118592525): to support multi-display, we start to add something which is
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
index 1c9cb3d..fef5a74 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
@@ -25,6 +25,7 @@
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.coroutineScope
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.internal.logging.MetricsLogger
 import com.android.systemui.animation.Expandable
 import com.android.systemui.dagger.qualifiers.Background
@@ -48,7 +49,6 @@
 import com.android.systemui.qs.tiles.viewmodel.QSTileState
 import com.android.systemui.res.R
 import javax.inject.Inject
-import com.android.app.tracing.coroutines.launchTraced as launch
 import kotlinx.coroutines.runBlocking
 
 class ModesTile
@@ -120,8 +120,7 @@
         tileState = tileMapper.map(config, model)
         state?.apply {
             this.state = tileState.activationState.legacyState
-            val tileStateIcon = tileState.icon()
-            icon = tileStateIcon?.asQSTileIcon() ?: ResourceIcon.get(ICON_RES_ID)
+            icon = tileState.icon?.asQSTileIcon() ?: ResourceIcon.get(ICON_RES_ID)
             label = tileLabel
             secondaryLabel = tileState.secondaryLabel
             contentDescription = tileState.contentDescription
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt
index 9fb1d46..d67057a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt
@@ -30,10 +30,8 @@
 /** Maps [AirplaneModeTileModel] to [QSTileState]. */
 class AirplaneModeMapper
 @Inject
-constructor(
-    @Main private val resources: Resources,
-    val theme: Theme,
-) : QSTileDataToStateMapper<AirplaneModeTileModel> {
+constructor(@Main private val resources: Resources, val theme: Theme) :
+    QSTileDataToStateMapper<AirplaneModeTileModel> {
 
     override fun map(config: QSTileConfig, data: AirplaneModeTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
@@ -43,16 +41,7 @@
                 } else {
                     R.drawable.qs_airplane_icon_off
                 }
-
-            icon = {
-                Icon.Loaded(
-                    resources.getDrawable(
-                        iconRes!!,
-                        theme,
-                    ),
-                    contentDescription = null
-                )
-            }
+            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
             if (data.isEnabled) {
                 activationState = QSTileState.ActivationState.ACTIVE
                 secondaryLabel = resources.getStringArray(R.array.tile_states_airplane)[2]
@@ -62,9 +51,6 @@
             }
             contentDescription = label
             supportedActions =
-                setOf(
-                    QSTileState.UserAction.CLICK,
-                    QSTileState.UserAction.LONG_CLICK,
-                )
+                setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
         }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt
index f088943..7322b8d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt
@@ -45,6 +45,7 @@
         val formatter24Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("E HH:mm")
         val formatterDateOnly: DateTimeFormatter = DateTimeFormatter.ofPattern("E MMM d")
     }
+
     override fun map(config: QSTileConfig, data: AlarmTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
             when (data) {
@@ -54,13 +55,13 @@
                     val alarmDateTime =
                         LocalDateTime.ofInstant(
                             Instant.ofEpochMilli(data.alarmClockInfo.triggerTime),
-                            TimeZone.getDefault().toZoneId()
+                            TimeZone.getDefault().toZoneId(),
                         )
 
                     val nowDateTime =
                         LocalDateTime.ofInstant(
                             Instant.ofEpochMilli(clock.currentTimeMillis()),
-                            TimeZone.getDefault().toZoneId()
+                            TimeZone.getDefault().toZoneId(),
                         )
 
                     // Edge case: If it's 8:00:30 right now and alarm is requested for next week at
@@ -84,7 +85,7 @@
                 }
             }
             iconRes = R.drawable.ic_alarm
-            icon = { Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) }
+            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
             sideViewIcon = QSTileState.SideViewIcon.Chevron
             contentDescription = label
             supportedActions = setOf(QSTileState.UserAction.CLICK)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt
index bcf0935..5b30e8d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt
@@ -29,10 +29,8 @@
 /** Maps [BatterySaverTileModel] to [QSTileState]. */
 open class BatterySaverTileMapper
 @Inject
-constructor(
-    @Main protected val resources: Resources,
-    private val theme: Resources.Theme,
-) : QSTileDataToStateMapper<BatterySaverTileModel> {
+constructor(@Main protected val resources: Resources, private val theme: Resources.Theme) :
+    QSTileDataToStateMapper<BatterySaverTileModel> {
 
     override fun map(config: QSTileConfig, data: BatterySaverTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
@@ -41,8 +39,7 @@
             iconRes =
                 if (data.isPowerSaving) R.drawable.qs_battery_saver_icon_on
                 else R.drawable.qs_battery_saver_icon_off
-            icon = { Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) }
-
+            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
             sideViewIcon = QSTileState.SideViewIcon.None
 
             if (data.isPluggedIn) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt
index cad7c65..7c90b3d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.qs.tiles.impl.colorcorrection.domain
 
 import android.content.res.Resources
+import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
 import com.android.systemui.qs.tiles.impl.colorcorrection.domain.model.ColorCorrectionTileModel
@@ -28,17 +29,14 @@
 /** Maps [ColorCorrectionTileModel] to [QSTileState]. */
 class ColorCorrectionTileMapper
 @Inject
-constructor(
-    @Main private val resources: Resources,
-    private val theme: Resources.Theme,
-) : QSTileDataToStateMapper<ColorCorrectionTileModel> {
+constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
+    QSTileDataToStateMapper<ColorCorrectionTileModel> {
 
     override fun map(config: QSTileConfig, data: ColorCorrectionTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
             val subtitleArray = resources.getStringArray(R.array.tile_states_color_correction)
-
             iconRes = R.drawable.ic_qs_color_correction
-
+            icon = Icon.Loaded(resources.getDrawable(R.drawable.ic_qs_color_correction)!!, null)
             if (data.isEnabled) {
                 activationState = QSTileState.ActivationState.ACTIVE
                 secondaryLabel = subtitleArray[2]
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/CustomTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/CustomTileMapper.kt
index 984228d..60aa4ea 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/CustomTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/CustomTileMapper.kt
@@ -35,10 +35,8 @@
 @SysUISingleton
 class CustomTileMapper
 @Inject
-constructor(
-    private val context: Context,
-    private val uriGrantsManager: IUriGrantsManager,
-) : QSTileDataToStateMapper<CustomTileDataModel> {
+constructor(private val context: Context, private val uriGrantsManager: IUriGrantsManager) :
+    QSTileDataToStateMapper<CustomTileDataModel> {
 
     override fun map(config: QSTileConfig, data: CustomTileDataModel): QSTileState {
         val userContext =
@@ -50,7 +48,7 @@
 
         val iconResult =
             if (userContext != null) {
-                getIconProvider(
+                getIcon(
                     userContext = userContext,
                     icon = data.tile.icon,
                     callingAppUid = data.callingAppUid,
@@ -58,16 +56,16 @@
                     defaultIcon = data.defaultTileIcon,
                 )
             } else {
-                IconResult({ null }, true)
+                IconResult(null, true)
             }
 
-        return QSTileState.build(iconResult.iconProvider, data.tile.label) {
+        return QSTileState.build(iconResult.icon, data.tile.label) {
             var tileState: Int = data.tile.state
             if (data.hasPendingBind) {
                 tileState = Tile.STATE_UNAVAILABLE
             }
 
-            icon = iconResult.iconProvider
+            icon = iconResult.icon
             activationState =
                 if (iconResult.failedToLoad) {
                     QSTileState.ActivationState.UNAVAILABLE
@@ -102,7 +100,7 @@
     }
 
     @SuppressLint("MissingPermission") // android.permission.INTERACT_ACROSS_USERS_FULL
-    private fun getIconProvider(
+    private fun getIcon(
         userContext: Context,
         icon: android.graphics.drawable.Icon?,
         callingAppUid: Int,
@@ -123,17 +121,12 @@
                 null
             } ?: defaultIcon?.loadDrawable(userContext)
         return IconResult(
-            {
-                drawable?.constantState?.newDrawable()?.let {
-                    Icon.Loaded(it, contentDescription = null)
-                }
+            drawable?.constantState?.newDrawable()?.let {
+                Icon.Loaded(it, contentDescription = null)
             },
             failedToLoad,
         )
     }
 
-    class IconResult(
-        val iconProvider: () -> Icon?,
-        val failedToLoad: Boolean,
-    )
+    class IconResult(val icon: Icon?, val failedToLoad: Boolean)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
index d7d6124..7e557eb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
@@ -30,10 +30,8 @@
 /** Maps [FlashlightTileModel] to [QSTileState]. */
 class FlashlightMapper
 @Inject
-constructor(
-    @Main private val resources: Resources,
-    private val theme: Theme,
-) : QSTileDataToStateMapper<FlashlightTileModel> {
+constructor(@Main private val resources: Resources, private val theme: Theme) :
+    QSTileDataToStateMapper<FlashlightTileModel> {
 
     override fun map(config: QSTileConfig, data: FlashlightTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
@@ -43,15 +41,8 @@
                 } else {
                     R.drawable.qs_flashlight_icon_off
                 }
-            val icon =
-                Icon.Loaded(
-                    resources.getDrawable(
-                        iconRes!!,
-                        theme,
-                    ),
-                    contentDescription = null
-                )
-            this.icon = { icon }
+
+            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
 
             contentDescription = label
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt
index 6b4dda1..9d44fc6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt
@@ -29,23 +29,13 @@
 /** Maps [FontScalingTileModel] to [QSTileState]. */
 class FontScalingTileMapper
 @Inject
-constructor(
-    @Main private val resources: Resources,
-    private val theme: Resources.Theme,
-) : QSTileDataToStateMapper<FontScalingTileModel> {
+constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
+    QSTileDataToStateMapper<FontScalingTileModel> {
 
     override fun map(config: QSTileConfig, data: FontScalingTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
             iconRes = R.drawable.ic_qs_font_scaling
-            val icon =
-                Icon.Loaded(
-                    resources.getDrawable(
-                        iconRes!!,
-                        theme,
-                    ),
-                    contentDescription = null
-                )
-            this.icon = { icon }
+            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
             contentDescription = label
             activationState = QSTileState.ActivationState.ACTIVE
             sideViewIcon = QSTileState.SideViewIcon.Chevron
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt
index 8dd611f..c3ac1f8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt
@@ -36,9 +36,7 @@
         QSTileState.build(resources, theme, config.uiConfig) {
             label = resources.getString(R.string.quick_settings_hearing_devices_label)
             iconRes = R.drawable.qs_hearing_devices_icon
-            val loadedIcon =
-                Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
-            icon = { loadedIcon }
+            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
             sideViewIcon = QSTileState.SideViewIcon.Chevron
             contentDescription = label
             if (data.isAnyActiveHearingDevice) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt
index bb0b9b7..fc94585 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt
@@ -61,28 +61,26 @@
             when (val dataIcon = data.icon) {
                 is InternetTileIconModel.ResourceId -> {
                     iconRes = dataIcon.resId
-                    icon = {
+                    icon =
                         Icon.Loaded(
                             resources.getDrawable(dataIcon.resId, theme),
                             contentDescription = null,
                         )
-                    }
                 }
 
                 is InternetTileIconModel.Cellular -> {
                     val signalDrawable = SignalDrawable(context, handler)
                     signalDrawable.setLevel(dataIcon.level)
-                    icon = { Icon.Loaded(signalDrawable, contentDescription = null) }
+                    icon = Icon.Loaded(signalDrawable, contentDescription = null)
                 }
 
                 is InternetTileIconModel.Satellite -> {
                     iconRes = dataIcon.resourceIcon.res // level is inferred from res
-                    icon = {
+                    icon =
                         Icon.Loaded(
                             resources.getDrawable(dataIcon.resourceIcon.res, theme),
                             contentDescription = null,
                         )
-                    }
                 }
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt
index 40aee65..3692c35 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt
@@ -30,10 +30,8 @@
 /** Maps [ColorInversionTileModel] to [QSTileState]. */
 class ColorInversionTileMapper
 @Inject
-constructor(
-    @Main private val resources: Resources,
-    private val theme: Theme,
-) : QSTileDataToStateMapper<ColorInversionTileModel> {
+constructor(@Main private val resources: Resources, private val theme: Theme) :
+    QSTileDataToStateMapper<ColorInversionTileModel> {
     override fun map(config: QSTileConfig, data: ColorInversionTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
             val subtitleArray = resources.getStringArray(R.array.tile_states_inversion)
@@ -47,7 +45,7 @@
                 secondaryLabel = subtitleArray[1]
                 iconRes = R.drawable.qs_invert_colors_icon_off
             }
-            icon = { Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) }
+            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
             contentDescription = label
             supportedActions =
                 setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt
index ff931b3..3fe2a77 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt
@@ -28,21 +28,26 @@
 
 class IssueRecordingMapper
 @Inject
-constructor(
-    @Main private val resources: Resources,
-    private val theme: Theme,
-) : QSTileDataToStateMapper<IssueRecordingModel> {
+constructor(@Main private val resources: Resources, private val theme: Theme) :
+    QSTileDataToStateMapper<IssueRecordingModel> {
     override fun map(config: QSTileConfig, data: IssueRecordingModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
-            if (data.isRecording) {
-                activationState = QSTileState.ActivationState.ACTIVE
-                secondaryLabel = resources.getString(R.string.qs_record_issue_stop)
-                icon = { Icon.Resource(R.drawable.qs_record_issue_icon_on, null) }
-            } else {
-                icon = { Icon.Resource(R.drawable.qs_record_issue_icon_off, null) }
-                activationState = QSTileState.ActivationState.INACTIVE
-                secondaryLabel = resources.getString(R.string.qs_record_issue_start)
-            }
+            icon =
+                if (data.isRecording) {
+                    activationState = QSTileState.ActivationState.ACTIVE
+                    secondaryLabel = resources.getString(R.string.qs_record_issue_stop)
+                    Icon.Loaded(
+                        resources.getDrawable(R.drawable.qs_record_issue_icon_on, theme),
+                        null,
+                    )
+                } else {
+                    activationState = QSTileState.ActivationState.INACTIVE
+                    secondaryLabel = resources.getString(R.string.qs_record_issue_start)
+                    Icon.Loaded(
+                        resources.getDrawable(R.drawable.qs_record_issue_icon_off, theme),
+                        null,
+                    )
+                }
             supportedActions = setOf(QSTileState.UserAction.CLICK)
             contentDescription = "$label, $secondaryLabel"
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt
index d58f5ab..08432f6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt
@@ -30,10 +30,8 @@
 /** Maps [LocationTileModel] to [QSTileState]. */
 class LocationTileMapper
 @Inject
-constructor(
-    @Main private val resources: Resources,
-    private val theme: Theme,
-) : QSTileDataToStateMapper<LocationTileModel> {
+constructor(@Main private val resources: Resources, private val theme: Theme) :
+    QSTileDataToStateMapper<LocationTileModel> {
 
     override fun map(config: QSTileConfig, data: LocationTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
@@ -43,17 +41,9 @@
                 } else {
                     R.drawable.qs_location_icon_off
                 }
-            val icon =
-                Icon.Loaded(
-                    resources.getDrawable(
-                        iconRes!!,
-                        theme,
-                    ),
-                    contentDescription = null
-                )
-            this.icon = { icon }
+            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
 
-            this.label = resources.getString(R.string.quick_settings_location_label)
+            label = resources.getString(R.string.quick_settings_location_label)
 
             if (data.isEnabled) {
                 activationState = QSTileState.ActivationState.ACTIVE
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
index 69da313..4a64313 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
@@ -30,14 +30,12 @@
 
 class ModesTileMapper
 @Inject
-constructor(
-    @Main private val resources: Resources,
-    val theme: Resources.Theme,
-) : QSTileDataToStateMapper<ModesTileModel> {
+constructor(@Main private val resources: Resources, val theme: Resources.Theme) :
+    QSTileDataToStateMapper<ModesTileModel> {
     override fun map(config: QSTileConfig, data: ModesTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
             iconRes = data.iconResId
-            icon = { data.icon }
+            icon = data.icon
             activationState =
                 if (data.isActivated) {
                     QSTileState.ActivationState.ACTIVE
@@ -47,10 +45,7 @@
             secondaryLabel = getModesStatus(data, resources)
             contentDescription = "$label. $secondaryLabel"
             supportedActions =
-                setOf(
-                    QSTileState.UserAction.CLICK,
-                    QSTileState.UserAction.LONG_CLICK,
-                )
+                setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
             sideViewIcon = QSTileState.SideViewIcon.Chevron
             expandedAccessibilityClass = Button::class
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt
index bcf7cc7..081a03c7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt
@@ -57,9 +57,8 @@
                 activationState = QSTileState.ActivationState.INACTIVE
                 iconRes = R.drawable.qs_nightlight_icon_off
             }
-            val loadedIcon =
-                Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
-            icon = { loadedIcon }
+
+            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
 
             secondaryLabel = getSecondaryLabel(data, resources)
 
@@ -70,7 +69,7 @@
 
     private fun getSecondaryLabel(
         data: NightDisplayTileModel,
-        resources: Resources
+        resources: Resources,
     ): CharSequence? {
         when (data) {
             is NightDisplayTileModel.AutoModeTwilight -> {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt
index 4080996..8e5d0d4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt
@@ -29,17 +29,15 @@
 /** Maps [OneHandedModeTileModel] to [QSTileState]. */
 class OneHandedModeTileMapper
 @Inject
-constructor(
-    @Main private val resources: Resources,
-    private val theme: Resources.Theme,
-) : QSTileDataToStateMapper<OneHandedModeTileModel> {
+constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
+    QSTileDataToStateMapper<OneHandedModeTileModel> {
 
     override fun map(config: QSTileConfig, data: OneHandedModeTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
             val subtitleArray = resources.getStringArray(R.array.tile_states_onehanded)
             label = resources.getString(R.string.quick_settings_onehanded_label)
             iconRes = com.android.internal.R.drawable.ic_qs_one_handed_mode
-            icon = { Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) }
+            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
             if (data.isEnabled) {
                 activationState = QSTileState.ActivationState.ACTIVE
                 secondaryLabel = subtitleArray[2]
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt
index 8231742..5c6351e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt
@@ -29,17 +29,15 @@
 /** Maps [QRCodeScannerTileModel] to [QSTileState]. */
 class QRCodeScannerTileMapper
 @Inject
-constructor(
-    @Main private val resources: Resources,
-    private val theme: Resources.Theme,
-) : QSTileDataToStateMapper<QRCodeScannerTileModel> {
+constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
+    QSTileDataToStateMapper<QRCodeScannerTileModel> {
 
     override fun map(config: QSTileConfig, data: QRCodeScannerTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
             label = resources.getString(R.string.qr_code_scanner_title)
             contentDescription = label
             iconRes = R.drawable.ic_qr_code_scanner
-            icon = { Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) }
+            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
             sideViewIcon = QSTileState.SideViewIcon.Chevron
             supportedActions = setOf(QSTileState.UserAction.CLICK)
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt
index 85ee022..fe77fe6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt
@@ -30,10 +30,8 @@
 /** Maps [ReduceBrightColorsTileModel] to [QSTileState]. */
 class ReduceBrightColorsTileMapper
 @Inject
-constructor(
-    @Main private val resources: Resources,
-    private val theme: Resources.Theme,
-) : QSTileDataToStateMapper<ReduceBrightColorsTileModel> {
+constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
+    QSTileDataToStateMapper<ReduceBrightColorsTileModel> {
 
     override fun map(config: QSTileConfig, data: ReduceBrightColorsTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
@@ -50,12 +48,7 @@
                     resources
                         .getStringArray(R.array.tile_states_reduce_brightness)[Tile.STATE_INACTIVE]
             }
-            icon = {
-                Icon.Loaded(
-                    drawable = resources.getDrawable(iconRes!!, theme),
-                    contentDescription = null
-                )
-            }
+            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
             label =
                 resources.getString(com.android.internal.R.string.reduce_bright_colors_feature_name)
             contentDescription = label
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt
index 33dc6ed..9a003ff 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt
@@ -36,36 +36,33 @@
     @Main private val resources: Resources,
     private val theme: Resources.Theme,
     private val devicePostureController: DevicePostureController,
-    private val deviceStateManager: DeviceStateManager
+    private val deviceStateManager: DeviceStateManager,
 ) : QSTileDataToStateMapper<RotationLockTileModel> {
     override fun map(config: QSTileConfig, data: RotationLockTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
-            this.label = resources.getString(R.string.quick_settings_rotation_unlocked_label)
-            this.contentDescription =
-                resources.getString(R.string.accessibility_quick_settings_rotation)
+            label = resources.getString(R.string.quick_settings_rotation_unlocked_label)
+            contentDescription = resources.getString(R.string.accessibility_quick_settings_rotation)
 
             if (data.isRotationLocked) {
                 activationState = QSTileState.ActivationState.INACTIVE
-                this.secondaryLabel = EMPTY_SECONDARY_STRING
+                secondaryLabel = EMPTY_SECONDARY_STRING
                 iconRes = R.drawable.qs_auto_rotate_icon_off
             } else {
                 activationState = QSTileState.ActivationState.ACTIVE
-                this.secondaryLabel =
+                secondaryLabel =
                     if (data.isCameraRotationEnabled) {
                         resources.getString(R.string.rotation_lock_camera_rotation_on)
                     } else {
                         EMPTY_SECONDARY_STRING
                     }
-                this.iconRes = R.drawable.qs_auto_rotate_icon_on
+                iconRes = R.drawable.qs_auto_rotate_icon_on
             }
-            this.icon = {
-                Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
-            }
+            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
             if (isDeviceFoldable(resources, deviceStateManager)) {
-                this.secondaryLabel = getSecondaryLabelWithPosture(this.activationState)
+                secondaryLabel = getSecondaryLabelWithPosture(activationState)
             }
-            this.stateDescription = this.secondaryLabel
-            this.sideViewIcon = QSTileState.SideViewIcon.None
+            stateDescription = secondaryLabel
+            sideViewIcon = QSTileState.SideViewIcon.None
             supportedActions =
                 setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
         }
@@ -86,7 +83,7 @@
         return resources.getString(
             R.string.rotation_tile_with_posture_secondary_label_template,
             stateName,
-            posture
+            posture,
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt
index 888bba87..08196bb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt
@@ -29,10 +29,8 @@
 /** Maps [DataSaverTileModel] to [QSTileState]. */
 class DataSaverTileMapper
 @Inject
-constructor(
-    @Main private val resources: Resources,
-    private val theme: Resources.Theme,
-) : QSTileDataToStateMapper<DataSaverTileModel> {
+constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
+    QSTileDataToStateMapper<DataSaverTileModel> {
     override fun map(config: QSTileConfig, data: DataSaverTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
             with(data) {
@@ -45,9 +43,7 @@
                     iconRes = R.drawable.qs_data_saver_icon_off
                     secondaryLabel = resources.getStringArray(R.array.tile_states_saver)[1]
                 }
-                val loadedIcon =
-                    Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
-                icon = { loadedIcon }
+                icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
                 contentDescription = label
                 supportedActions =
                     setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt
index e74e77f..ba06de9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt
@@ -30,10 +30,8 @@
 /** Maps [ScreenRecordModel] to [QSTileState]. */
 class ScreenRecordTileMapper
 @Inject
-constructor(
-    @Main private val resources: Resources,
-    private val theme: Resources.Theme,
-) : QSTileDataToStateMapper<ScreenRecordModel> {
+constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
+    QSTileDataToStateMapper<ScreenRecordModel> {
     override fun map(config: QSTileConfig, data: ScreenRecordModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
             label = resources.getString(R.string.quick_settings_screen_record_label)
@@ -43,24 +41,12 @@
                 is ScreenRecordModel.Recording -> {
                     activationState = QSTileState.ActivationState.ACTIVE
                     iconRes = R.drawable.qs_screen_record_icon_on
-                    val loadedIcon =
-                        Icon.Loaded(
-                            resources.getDrawable(iconRes!!, theme),
-                            contentDescription = null
-                        )
-                    icon = { loadedIcon }
                     sideViewIcon = QSTileState.SideViewIcon.None
                     secondaryLabel = resources.getString(R.string.quick_settings_screen_record_stop)
                 }
                 is ScreenRecordModel.Starting -> {
                     activationState = QSTileState.ActivationState.ACTIVE
                     iconRes = R.drawable.qs_screen_record_icon_on
-                    val loadedIcon =
-                        Icon.Loaded(
-                            resources.getDrawable(iconRes!!, theme),
-                            contentDescription = null
-                        )
-                    icon = { loadedIcon }
                     val countDown = data.countdownSeconds
                     sideViewIcon = QSTileState.SideViewIcon.None
                     secondaryLabel = String.format("%d...", countDown)
@@ -68,17 +54,13 @@
                 is ScreenRecordModel.DoingNothing -> {
                     activationState = QSTileState.ActivationState.INACTIVE
                     iconRes = R.drawable.qs_screen_record_icon_off
-                    val loadedIcon =
-                        Icon.Loaded(
-                            resources.getDrawable(iconRes!!, theme),
-                            contentDescription = null
-                        )
-                    icon = { loadedIcon }
                     sideViewIcon = QSTileState.SideViewIcon.Chevron // tapping will open dialog
                     secondaryLabel =
                         resources.getString(R.string.quick_settings_screen_record_start)
                 }
             }
+            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
+
             contentDescription =
                 if (TextUtils.isEmpty(secondaryLabel)) label
                 else TextUtils.concat(label, ", ", secondaryLabel)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt
index 597cf27..b4cfec4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt
@@ -51,8 +51,7 @@
             supportedActions =
                 setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
             iconRes = sensorPrivacyTileResources.getIconRes(data.isBlocked)
-            icon = { Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) }
-
+            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
             sideViewIcon = QSTileState.SideViewIcon.None
 
             if (data.isBlocked) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt
index f29c745d..eda8e5c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt
@@ -34,14 +34,13 @@
 /** Maps [UiModeNightTileModel] to [QSTileState]. */
 class UiModeNightTileMapper
 @Inject
-constructor(
-    @Main private val resources: Resources,
-    private val theme: Theme,
-) : QSTileDataToStateMapper<UiModeNightTileModel> {
+constructor(@Main private val resources: Resources, private val theme: Theme) :
+    QSTileDataToStateMapper<UiModeNightTileModel> {
     companion object {
         val formatter12Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("hh:mm a")
         val formatter24Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm")
     }
+
     override fun map(config: QSTileConfig, data: UiModeNightTileModel): QSTileState =
         with(data) {
             QSTileState.build(resources, theme, config.uiConfig) {
@@ -76,7 +75,7 @@
                                 if (isNightMode)
                                     R.string.quick_settings_dark_mode_secondary_label_until
                                 else R.string.quick_settings_dark_mode_secondary_label_on_at,
-                                formatter.format(time)
+                                formatter.format(time),
                             )
                     } else if (
                         nightModeCustomType == UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME
@@ -121,9 +120,7 @@
                     if (activationState == QSTileState.ActivationState.ACTIVE)
                         R.drawable.qs_light_dark_theme_icon_on
                     else R.drawable.qs_light_dark_theme_icon_off
-                val loadedIcon =
-                    Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
-                icon = { loadedIcon }
+                icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
 
                 supportedActions =
                     if (activationState == QSTileState.ActivationState.UNAVAILABLE)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt
index eee95b7..a1bc8a8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt
@@ -42,9 +42,7 @@
             label = getTileLabel()!!
             contentDescription = label
             iconRes = com.android.internal.R.drawable.stat_sys_managed_profile_status
-            icon = {
-                Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
-            }
+            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
 
             when (data) {
                 is WorkModeTileModel.HasActiveProfile -> {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
index 549f0a7..8394be5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
@@ -35,7 +35,7 @@
  * // TODO(b/http://b/299909989): Clean up legacy mappings after the transition
  */
 data class QSTileState(
-    val icon: () -> Icon?,
+    val icon: Icon?,
     val iconRes: Int?,
     val label: CharSequence,
     val activationState: ActivationState,
@@ -54,21 +54,18 @@
             resources: Resources,
             theme: Theme,
             config: QSTileUIConfig,
-            builder: Builder.() -> Unit
+            builder: Builder.() -> Unit,
         ): QSTileState {
             val iconDrawable = resources.getDrawable(config.iconRes, theme)
             return build(
-                { Icon.Loaded(iconDrawable, null) },
+                Icon.Loaded(iconDrawable, null),
                 resources.getString(config.labelRes),
                 builder,
             )
         }
 
-        fun build(
-            icon: () -> Icon?,
-            label: CharSequence,
-            builder: Builder.() -> Unit
-        ): QSTileState = Builder(icon, label).apply { builder() }.build()
+        fun build(icon: Icon?, label: CharSequence, builder: Builder.() -> Unit): QSTileState =
+            Builder(icon, label).apply { builder() }.build()
     }
 
     enum class ActivationState(val legacyState: Int) {
@@ -117,10 +114,7 @@
         data object None : SideViewIcon
     }
 
-    class Builder(
-        var icon: () -> Icon?,
-        var label: CharSequence,
-    ) {
+    class Builder(var icon: Icon?, var label: CharSequence) {
         var iconRes: Int? = null
         var activationState: ActivationState = ActivationState.INACTIVE
         var secondaryLabel: CharSequence? = null
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
index f89745f..35b1b96 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
@@ -19,6 +19,7 @@
 import android.content.Context
 import android.os.UserHandle
 import android.util.Log
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.internal.logging.InstanceId
 import com.android.systemui.Dumpable
 import com.android.systemui.animation.Expandable
@@ -42,7 +43,6 @@
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.takeWhile
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 // TODO(b/http://b/299909989): Use QSTileViewModel directly after the rollout
 class QSTileViewModelAdapter
@@ -223,7 +223,7 @@
         fun mapState(
             context: Context,
             viewModelState: QSTileState,
-            config: QSTileConfig
+            config: QSTileConfig,
         ): QSTile.State =
             // we have to use QSTile.BooleanState to support different side icons
             // which are bound to instanceof QSTile.BooleanState in QSTileView.
@@ -241,7 +241,7 @@
                     viewModelState.supportedActions.contains(QSTileState.UserAction.TOGGLE_CLICK)
 
                 icon =
-                    when (val stateIcon = viewModelState.icon()) {
+                    when (val stateIcon = viewModelState.icon) {
                         is Icon.Loaded ->
                             if (viewModelState.iconRes == null) DrawableIcon(stateIcon.drawable)
                             else DrawableIconWithRes(stateIcon.drawable, viewModelState.iconRes)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
index 6d5bf32..d4adcdd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
@@ -22,6 +22,7 @@
 import android.view.View
 import androidx.annotation.VisibleForTesting
 import androidx.asynclayoutinflater.view.AsyncLayoutInflater
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.settingslib.applications.InterestingConfigChanges
 import com.android.systemui.Dumpable
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
@@ -36,6 +37,7 @@
 import com.android.systemui.qs.dagger.QSSceneComponent
 import com.android.systemui.res.R
 import com.android.systemui.settings.brightness.MirrorController
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.util.kotlin.sample
@@ -57,7 +59,6 @@
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.flow.update
-import com.android.app.tracing.coroutines.launchTraced as launch
 import kotlinx.coroutines.withContext
 
 // TODO(307945185) Split View concerns into a ViewBinder
@@ -206,7 +207,7 @@
     dumpManager: DumpManager,
     @Main private val mainDispatcher: CoroutineDispatcher,
     @Application applicationScope: CoroutineScope,
-    private val configurationInteractor: ConfigurationInteractor,
+    @ShadeDisplayAware private val configurationInteractor: ConfigurationInteractor,
     private val asyncLayoutInflaterFactory: (Context) -> AsyncLayoutInflater,
 ) : QSContainerController, QSSceneAdapter, Dumpable {
 
@@ -219,7 +220,7 @@
         dumpManager: DumpManager,
         @Main dispatcher: CoroutineDispatcher,
         @Application scope: CoroutineScope,
-        configurationInteractor: ConfigurationInteractor,
+        @ShadeDisplayAware configurationInteractor: ConfigurationInteractor,
     ) : this(
         qsSceneComponentFactory,
         qsImplProvider,
@@ -256,7 +257,7 @@
             .stateIn(
                 applicationScope,
                 SharingStarted.WhileSubscribed(),
-                customizerState.value.isShowing
+                customizerState.value.isShowing,
             )
     override val customizerAnimationDuration: StateFlow<Int> =
         customizerState
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index f3c6190..580a51a 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -46,7 +46,6 @@
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.domain.interactor.KeyguardEnabledInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor
 import com.android.systemui.model.SceneContainerPlugin
 import com.android.systemui.model.SysUiState
 import com.android.systemui.model.updateFlags
@@ -97,7 +96,6 @@
 import kotlinx.coroutines.flow.filterIsInstance
 import kotlinx.coroutines.flow.filterNot
 import kotlinx.coroutines.flow.filterNotNull
-import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
@@ -138,7 +136,6 @@
     private val uiEventLogger: UiEventLogger,
     private val sceneBackInteractor: SceneBackInteractor,
     private val shadeSessionStorage: SessionStorage,
-    private val windowMgrLockscreenVisInteractor: WindowManagerLockscreenVisibilityInteractor,
     private val keyguardEnabledInteractor: KeyguardEnabledInteractor,
     private val dismissCallbackRegistry: DismissCallbackRegistry,
     private val statusBarStateController: SysuiStatusBarStateController,
@@ -213,7 +210,6 @@
     /** Updates the visibility of the scene container. */
     private fun hydrateVisibility() {
         applicationScope.launch {
-            // TODO(b/296114544): Combine with some global hun state to make it visible!
             deviceProvisioningInteractor.isDeviceProvisioned
                 .flatMapLatest { isAllowedToBeVisible ->
                     if (isAllowedToBeVisible) {
@@ -271,27 +267,6 @@
         handleDeviceUnlockStatus()
         handlePowerState()
         handleShadeTouchability()
-        handleSurfaceBehindKeyguardVisibility()
-    }
-
-    private fun handleSurfaceBehindKeyguardVisibility() {
-        applicationScope.launch {
-            sceneInteractor.currentScene.collectLatest { currentScene ->
-                if (currentScene == Scenes.Lockscreen) {
-                    // Wait for the screen to be on
-                    powerInteractor.isAwake.first { it }
-                    // Wait for surface to become visible
-                    windowMgrLockscreenVisInteractor.surfaceBehindVisibility.first { it }
-                    // Make sure the device is actually unlocked before force-changing the scene
-                    deviceUnlockedInteractor.deviceUnlockStatus.first { it.isUnlocked }
-                    // Override the current transition, if any, by forcing the scene to Gone
-                    sceneInteractor.changeScene(
-                        toScene = Scenes.Gone,
-                        loggingReason = "surface behind keyguard is visible",
-                    )
-                }
-            }
-        }
     }
 
     private fun handleBouncerImeVisibility() {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt b/packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt
index 2048b7c..137b4fd 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.screenshot.data.model
 
 import android.app.ActivityTaskManager.RootTaskInfo
+import com.android.systemui.screenshot.policy.childTasksTopDown
 
 /** Information about the tasks on a display. */
 data class DisplayContentModel(
@@ -27,3 +28,5 @@
     /** A list of root tasks on the display, ordered from top to bottom along the z-axis */
     val rootTasks: List<RootTaskInfo>,
 )
+
+fun DisplayContentModel.allTasks() = rootTasks.asSequence().flatMap { it.childTasksTopDown() }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureParameters.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureParameters.kt
index 5e2b576..2a4fe3e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureParameters.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureParameters.kt
@@ -16,15 +16,17 @@
 
 package com.android.systemui.screenshot.policy
 
-import android.content.ComponentName
 import android.os.UserHandle
 
-/** The parameters dictated by a [CapturePolicy], used to adjust alter screenshot request. */
 data class CaptureParameters(
-    /** How should the content be captured? */
+    /** Describes how the image should be obtained. */
     val type: CaptureType,
-    /** The focused or top component at the time of the screenshot. */
-    val component: ComponentName?,
-    /** Which user should receive the screenshot file? */
+    /** Which user to receive the image. */
     val owner: UserHandle,
+    /**
+     * The task which represents the main content or focal point of the screenshot. This is the task
+     * used for retrieval of [AssistContent][android.app.assist.AssistContent] as well as
+     * [Scroll Capture][android.view.IWindowManager.requestScrollCapture].
+     */
+    val contentTask: TaskReference,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CapturePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CapturePolicy.kt
index 0fb5366..73ff566 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CapturePolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CapturePolicy.kt
@@ -22,7 +22,7 @@
 fun interface CapturePolicy {
     /**
      * Test the policy against the current display task state. If the policy applies, Returns a
-     * [PolicyResult.Matched] containing [CaptureParameters] used to alter the request.
+     * [PolicyResult.Matched] containing [LegacyCaptureParameters] used to alter the request.
      */
     suspend fun check(content: DisplayContentModel): PolicyResult
 
@@ -35,7 +35,7 @@
             /** Why the policy matched. */
             val reason: String,
             /** Details on how to modify the screen capture request. */
-            val parameters: CaptureParameters,
+            val parameters: LegacyCaptureParameters,
         ) : PolicyResult
 
         /** The policy rules do not match the given display content and do not apply. */
@@ -43,7 +43,7 @@
             /** The name of the policy rule which matched. */
             val policy: String,
             /** Why the policy did not match. */
-            val reason: String
+            val reason: String,
         ) : PolicyResult
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureType.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureType.kt
index 9455201..34c85110 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureType.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureType.kt
@@ -20,12 +20,10 @@
 
 /** What to capture */
 sealed interface CaptureType {
+
     /** Capture the entire screen contents. */
     data class FullScreen(val displayId: Int) : CaptureType
 
     /** Capture the contents of the task only. */
     data class IsolatedTask(val taskId: Int, val taskBounds: Rect?) : CaptureType
-
-    data class RootTask(val parentTaskId: Int, val taskBounds: Rect?, val childTaskIds: List<Int>) :
-        CaptureType
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/LegacyCaptureParameters.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/LegacyCaptureParameters.kt
new file mode 100644
index 0000000..4b697b2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/LegacyCaptureParameters.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 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.screenshot.policy
+
+import android.content.ComponentName
+import android.os.UserHandle
+
+/** The parameters dictated by a [CapturePolicy], used to adjust alter screenshot request. */
+data class LegacyCaptureParameters(
+    /** How should the content be captured? */
+    val type: CaptureType,
+    /** The focused or top component at the time of the screenshot. */
+    val component: ComponentName?,
+    /** Which user should receive the screenshot file? */
+    val owner: UserHandle,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt
index e840668..a84cdf40 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt
@@ -80,6 +80,7 @@
                     Log.i(TAG, "$result")
                     return modify(original, result.parameters)
                 }
+
                 is NotMatched -> Log.i(TAG, "$result")
             }
         }
@@ -89,7 +90,45 @@
     }
 
     /** Produce a new [ScreenshotData] using [CaptureParameters] */
-    suspend fun modify(original: ScreenshotData, updates: CaptureParameters): ScreenshotData {
+    suspend fun modify(original: ScreenshotData, params: CaptureParameters): ScreenshotData {
+        Log.d(TAG, "[modify] CaptureParameters = $params")
+        // Update and apply bitmap capture depending on the parameters.
+        when (val type = params.type) {
+            is IsolatedTask -> {
+                Log.i(TAG, "Capturing task snapshot: $params")
+
+                val taskSnapshot =
+                    capture.captureTask(type.taskId) ?: error("Failed to capture task")
+
+                return original.copy(
+                    type = TAKE_SCREENSHOT_PROVIDED_IMAGE,
+                    bitmap = taskSnapshot,
+                    userHandle = params.owner,
+                    taskId = params.contentTask.taskId,
+                    topComponent = params.contentTask.component,
+                    originalScreenBounds = type.taskBounds,
+                )
+            }
+
+            is FullScreen -> {
+                Log.i(TAG, "Capturing screenshot: $params")
+
+                val screenshot =
+                    captureDisplay(type.displayId) ?: error("Failed to capture screenshot")
+                return original.copy(
+                    type = TAKE_SCREENSHOT_FULLSCREEN,
+                    bitmap = screenshot,
+                    userHandle = params.owner,
+                    topComponent = params.contentTask.component,
+                    originalScreenBounds = Rect(0, 0, screenshot.width, screenshot.height),
+                    taskId = params.contentTask.taskId,
+                )
+            }
+        }
+    }
+
+    /** Produce a new [ScreenshotData] using [LegacyCaptureParameters] */
+    suspend fun modify(original: ScreenshotData, updates: LegacyCaptureParameters): ScreenshotData {
         Log.d(TAG, "[modify] CaptureParameters = $updates")
         // Update and apply bitmap capture depending on the parameters.
         val updated =
@@ -102,14 +141,7 @@
                         type.taskId,
                         type.taskBounds,
                     )
-                is CaptureType.RootTask ->
-                    replaceWithTaskSnapshot(
-                        original,
-                        updates.component,
-                        updates.owner,
-                        type.parentTaskId,
-                        type.taskBounds,
-                    )
+
                 is FullScreen ->
                     replaceWithScreenshot(
                         original,
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt
index 1945c25..3857ba3 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt
@@ -31,11 +31,8 @@
  *
  * Parameters: Capture the whole screen, owned by the private user.
  */
-class PrivateProfilePolicy
-@Inject
-constructor(
-    private val profileTypes: ProfileTypeRepository,
-) : CapturePolicy {
+class PrivateProfilePolicy @Inject constructor(private val profileTypes: ProfileTypeRepository) :
+    CapturePolicy {
     override suspend fun check(content: DisplayContentModel): PolicyResult {
         // The systemUI notification shade isn't a private profile app, skip.
         if (content.systemUiState.shadeExpanded) {
@@ -47,25 +44,23 @@
             content.rootTasks
                 .filter { it.isVisible }
                 .firstNotNullOfOrNull { root ->
-                    root
-                        .childTasksTopDown()
-                        .firstOrNull {
-                            profileTypes.getProfileType(it.userId) == ProfileType.PRIVATE
-                        }
-                }
-                ?: return NotMatched(policy = NAME, reason = NO_VISIBLE_TASKS)
+                    root.childTasksTopDown().firstOrNull {
+                        profileTypes.getProfileType(it.userId) == ProfileType.PRIVATE
+                    }
+                } ?: return NotMatched(policy = NAME, reason = NO_VISIBLE_TASKS)
 
         // If matched, return parameters needed to modify the request.
         return Matched(
             policy = NAME,
             reason = PRIVATE_TASK_VISIBLE,
-            CaptureParameters(
+            LegacyCaptureParameters(
                 type = FullScreen(content.displayId),
                 component = content.rootTasks.first { it.isVisible }.topActivity,
                 owner = UserHandle.of(childTask.userId),
-            )
+            ),
         )
     }
+
     companion object {
         const val NAME = "PrivateProfile"
         const val SHADE_EXPANDED = "Notification shade is expanded"
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt
index dd39f92..c43e929 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt
@@ -20,7 +20,7 @@
 import com.android.systemui.screenshot.data.model.ChildTaskModel
 
 /** The child tasks of A RootTaskInfo as [ChildTaskModel] in top-down (z-index ascending) order. */
-internal fun RootTaskInfo.childTasksTopDown(): Sequence<ChildTaskModel> {
+fun RootTaskInfo.childTasksTopDown(): Sequence<ChildTaskModel> {
     return ((childTaskIds.size - 1) downTo 0).asSequence().map { index ->
         ChildTaskModel(
             childTaskIds[index],
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicy.kt
index 9967aff..5213579 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicy.kt
@@ -20,18 +20,16 @@
 import android.app.WindowConfiguration
 import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
 import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
-import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
 import android.content.ComponentName
+import android.graphics.Rect
 import android.os.UserHandle
 import android.util.Log
 import com.android.systemui.screenshot.data.model.DisplayContentModel
-import com.android.systemui.screenshot.data.model.ProfileType
 import com.android.systemui.screenshot.data.model.ProfileType.PRIVATE
 import com.android.systemui.screenshot.data.model.ProfileType.WORK
 import com.android.systemui.screenshot.data.repository.ProfileTypeRepository
 import com.android.systemui.screenshot.policy.CaptureType.FullScreen
 import com.android.systemui.screenshot.policy.CaptureType.IsolatedTask
-import com.android.systemui.screenshot.policy.CaptureType.RootTask
 import javax.inject.Inject
 
 private const val TAG = "ScreenshotPolicy"
@@ -39,7 +37,7 @@
 /** Determines what to capture and which user owns the output. */
 class ScreenshotPolicy @Inject constructor(private val profileTypes: ProfileTypeRepository) {
     /**
-     * Apply the policy to the content, resulting in [CaptureParameters].
+     * Apply the policy to the content, resulting in [LegacyCaptureParameters].
      *
      * @param content the content of the display
      * @param defaultComponent the component associated with the screenshot by default
@@ -53,7 +51,7 @@
         val defaultFullScreen by lazy {
             CaptureParameters(
                 type = FullScreen(displayId = content.displayId),
-                component = defaultComponent,
+                contentTask = TaskReference(-1, defaultComponent, defaultOwner, Rect()),
                 owner = defaultOwner,
             )
         }
@@ -70,32 +68,47 @@
             } ?: return defaultFullScreen
 
         Log.d(TAG, "topRootTask: $topRootTask")
-        val rootTaskOwners = topRootTask.childTaskUserIds.distinct()
 
-        // Special case: Only WORK in top root task which is full-screen or maximized freeform
+        // When:
+        // * there is one or more child task
+        // * all owned by the same user
+        // * this user is a work profile
+        // * the root task is fullscreen or freeform-maximized
+        //
+        // Then:
+        // the result will be a task snapshot instead of a full screen capture. If there is more
+        // than one child task, the root task will be snapshot to include any/all child tasks. This
+        // is intended to cover split-screen mode.
+        val rootTaskOwners = topRootTask.childTaskUserIds.distinct()
         if (
             rootTaskOwners.size == 1 &&
                 profileTypes.getProfileType(rootTaskOwners.single()) == WORK &&
                 (topRootTask.isFullScreen() || topRootTask.isMaximizedFreeform())
         ) {
+            val topChildTask = topRootTask.childTasksTopDown().first()
+
+            // If there is more than one task, capture the parent to include both.
             val type =
                 if (topRootTask.childTaskCount() > 1) {
-                    RootTask(
-                        parentTaskId = topRootTask.taskId,
-                        taskBounds = topRootTask.bounds,
-                        childTaskIds = topRootTask.childTasksTopDown().map { it.id }.toList(),
-                    )
+                    IsolatedTask(taskId = topRootTask.taskId, taskBounds = topRootTask.bounds)
                 } else {
-                    IsolatedTask(
-                        taskId = topRootTask.childTasksTopDown().first().id,
-                        taskBounds = topRootTask.bounds,
-                    )
+                    // Otherwise capture the single task, and use its bounds.
+                    IsolatedTask(taskId = topChildTask.id, taskBounds = topChildTask.bounds)
                 }
-            // Capture the RootTask (and all children)
+
+            // The content task (the focus of the screenshot) must represent a single task
+            // containing an activity, so always reference the top child task here. The owner
+            // of the screenshot here is always the same as well.
             return CaptureParameters(
                 type = type,
-                component = topRootTask.topActivity,
-                owner = UserHandle.of(rootTaskOwners.single()),
+                contentTask =
+                    TaskReference(
+                        taskId = topChildTask.id,
+                        component = topRootTask.topActivity ?: defaultComponent,
+                        owner = UserHandle.of(topChildTask.userId),
+                        bounds = topChildTask.bounds,
+                    ),
+                owner = UserHandle.of(topChildTask.userId),
             )
         }
 
@@ -105,26 +118,36 @@
         val visibleChildTasks =
             content.rootTasks.filter { it.isVisible }.flatMap { it.childTasksTopDown() }
 
+        // Don't target a PIP window as the screenshot "content", it should only be used
+        // to determine ownership (above).
+        val contentTask =
+            content.rootTasks
+                .filter {
+                    it.isVisible && it.windowingMode != WindowConfiguration.WINDOWING_MODE_PINNED
+                }
+                .flatMap { it.childTasksTopDown() }
+                .first()
+
         val allVisibleProfileTypes =
             visibleChildTasks
                 .map { it.userId }
                 .distinct()
                 .associate { profileTypes.getProfileType(it) to UserHandle.of(it) }
 
-        // If any visible content belongs to the private profile user -> private profile
-        // otherwise the personal user (including partial screen work content).
-        val ownerHandle =
-            allVisibleProfileTypes[PRIVATE]
-                ?: allVisibleProfileTypes[ProfileType.NONE]
-                ?: defaultOwner
-
-        // Attribute to the component of top-most task owned by this user (or fallback to default)
-        val topComponent =
-            visibleChildTasks.firstOrNull { it.userId == ownerHandle.identifier }?.componentName
+        // If any task is visible and owned by a PRIVATE profile user, the screenshot is assigned
+        // to that user. Work profile has been handled above so it is not considered here. Fallback
+        // to the default user which is the primary "current" user ('aka' personal "profile").
+        val ownerHandle = allVisibleProfileTypes[PRIVATE] ?: defaultOwner
 
         return CaptureParameters(
             type = FullScreen(content.displayId),
-            component = topComponent ?: topRootTask.topActivity ?: defaultComponent,
+            contentTask =
+                TaskReference(
+                    taskId = contentTask.id,
+                    component = contentTask.componentName,
+                    owner = UserHandle.of(contentTask.userId),
+                    bounds = contentTask.bounds,
+                ),
             owner = ownerHandle,
         )
     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/TaskReference.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/TaskReference.kt
new file mode 100644
index 0000000..04f5b1e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/TaskReference.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 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.screenshot.policy
+
+import android.content.ComponentName
+import android.graphics.Rect
+import android.os.UserHandle
+
+data class TaskReference(
+    /** The id of the task. */
+    val taskId: Int,
+    /** The component name of the task. */
+    val component: ComponentName?,
+    /** The owner of the task. */
+    val owner: UserHandle,
+    /** The bounds of the task. */
+    val bounds: Rect,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
index cf90c0a..109c1cb 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
@@ -68,7 +68,7 @@
         return PolicyResult.Matched(
             policy = NAME,
             reason = WORK_TASK_IS_TOP,
-            CaptureParameters(
+            LegacyCaptureParameters(
                 type = IsolatedTask(taskId = childTask.id, taskBounds = childTask.bounds),
                 component = childTask.componentName ?: rootTask.topActivity,
                 owner = UserHandle.of(childTask.userId),
diff --git a/packages/SystemUI/src/com/android/systemui/settings/SecureSettingsRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/settings/SecureSettingsRepositoryModule.kt
deleted file mode 100644
index 6199a83..0000000
--- a/packages/SystemUI/src/com/android/systemui/settings/SecureSettingsRepositoryModule.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2024 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.settings
-
-import android.content.ContentResolver
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
-import com.android.systemui.shared.settings.data.repository.SecureSettingsRepositoryImpl
-import dagger.Module
-import dagger.Provides
-import kotlinx.coroutines.CoroutineDispatcher
-
-@Module
-object SecureSettingsRepositoryModule {
-    @JvmStatic
-    @Provides
-    @SysUISingleton
-    fun provideSecureSettingsRepository(
-        contentResolver: ContentResolver,
-        @Background backgroundDispatcher: CoroutineDispatcher,
-    ): SecureSettingsRepository =
-        SecureSettingsRepositoryImpl(contentResolver, backgroundDispatcher)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/SystemSettingsRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/settings/SystemSettingsRepositoryModule.kt
deleted file mode 100644
index 02ce74a..0000000
--- a/packages/SystemUI/src/com/android/systemui/settings/SystemSettingsRepositoryModule.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2024 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.settings
-
-import android.content.ContentResolver
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.shared.settings.data.repository.SystemSettingsRepository
-import com.android.systemui.shared.settings.data.repository.SystemSettingsRepositoryImpl
-import dagger.Module
-import dagger.Provides
-import kotlinx.coroutines.CoroutineDispatcher
-
-@Module
-object SystemSettingsRepositoryModule {
-    @JvmStatic
-    @Provides
-    @SysUISingleton
-    fun provideSystemSettingsRepository(
-        contentResolver: ContentResolver,
-        @Background backgroundDispatcher: CoroutineDispatcher,
-    ): SystemSettingsRepository =
-        SystemSettingsRepositoryImpl(contentResolver, backgroundDispatcher)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserSettingsRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/settings/UserSettingsRepositoryModule.kt
new file mode 100644
index 0000000..3d7b2ea
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserSettingsRepositoryModule.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2024 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.settings
+
+import android.content.ContentResolver
+import com.android.systemui.Flags
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
+import com.android.systemui.shared.settings.data.repository.SecureSettingsRepositoryImpl
+import com.android.systemui.shared.settings.data.repository.SystemSettingsRepository
+import com.android.systemui.shared.settings.data.repository.SystemSettingsRepositoryImpl
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SystemSettings
+import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository
+import com.android.systemui.util.settings.repository.UserAwareSystemSettingsRepository
+import dagger.Lazy
+import dagger.Module
+import dagger.Provides
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineDispatcher
+
+@Module
+object UserSettingsRepositoryModule {
+    @JvmStatic
+    @Provides
+    @SysUISingleton
+    fun provideSecureSettingsRepository(
+        secureSettings: Lazy<SecureSettings>,
+        userRepository: Lazy<UserRepository>,
+        contentResolver: Lazy<ContentResolver>,
+        @Background backgroundDispatcher: CoroutineDispatcher,
+        @Background backgroundContext: CoroutineContext,
+    ): SecureSettingsRepository {
+        return if (Flags.userAwareSettingsRepositories()) {
+            UserAwareSecureSettingsRepository(
+                secureSettings.get(),
+                userRepository.get(),
+                backgroundDispatcher,
+                backgroundContext,
+            )
+        } else {
+            SecureSettingsRepositoryImpl(contentResolver.get(), backgroundDispatcher)
+        }
+    }
+
+    @JvmStatic
+    @Provides
+    @SysUISingleton
+    fun provideSystemSettingsRepository(
+        systemSettings: Lazy<SystemSettings>,
+        userRepository: Lazy<UserRepository>,
+        contentResolver: Lazy<ContentResolver>,
+        @Background backgroundDispatcher: CoroutineDispatcher,
+        @Background backgroundContext: CoroutineContext,
+    ): SystemSettingsRepository {
+        return if (Flags.userAwareSettingsRepositories()) {
+            UserAwareSystemSettingsRepository(
+                systemSettings.get(),
+                userRepository.get(),
+                backgroundDispatcher,
+                backgroundContext,
+            )
+        } else {
+            SystemSettingsRepositoryImpl(contentResolver.get(), backgroundDispatcher)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
index 6e63446..1776a2c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
@@ -18,14 +18,14 @@
 
 import android.content.Context
 import android.view.ViewGroup
-import com.android.systemui.res.R
 import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.StatusBarState.SHADE
-import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED
+import com.android.systemui.res.R
 import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator
 import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.END
 import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.START
 import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate
+import com.android.systemui.statusbar.StatusBarState.SHADE
+import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED
 import com.android.systemui.unfold.SysUIUnfoldScope
 import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
 import javax.inject.Inject
@@ -39,8 +39,10 @@
     progressProvider: NaturalRotationUnfoldProgressProvider,
 ) {
 
-    private val filterShade: () -> Boolean = { statusBarStateController.getState() == SHADE ||
-        statusBarStateController.getState() == SHADE_LOCKED }
+    private val filterShade: () -> Boolean = {
+        statusBarStateController.getState() == SHADE ||
+            statusBarStateController.getState() == SHADE_LOCKED
+    }
 
     private val translateAnimator by lazy {
         UnfoldConstantTranslateAnimator(
@@ -48,21 +50,23 @@
                 setOf(
                     ViewIdToTranslate(R.id.quick_settings_panel, START, filterShade),
                     ViewIdToTranslate(R.id.qs_footer_actions, START, filterShade),
-                    ViewIdToTranslate(R.id.notification_stack_scroller, END, filterShade)),
-            progressProvider = progressProvider)
+                    ViewIdToTranslate(R.id.notification_stack_scroller, END, filterShade),
+                ),
+            progressProvider = progressProvider,
+        )
     }
 
     private val translateAnimatorStatusBar by lazy {
         UnfoldConstantTranslateAnimator(
             viewsIdToTranslate =
-            setOf(
-                ViewIdToTranslate(R.id.shade_header_system_icons, END, filterShade),
-                ViewIdToTranslate(R.id.privacy_container, END, filterShade),
-                ViewIdToTranslate(R.id.carrier_group, END, filterShade),
-                ViewIdToTranslate(R.id.clock, START, filterShade),
-                ViewIdToTranslate(R.id.date, START, filterShade)
-            ),
-            progressProvider = progressProvider
+                setOf(
+                    ViewIdToTranslate(R.id.shade_header_system_icons, END, filterShade),
+                    ViewIdToTranslate(R.id.privacy_container, END, filterShade),
+                    ViewIdToTranslate(R.id.carrier_group, END, filterShade),
+                    ViewIdToTranslate(R.id.clock, START, filterShade),
+                    ViewIdToTranslate(R.id.date, START, filterShade),
+                ),
+            progressProvider = progressProvider,
         )
     }
 
@@ -73,10 +77,7 @@
         val splitShadeStatusBarViewGroup: ViewGroup? =
             root.findViewById(R.id.split_shade_status_bar)
         if (splitShadeStatusBarViewGroup != null) {
-            translateAnimatorStatusBar.init(
-                splitShadeStatusBarViewGroup,
-                translationMax
-            )
+            translateAnimatorStatusBar.init(splitShadeStatusBarViewGroup, translationMax)
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 0e82bf8..c15c8f9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -2053,6 +2053,9 @@
         }
         if (mQsController.getExpanded()) {
             mQsController.flingQs(0, FLING_COLLAPSE);
+        } else if (mBarState == KEYGUARD) {
+            mLockscreenShadeTransitionController.goToLockedShade(
+                    /* expandedView= */null, /* needsQSAnimation= */false);
         } else {
             expand(true /* animate */);
         }
@@ -3109,7 +3112,7 @@
         if (isTracking()) {
             onTrackingStopped(true);
         }
-        if (isExpanded() && !mQsController.getExpanded()) {
+        if (isExpanded() && mBarState != KEYGUARD && !mQsController.getExpanded()) {
             mShadeLog.d("Status Bar was long pressed. Expanding to QS.");
             expandToQs();
         } else {
@@ -5091,13 +5094,6 @@
             }
             boolean handled = mHeadsUpTouchHelper.onTouchEvent(event);
 
-            if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && mQsController.handleTouch(
-                    event, isFullyCollapsed(), isShadeOrQsHeightAnimationRunning())) {
-                if (event.getActionMasked() != MotionEvent.ACTION_MOVE) {
-                    mShadeLog.logMotionEvent(event, "onTouch: handleQsTouch handled event");
-                }
-                return true;
-            }
             // This touch session has already resulted in shade expansion. Ignore everything else.
             if (ShadeExpandsOnStatusBarLongPress.isEnabled()
                     && event.getActionMasked() != MotionEvent.ACTION_DOWN
@@ -5105,6 +5101,13 @@
                 mShadeLog.d("Touch has same down time as Status Bar long press. Ignoring.");
                 return false;
             }
+            if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && mQsController.handleTouch(
+                    event, isFullyCollapsed(), isShadeOrQsHeightAnimationRunning())) {
+                if (event.getActionMasked() != MotionEvent.ACTION_MOVE) {
+                    mShadeLog.logMotionEvent(event, "onTouch: handleQsTouch handled event");
+                }
+                return true;
+            }
             if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) {
                 mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
                 handled = true;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
index a8026cd..42d4eff 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
@@ -20,7 +20,13 @@
 import android.content.res.Resources
 import android.view.LayoutInflater
 import android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
+import com.android.systemui.common.ui.ConfigurationState
+import com.android.systemui.common.ui.ConfigurationStateImpl
 import com.android.systemui.common.ui.GlobalConfig
+import com.android.systemui.common.ui.data.repository.ConfigurationRepository
+import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImpl
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.res.R
 import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
@@ -92,9 +98,55 @@
     @ShadeDisplayAware
     @SysUISingleton
     fun provideShadeWindowConfigurationForwarder(
-        @ShadeDisplayAware shadeConfigurationController: ConfigurationController,
+        @ShadeDisplayAware shadeConfigurationController: ConfigurationController
     ): ConfigurationForwarder {
         ShadeWindowGoesAround.isUnexpectedlyInLegacyMode()
         return shadeConfigurationController
     }
+
+    @SysUISingleton
+    @Provides
+    @ShadeDisplayAware
+    fun provideShadeDisplayAwareConfigurationState(
+        factory: ConfigurationStateImpl.Factory,
+        @ShadeDisplayAware configurationController: ConfigurationController,
+        @ShadeDisplayAware context: Context,
+        @GlobalConfig configurationState: ConfigurationState,
+    ): ConfigurationState {
+        return if (ShadeWindowGoesAround.isEnabled) {
+            factory.create(context, configurationController)
+        } else {
+            configurationState
+        }
+    }
+
+    @SysUISingleton
+    @Provides
+    @ShadeDisplayAware
+    fun provideShadeDisplayAwareConfigurationRepository(
+        factory: ConfigurationRepositoryImpl.Factory,
+        @ShadeDisplayAware configurationController: ConfigurationController,
+        @ShadeDisplayAware context: Context,
+        @GlobalConfig globalConfigurationRepository: ConfigurationRepository,
+    ): ConfigurationRepository {
+        return if (ShadeWindowGoesAround.isEnabled) {
+            factory.create(context, configurationController)
+        } else {
+            globalConfigurationRepository
+        }
+    }
+
+    @SysUISingleton
+    @Provides
+    @ShadeDisplayAware
+    fun provideShadeAwareConfigurationInteractor(
+        @ShadeDisplayAware configurationRepository: ConfigurationRepository,
+        @GlobalConfig configurationInteractor: ConfigurationInteractor,
+    ): ConfigurationInteractor {
+        return if (ShadeWindowGoesAround.isEnabled) {
+            ConfigurationInteractorImpl(configurationRepository)
+        } else {
+            configurationInteractor
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index 72a4650..2348a11 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -49,10 +49,7 @@
 import javax.inject.Provider
 
 /** Module for classes related to the notification shade. */
-@Module(
-    includes =
-        [StartShadeModule::class, ShadeViewProviderModule::class]
-)
+@Module(includes = [StartShadeModule::class, ShadeViewProviderModule::class])
 abstract class ShadeModule {
     companion object {
         @Provides
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LongPressGestureDetector.kt b/packages/SystemUI/src/com/android/systemui/shade/StatusBarLongPressGestureDetector.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/shade/LongPressGestureDetector.kt
rename to packages/SystemUI/src/com/android/systemui/shade/StatusBarLongPressGestureDetector.kt
index 6fb3ca5..ae36e81 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LongPressGestureDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/StatusBarLongPressGestureDetector.kt
@@ -25,7 +25,7 @@
 
 /** Accepts touch events, detects long press, and calls ShadeViewController#onStatusBarLongPress. */
 @SysUISingleton
-class LongPressGestureDetector
+class StatusBarLongPressGestureDetector
 @Inject
 constructor(context: Context, val shadeViewController: ShadeViewController) {
     val gestureDetector =
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
index 5629938..ef62d2d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
@@ -15,9 +15,7 @@
  */
 package com.android.systemui.shade.data.repository
 
-import android.content.Context
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
 import javax.inject.Inject
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -182,8 +180,7 @@
 
 /** Business logic for shade interactions */
 @SysUISingleton
-class ShadeRepositoryImpl @Inject constructor() :
-    ShadeRepository {
+class ShadeRepositoryImpl @Inject constructor() : ShadeRepository {
     private val _qsExpansion = MutableStateFlow(0f)
     @Deprecated("Use ShadeInteractor.qsExpansion instead")
     override val qsExpansion: StateFlow<Float> = _qsExpansion.asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
index e5d08a0..44f2911 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.shade.domain.startable
 
 import android.content.Context
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.CoreStartable
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
 import com.android.systemui.common.ui.data.repository.ConfigurationRepository
@@ -42,7 +43,6 @@
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onStart
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 @SysUISingleton
 class ShadeStartable
@@ -51,7 +51,7 @@
     @Application private val applicationScope: CoroutineScope,
     @ShadeDisplayAware private val context: Context,
     @ShadeTouchLog private val touchLog: LogBuffer,
-    private val configurationRepository: ConfigurationRepository,
+    @ShadeDisplayAware private val configurationRepository: ConfigurationRepository,
     private val shadeRepository: ShadeRepository,
     private val splitShadeStateController: SplitShadeStateController,
     private val scrimShadeTransitionController: ScrimShadeTransitionController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 520cbf9..8c5a711 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -619,10 +619,11 @@
     }
 
     private void updateLockScreenUserLockedMsg(int userId) {
-        boolean userUnlocked = mKeyguardUpdateMonitor.isUserUnlocked(userId);
+        boolean userStorageUnlocked = mKeyguardUpdateMonitor.isUserUnlocked(userId);
         boolean encryptedOrLockdown = mKeyguardUpdateMonitor.isEncryptedOrLockdown(userId);
-        mKeyguardLogger.logUpdateLockScreenUserLockedMsg(userId, userUnlocked, encryptedOrLockdown);
-        if (!userUnlocked || encryptedOrLockdown) {
+        mKeyguardLogger.logUpdateLockScreenUserLockedMsg(userId, userStorageUnlocked,
+                encryptedOrLockdown);
+        if (!userStorageUnlocked || encryptedOrLockdown) {
             mRotateTextViewController.updateIndication(
                     INDICATION_TYPE_USER_LOCKED,
                     new KeyguardIndication.Builder()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
index 3abbc6e..f441fd6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener
 import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewUpdatedListener
+import com.android.systemui.statusbar.data.repository.StatusBarModePerDisplayRepository
 import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions
 import com.android.systemui.statusbar.phone.PhoneStatusBarView
 import com.android.systemui.statusbar.phone.PhoneStatusBarViewController
@@ -71,7 +72,10 @@
     }
 
     interface Factory {
-        fun create(statusBarWindowController: StatusBarWindowController): StatusBarInitializer
+        fun create(
+            statusBarWindowController: StatusBarWindowController,
+            statusBarModePerDisplayRepository: StatusBarModePerDisplayRepository,
+        ): StatusBarInitializer
     }
 }
 
@@ -79,6 +83,7 @@
 @AssistedInject
 constructor(
     @Assisted private val statusBarWindowController: StatusBarWindowController,
+    @Assisted private val statusBarModePerDisplayRepository: StatusBarModePerDisplayRepository,
     private val collapsedStatusBarFragmentProvider: Provider<CollapsedStatusBarFragment>,
     private val statusBarRootFactory: StatusBarRootFactory,
     private val componentFactory: HomeStatusBarComponent.Factory,
@@ -127,7 +132,7 @@
                 val phoneStatusBarView = cv.findViewById<PhoneStatusBarView>(R.id.status_bar)
                 component =
                     componentFactory.create(phoneStatusBarView).also { component ->
-                        // CollapsedStatusBarFragment used to be responsible initializting
+                        // CollapsedStatusBarFragment used to be responsible initializing
                         component.init()
 
                         statusBarViewUpdatedListener?.onStatusBarViewUpdated(
@@ -135,8 +140,12 @@
                             component.phoneStatusBarTransitions,
                         )
 
-                        creationListeners.forEach { listener ->
-                            listener.onStatusBarViewInitialized(component)
+                        if (StatusBarConnectedDisplays.isEnabled) {
+                            statusBarModePerDisplayRepository.onStatusBarViewInitialized(component)
+                        } else {
+                            creationListeners.forEach { listener ->
+                                listener.onStatusBarViewInitialized(component)
+                            }
                         }
                     }
             }
@@ -184,7 +193,8 @@
     @AssistedFactory
     interface Factory : StatusBarInitializer.Factory {
         override fun create(
-            statusBarWindowController: StatusBarWindowController
+            statusBarWindowController: StatusBarWindowController,
+            statusBarModePerDisplayRepository: StatusBarModePerDisplayRepository,
         ): StatusBarInitializerImpl
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt
index 041f0b0..4f815c1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.display.data.repository.PerDisplayStore
 import com.android.systemui.display.data.repository.PerDisplayStoreImpl
 import com.android.systemui.display.data.repository.SingleDisplayStore
+import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
 import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -37,6 +38,7 @@
     displayRepository: DisplayRepository,
     private val factory: StatusBarInitializer.Factory,
     private val statusBarWindowControllerStore: StatusBarWindowControllerStore,
+    private val statusBarModeRepositoryStore: StatusBarModeRepositoryStore,
 ) :
     StatusBarInitializerStore,
     PerDisplayStoreImpl<StatusBarInitializer>(backgroundApplicationScope, displayRepository) {
@@ -47,7 +49,8 @@
 
     override fun createInstanceForDisplay(displayId: Int): StatusBarInitializer {
         return factory.create(
-            statusBarWindowController = statusBarWindowControllerStore.forDisplay(displayId)
+            statusBarWindowController = statusBarWindowControllerStore.forDisplay(displayId),
+            statusBarModePerDisplayRepository = statusBarModeRepositoryStore.forDisplay(displayId),
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
index f65ae67..4341200 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.log.LogBufferFactory
 import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
 import com.android.systemui.statusbar.data.StatusBarDataLayerModule
+import com.android.systemui.statusbar.data.repository.LightBarControllerStore
 import com.android.systemui.statusbar.phone.LightBarController
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProviderImpl
@@ -55,26 +56,26 @@
  *   [com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule], etc.).
  */
 @Module(includes = [StatusBarDataLayerModule::class, SystemBarUtilsProxyImpl.Module::class])
-abstract class StatusBarModule {
+interface StatusBarModule {
 
     @Binds
     @IntoMap
     @ClassKey(OngoingCallController::class)
-    abstract fun bindOngoingCallController(impl: OngoingCallController): CoreStartable
+    fun bindOngoingCallController(impl: OngoingCallController): CoreStartable
 
     @Binds
     @IntoMap
     @ClassKey(LightBarController::class)
-    abstract fun bindLightBarController(impl: LightBarController): CoreStartable
+    fun lightBarControllerAsCoreStartable(controller: LightBarController): CoreStartable
 
     @Binds
     @IntoMap
     @ClassKey(StatusBarSignalPolicy::class)
-    abstract fun bindStatusBarSignalPolicy(impl: StatusBarSignalPolicy): CoreStartable
+    fun bindStatusBarSignalPolicy(impl: StatusBarSignalPolicy): CoreStartable
 
     @Binds
     @SysUISingleton
-    abstract fun statusBarWindowControllerFactory(
+    fun statusBarWindowControllerFactory(
         implFactory: StatusBarWindowControllerImpl.Factory
     ): StatusBarWindowController.Factory
 
@@ -82,6 +83,12 @@
 
         @Provides
         @SysUISingleton
+        fun lightBarController(store: LightBarControllerStore): LightBarController {
+            return store.defaultDisplay
+        }
+
+        @Provides
+        @SysUISingleton
         fun windowControllerStore(
             multiDisplayImplLazy: Lazy<MultiDisplayStatusBarWindowControllerStore>,
             singleDisplayImplLazy: Lazy<SingleDisplayStatusBarWindowControllerStore>,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt
index f2d926f..39de28e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt
@@ -16,6 +16,7 @@
 package com.android.systemui.statusbar.data
 
 import com.android.systemui.statusbar.data.repository.KeyguardStatusBarRepositoryModule
+import com.android.systemui.statusbar.data.repository.LightBarControllerStoreModule
 import com.android.systemui.statusbar.data.repository.RemoteInputRepositoryModule
 import com.android.systemui.statusbar.data.repository.StatusBarConfigurationControllerModule
 import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStoreModule
@@ -28,6 +29,7 @@
     includes =
         [
             KeyguardStatusBarRepositoryModule::class,
+            LightBarControllerStoreModule::class,
             RemoteInputRepositoryModule::class,
             StatusBarConfigurationControllerModule::class,
             StatusBarContentInsetsProviderStoreModule::class,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/LightBarControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/LightBarControllerStore.kt
new file mode 100644
index 0000000..ff50e31
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/LightBarControllerStore.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 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.data.repository
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.data.repository.DisplayScopeRepository
+import com.android.systemui.display.data.repository.PerDisplayStore
+import com.android.systemui.display.data.repository.PerDisplayStoreImpl
+import com.android.systemui.statusbar.phone.LightBarController
+import com.android.systemui.statusbar.phone.LightBarControllerImpl
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+
+/** Provides per display instances of [LightBarController]. */
+interface LightBarControllerStore : PerDisplayStore<LightBarController>
+
+@SysUISingleton
+class LightBarControllerStoreImpl
+@Inject
+constructor(
+    @Background backgroundApplicationScope: CoroutineScope,
+    displayRepository: DisplayRepository,
+    private val factory: LightBarControllerImpl.Factory,
+    private val displayScopeRepository: DisplayScopeRepository,
+    private val statusBarModeRepositoryStore: StatusBarModeRepositoryStore,
+) :
+    LightBarControllerStore,
+    PerDisplayStoreImpl<LightBarController>(backgroundApplicationScope, displayRepository) {
+
+    override fun createInstanceForDisplay(displayId: Int): LightBarController {
+        return factory
+            .create(
+                displayId,
+                displayScopeRepository.scopeForDisplay(displayId),
+                statusBarModeRepositoryStore.forDisplay(displayId),
+            )
+            .also { it.start() }
+    }
+
+    override suspend fun onDisplayRemovalAction(instance: LightBarController) {
+        instance.stop()
+    }
+
+    override val instanceClass = LightBarController::class.java
+}
+
+@Module
+interface LightBarControllerStoreModule {
+
+    @Binds fun store(impl: LightBarControllerStoreImpl): LightBarControllerStore
+
+    @Binds
+    @IntoMap
+    @ClassKey(LightBarControllerStore::class)
+    fun storeAsCoreStartable(impl: LightBarControllerStoreImpl): CoreStartable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt
index 44bee1d..cc91e2d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt
@@ -27,7 +27,7 @@
 import android.view.WindowInsetsController.Appearance
 import com.android.internal.statusbar.LetterboxDetails
 import com.android.internal.view.AppearanceRegion
-import com.android.systemui.Dumpable
+import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener
@@ -59,7 +59,7 @@
  * Note: These status bar modes are status bar *window* states that are sent to us from
  * WindowManager, not determined internally.
  */
-interface StatusBarModePerDisplayRepository {
+interface StatusBarModePerDisplayRepository : OnStatusBarViewInitializedListener, CoreStartable {
     /**
      * True if the status bar window is showing transiently and will disappear soon, and false
      * otherwise. ("Otherwise" in this case means the status bar is persistently hidden OR
@@ -104,6 +104,12 @@
      *   determined internally instead.
      */
     fun clearTransient()
+
+    /**
+     * Called when the [StatusBarModePerDisplayRepository] should stop doing any work and clean up
+     * if needed.
+     */
+    fun stop()
 }
 
 class StatusBarModePerDisplayRepositoryImpl
@@ -114,7 +120,7 @@
     private val commandQueue: CommandQueue,
     private val letterboxAppearanceCalculator: LetterboxAppearanceCalculator,
     ongoingCallRepository: OngoingCallRepository,
-) : StatusBarModePerDisplayRepository, OnStatusBarViewInitializedListener, Dumpable {
+) : StatusBarModePerDisplayRepository {
 
     private val commandQueueCallback =
         object : CommandQueue.Callbacks {
@@ -163,10 +169,14 @@
             }
         }
 
-    fun start() {
+    override fun start() {
         commandQueue.addCallback(commandQueueCallback)
     }
 
+    override fun stop() {
+        commandQueue.removeCallback(commandQueueCallback)
+    }
+
     private val _isTransientShown = MutableStateFlow(false)
     override val isTransientShown: StateFlow<Boolean> = _isTransientShown.asStateFlow()
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt
index 2c9fa25..143e998 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt
@@ -18,21 +18,54 @@
 
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.DisplayId
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.data.repository.PerDisplayStore
+import com.android.systemui.display.data.repository.PerDisplayStoreImpl
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
 import com.android.systemui.statusbar.core.StatusBarInitializer
 import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent
 import dagger.Binds
+import dagger.Lazy
 import dagger.Module
+import dagger.Provides
 import dagger.multibindings.ClassKey
 import dagger.multibindings.IntoMap
 import dagger.multibindings.IntoSet
 import java.io.PrintWriter
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
 
-interface StatusBarModeRepositoryStore {
-    val defaultDisplay: StatusBarModePerDisplayRepository
+interface StatusBarModeRepositoryStore : PerDisplayStore<StatusBarModePerDisplayRepository>
 
-    fun forDisplay(displayId: Int): StatusBarModePerDisplayRepository
+@SysUISingleton
+class MultiDisplayStatusBarModeRepositoryStore
+@Inject
+constructor(
+    @Background backgroundApplicationScope: CoroutineScope,
+    private val factory: StatusBarModePerDisplayRepositoryFactory,
+    displayRepository: DisplayRepository,
+) :
+    StatusBarModeRepositoryStore,
+    PerDisplayStoreImpl<StatusBarModePerDisplayRepository>(
+        backgroundApplicationScope,
+        displayRepository,
+    ) {
+
+    init {
+        StatusBarConnectedDisplays.assertInNewMode()
+    }
+
+    override fun createInstanceForDisplay(displayId: Int): StatusBarModePerDisplayRepository {
+        return factory.create(displayId).also { it.start() }
+    }
+
+    override suspend fun onDisplayRemovalAction(instance: StatusBarModePerDisplayRepository) {
+        instance.stop()
+    }
+
+    override val instanceClass = StatusBarModePerDisplayRepository::class.java
 }
 
 @SysUISingleton
@@ -47,10 +80,7 @@
     StatusBarInitializer.OnStatusBarViewInitializedListener {
     override val defaultDisplay = factory.create(displayId)
 
-    override fun forDisplay(displayId: Int) =
-        // TODO(b/369337087): implement per display status bar modes.
-        //  For now just use default display instance.
-        defaultDisplay
+    override fun forDisplay(displayId: Int) = defaultDisplay
 
     override fun start() {
         defaultDisplay.start()
@@ -66,17 +96,40 @@
 }
 
 @Module
-interface StatusBarModeRepositoryModule {
-    @Binds fun bindImpl(impl: StatusBarModeRepositoryImpl): StatusBarModeRepositoryStore
-
-    @Binds
-    @IntoMap
-    @ClassKey(StatusBarModeRepositoryStore::class)
-    fun bindCoreStartable(impl: StatusBarModeRepositoryImpl): CoreStartable
-
+abstract class StatusBarModeRepositoryModule {
     @Binds
     @IntoSet
-    fun bindViewInitListener(
+    abstract fun bindViewInitListener(
         impl: StatusBarModeRepositoryImpl
     ): StatusBarInitializer.OnStatusBarViewInitializedListener
+
+    companion object {
+        @Provides
+        @SysUISingleton
+        @IntoMap
+        @ClassKey(StatusBarModeRepositoryStore::class)
+        fun storeAsCoreStartable(
+            singleDisplayLazy: Lazy<StatusBarModeRepositoryImpl>,
+            multiDisplayLazy: Lazy<MultiDisplayStatusBarModeRepositoryStore>,
+        ): CoreStartable {
+            return if (StatusBarConnectedDisplays.isEnabled) {
+                multiDisplayLazy.get()
+            } else {
+                singleDisplayLazy.get()
+            }
+        }
+
+        @Provides
+        @SysUISingleton
+        fun store(
+            singleDisplayLazy: Lazy<StatusBarModeRepositoryImpl>,
+            multiDisplayLazy: Lazy<MultiDisplayStatusBarModeRepositoryStore>,
+        ): StatusBarModeRepositoryStore {
+            return if (StatusBarConnectedDisplays.isEnabled) {
+                multiDisplayLazy.get()
+            } else {
+                singleDisplayLazy.get()
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
index 77ec65b..9a779300 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
@@ -49,12 +49,12 @@
 @Inject
 constructor(
     private val launcherApps: LauncherApps,
-    private val conversationNotificationManager: ConversationNotificationManager
+    private val conversationNotificationManager: ConversationNotificationManager,
 ) {
     fun processNotification(
         entry: NotificationEntry,
         recoveredBuilder: Notification.Builder,
-        logger: NotificationRowContentBinderLogger
+        logger: NotificationRowContentBinderLogger,
     ): Notification.MessagingStyle? {
         val messagingStyle = recoveredBuilder.style as? Notification.MessagingStyle ?: return null
         messagingStyle.conversationType =
@@ -83,7 +83,7 @@
     private val notifCollection: CommonNotifCollection,
     private val bindEventManager: BindEventManager,
     private val headsUpManager: HeadsUpManager,
-    private val statusBarStateController: StatusBarStateController
+    private val statusBarStateController: StatusBarStateController,
 ) {
 
     private var isStatusBarExpanded = false
@@ -118,7 +118,8 @@
             .flatMap { layout -> layout.allViews.asSequence() }
             .flatMap { view ->
                 (view as? ConversationLayout)?.messagingGroups?.asSequence()
-                    ?: (view as? MessagingLayout)?.messagingGroups?.asSequence() ?: emptySequence()
+                    ?: (view as? MessagingLayout)?.messagingGroups?.asSequence()
+                    ?: emptySequence()
             }
             .flatMap { messagingGroup -> messagingGroup.messageContainer.children }
             .mapNotNull { view ->
@@ -144,7 +145,7 @@
     bindEventManager: BindEventManager,
     @ShadeDisplayAware private val context: Context,
     private val notifCollection: CommonNotifCollection,
-    @Main private val mainHandler: Handler
+    @Main private val mainHandler: Handler,
 ) {
     // Need this state to be thread safe, since it's accessed from the ui thread
     // (NotificationEntryListener) and a bg thread (NotificationRowContentBinder)
@@ -175,7 +176,7 @@
                             // the notif has been moved in the shade
                             mainHandler.postDelayed(
                                 { layout.setIsImportantConversation(important, true) },
-                                IMPORTANCE_ANIMATION_DELAY.toLong()
+                                IMPORTANCE_ANIMATION_DELAY.toLong(),
                             )
                         } else {
                             layout.setIsImportantConversation(important, false)
@@ -233,8 +234,7 @@
                     state?.run {
                         if (shouldIncrementUnread(recoveredBuilder)) unreadCount + 1
                         else unreadCount
-                    }
-                        ?: 1
+                    } ?: 1
                 ConversationState(newCount, entry.sbn.notification)
             }!!
             .unreadCount
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt
index de868d4..df8e56e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt
@@ -33,29 +33,30 @@
  * they are fully attached.
  */
 @CoordinatorScope
-class RowAppearanceCoordinator @Inject internal constructor(
+class RowAppearanceCoordinator
+@Inject
+internal constructor(
     @ShadeDisplayAware context: Context,
     private var mAssistantFeedbackController: AssistantFeedbackController,
-    private var mSectionStyleProvider: SectionStyleProvider
+    private var mSectionStyleProvider: SectionStyleProvider,
 ) : Coordinator {
 
     private var entryToExpand: NotificationEntry? = null
 
     /**
-     * `true` if notifications not part of a group should by default be rendered in their
-     * expanded state. If `false`, then only the first notification will be expanded if
-     * possible.
+     * `true` if notifications not part of a group should by default be rendered in their expanded
+     * state. If `false`, then only the first notification will be expanded if possible.
      */
     private val mAlwaysExpandNonGroupedNotification =
         context.resources.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications)
 
     /**
-     * `true` if the first non-group expandable notification should be expanded automatically
-     * when possible. If `false`, then the first non-group expandable notification should not
-     * be expanded.
+     * `true` if the first non-group expandable notification should be expanded automatically when
+     * possible. If `false`, then the first non-group expandable notification should not be
+     * expanded.
      */
     private val mAutoExpandFirstNotification =
-            context.resources.getBoolean(R.bool.config_autoExpandFirstNotification)
+        context.resources.getBoolean(R.bool.config_autoExpandFirstNotification)
 
     override fun attach(pipeline: NotifPipeline) {
         pipeline.addOnBeforeRenderListListener(::onBeforeRenderList)
@@ -63,17 +64,20 @@
     }
 
     private fun onBeforeRenderList(list: List<ListEntry>) {
-        entryToExpand = list.firstOrNull()?.representativeEntry?.takeIf { entry ->
-            !mSectionStyleProvider.isMinimizedSection(entry.section!!)
-        }
+        entryToExpand =
+            list.firstOrNull()?.representativeEntry?.takeIf { entry ->
+                !mSectionStyleProvider.isMinimizedSection(entry.section!!)
+            }
     }
 
     private fun onAfterRenderEntry(entry: NotificationEntry, controller: NotifRowController) {
         // If mAlwaysExpandNonGroupedNotification is false, then only expand the
         // very first notification if it's not a child of grouped notifications and when
         // mAutoExpandFirstNotification is true.
-        controller.setSystemExpanded(mAlwaysExpandNonGroupedNotification ||
-                (mAutoExpandFirstNotification && entry == entryToExpand))
+        controller.setSystemExpanded(
+            mAlwaysExpandNonGroupedNotification ||
+                (mAutoExpandFirstNotification && entry == entryToExpand)
+        )
         // Show/hide the feedback icon
         controller.setFeedbackIcon(mAssistantFeedbackController.getFeedbackIcon(entry))
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
index db778b80..2d1eccd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
@@ -17,10 +17,12 @@
 package com.android.systemui.statusbar.notification.collection.coordinator
 
 import android.util.Log
+import com.android.app.tracing.traceSection
 import com.android.internal.widget.MessagingGroup
 import com.android.internal.widget.MessagingMessage
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener
 import com.android.systemui.statusbar.notification.ColorUpdateLogger
@@ -29,17 +31,17 @@
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.Compile
-import com.android.app.tracing.traceSection
-import com.android.systemui.shade.ShadeDisplayAware
 import javax.inject.Inject
 
 /**
- * A coordinator which ensures that notifications within the new pipeline are correctly inflated
- * for the current uiMode and screen properties; additionally deferring those changes when a user
- * change is in progress until that process has completed.
+ * A coordinator which ensures that notifications within the new pipeline are correctly inflated for
+ * the current uiMode and screen properties; additionally deferring those changes when a user change
+ * is in progress until that process has completed.
  */
 @CoordinatorScope
-class ViewConfigCoordinator @Inject internal constructor(
+class ViewConfigCoordinator
+@Inject
+internal constructor(
     @ShadeDisplayAware private val mConfigurationController: ConfigurationController,
     private val mLockscreenUserManager: NotificationLockscreenUserManager,
     private val mGutsManager: NotificationGutsManager,
@@ -52,28 +54,32 @@
     private var mDispatchUiModeChangeOnUserSwitched = false
     private var mPipeline: NotifPipeline? = null
 
-    private val mKeyguardUpdateCallback = object : KeyguardUpdateMonitorCallback() {
-        override fun onUserSwitching(userId: Int) {
-            colorUpdateLogger.logTriggerEvent("VCC.mKeyguardUpdateCallback.onUserSwitching()")
-            log { "ViewConfigCoordinator.onUserSwitching(userId=$userId)" }
-            mIsSwitchingUser = true
+    private val mKeyguardUpdateCallback =
+        object : KeyguardUpdateMonitorCallback() {
+            override fun onUserSwitching(userId: Int) {
+                colorUpdateLogger.logTriggerEvent("VCC.mKeyguardUpdateCallback.onUserSwitching()")
+                log { "ViewConfigCoordinator.onUserSwitching(userId=$userId)" }
+                mIsSwitchingUser = true
+            }
+
+            override fun onUserSwitchComplete(userId: Int) {
+                colorUpdateLogger.logTriggerEvent(
+                    "VCC.mKeyguardUpdateCallback.onUserSwitchComplete()"
+                )
+                log { "ViewConfigCoordinator.onUserSwitchComplete(userId=$userId)" }
+                mIsSwitchingUser = false
+                applyChangesOnUserSwitched()
+            }
         }
 
-        override fun onUserSwitchComplete(userId: Int) {
-            colorUpdateLogger.logTriggerEvent("VCC.mKeyguardUpdateCallback.onUserSwitchComplete()")
-            log { "ViewConfigCoordinator.onUserSwitchComplete(userId=$userId)" }
-            mIsSwitchingUser = false
-            applyChangesOnUserSwitched()
+    private val mUserChangedListener =
+        object : UserChangedListener {
+            override fun onUserChanged(userId: Int) {
+                colorUpdateLogger.logTriggerEvent("VCC.mUserChangedListener.onUserChanged()")
+                log { "ViewConfigCoordinator.onUserChanged(userId=$userId)" }
+                applyChangesOnUserSwitched()
+            }
         }
-    }
-
-    private val mUserChangedListener = object : UserChangedListener {
-        override fun onUserChanged(userId: Int) {
-            colorUpdateLogger.logTriggerEvent("VCC.mUserChangedListener.onUserChanged()")
-            log { "ViewConfigCoordinator.onUserChanged(userId=$userId)" }
-            applyChangesOnUserSwitched()
-        }
-    }
 
     override fun attach(pipeline: NotifPipeline) {
         mPipeline = pipeline
@@ -87,8 +93,8 @@
         log {
             val keyguardIsSwitchingUser = mKeyguardUpdateMonitor.isSwitchingUser
             "ViewConfigCoordinator.onDensityOrFontScaleChanged()" +
-                    " isSwitchingUser=$mIsSwitchingUser" +
-                    " keyguardUpdateMonitor.isSwitchingUser=$keyguardIsSwitchingUser"
+                " isSwitchingUser=$mIsSwitchingUser" +
+                " keyguardUpdateMonitor.isSwitchingUser=$keyguardIsSwitchingUser"
         }
         MessagingMessage.dropCache()
         MessagingGroup.dropCache()
@@ -104,8 +110,8 @@
         log {
             val keyguardIsSwitchingUser = mKeyguardUpdateMonitor.isSwitchingUser
             "ViewConfigCoordinator.onUiModeChanged()" +
-                    " isSwitchingUser=$mIsSwitchingUser" +
-                    " keyguardUpdateMonitor.isSwitchingUser=$keyguardIsSwitchingUser"
+                " isSwitchingUser=$mIsSwitchingUser" +
+                " keyguardUpdateMonitor.isSwitchingUser=$keyguardIsSwitchingUser"
         }
         if (!mIsSwitchingUser) {
             updateNotificationsOnUiModeChanged()
@@ -132,13 +138,13 @@
     }
 
     private fun updateNotificationsOnUiModeChanged() {
-        colorUpdateLogger.logEvent("VCC.updateNotificationsOnUiModeChanged()",
-                "mode=" + mConfigurationController.nightModeName)
+        colorUpdateLogger.logEvent(
+            "VCC.updateNotificationsOnUiModeChanged()",
+            "mode=" + mConfigurationController.nightModeName,
+        )
         log { "ViewConfigCoordinator.updateNotificationsOnUiModeChanged()" }
         traceSection("updateNotifOnUiModeChanged") {
-            mPipeline?.allNotifs?.forEach { entry ->
-                entry.row?.onUiModeChanged()
-            }
+            mPipeline?.allNotifs?.forEach { entry -> entry.row?.onUiModeChanged() }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
index cab4c1c..3c838e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
@@ -18,6 +18,8 @@
 
 import android.content.Context
 import android.view.View
+import com.android.app.tracing.traceSection
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
 import com.android.systemui.statusbar.notification.collection.GroupEntry
 import com.android.systemui.statusbar.notification.collection.ListEntry
@@ -26,8 +28,6 @@
 import com.android.systemui.statusbar.notification.collection.PipelineDumper
 import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
-import com.android.app.tracing.traceSection
-import com.android.systemui.shade.ShadeDisplayAware
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
@@ -36,7 +36,9 @@
  * Responsible for building and applying the "shade node spec": the list (tree) of things that
  * currently populate the notification shade.
  */
-class ShadeViewManager @AssistedInject constructor(
+class ShadeViewManager
+@AssistedInject
+constructor(
     @ShadeDisplayAware context: Context,
     @Assisted listContainer: NotificationListContainer,
     @Assisted private val stackController: NotifStackController,
@@ -45,13 +47,19 @@
     sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider,
     nodeSpecBuilderLogger: NodeSpecBuilderLogger,
     shadeViewDifferLogger: ShadeViewDifferLogger,
-    private val viewBarn: NotifViewBarn
+    private val viewBarn: NotifViewBarn,
 ) : PipelineDumpable {
     // We pass a shim view here because the listContainer may not actually have a view associated
     // with it and the differ never actually cares about the root node's view.
     private val rootController = RootNodeController(listContainer, View(context))
-    private val specBuilder = NodeSpecBuilder(mediaContainerController, featureManager,
-        sectionHeaderVisibilityProvider, viewBarn, nodeSpecBuilderLogger)
+    private val specBuilder =
+        NodeSpecBuilder(
+            mediaContainerController,
+            featureManager,
+            sectionHeaderVisibilityProvider,
+            viewBarn,
+            nodeSpecBuilderLogger,
+        )
     private val viewDiffer = ShadeViewDiffer(rootController, shadeViewDifferLogger)
 
     /** Method for attaching this manager to the pipeline. */
@@ -59,34 +67,36 @@
         renderStageManager.setViewRenderer(viewRenderer)
     }
 
-    override fun dumpPipeline(d: PipelineDumper) = with(d) {
-        dump("rootController", rootController)
-        dump("specBuilder", specBuilder)
-        dump("viewDiffer", viewDiffer)
-    }
-
-    private val viewRenderer = object : NotifViewRenderer {
-
-        override fun onRenderList(notifList: List<ListEntry>) {
-            traceSection("ShadeViewManager.onRenderList") {
-                viewDiffer.applySpec(specBuilder.buildNodeSpec(rootController, notifList))
-            }
+    override fun dumpPipeline(d: PipelineDumper) =
+        with(d) {
+            dump("rootController", rootController)
+            dump("specBuilder", specBuilder)
+            dump("viewDiffer", viewDiffer)
         }
 
-        override fun getStackController(): NotifStackController = stackController
+    private val viewRenderer =
+        object : NotifViewRenderer {
 
-        override fun getGroupController(group: GroupEntry): NotifGroupController =
-            viewBarn.requireGroupController(group.requireSummary)
+            override fun onRenderList(notifList: List<ListEntry>) {
+                traceSection("ShadeViewManager.onRenderList") {
+                    viewDiffer.applySpec(specBuilder.buildNodeSpec(rootController, notifList))
+                }
+            }
 
-        override fun getRowController(entry: NotificationEntry): NotifRowController =
-            viewBarn.requireRowController(entry)
-    }
+            override fun getStackController(): NotifStackController = stackController
+
+            override fun getGroupController(group: GroupEntry): NotifGroupController =
+                viewBarn.requireGroupController(group.requireSummary)
+
+            override fun getRowController(entry: NotificationEntry): NotifRowController =
+                viewBarn.requireRowController(entry)
+        }
 }
 
 @AssistedFactory
 interface ShadeViewManagerFactory {
     fun create(
         listContainer: NotificationListContainer,
-        stackController: NotifStackController
+        stackController: NotifStackController,
     ): ShadeViewManager
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/ReferenceNotificationsModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/ReferenceNotificationsModule.kt
index 925d4a5..4c25129 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/ReferenceNotificationsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/ReferenceNotificationsModule.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.dagger
 
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationsModule
 import com.android.systemui.statusbar.notification.row.NotificationRowModule
 import dagger.Module
 
@@ -23,5 +24,12 @@
  * A module that includes the standard notifications classes that most SysUI variants need. Variants
  * are free to not include this module and instead write a custom notifications module.
  */
-@Module(includes = [NotificationsModule::class, NotificationRowModule::class])
+@Module(
+    includes =
+        [
+            NotificationsModule::class,
+            NotificationRowModule::class,
+            PromotedNotificationsModule::class,
+        ]
+)
 object ReferenceNotificationsModule
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt
index 7d374b0..dbe5833 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt
@@ -16,12 +16,12 @@
 
 package com.android.systemui.statusbar.notification.data
 
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.settings.SecureSettingsRepositoryModule
-import com.android.systemui.settings.SystemSettingsRepositoryModule
+import com.android.systemui.settings.UserSettingsRepositoryModule
 import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository
 import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
 import com.android.systemui.shared.settings.data.repository.SystemSettingsRepository
@@ -32,9 +32,8 @@
 import dagger.multibindings.IntoMap
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
-import com.android.app.tracing.coroutines.launchTraced as launch
 
-@Module(includes = [SecureSettingsRepositoryModule::class, SystemSettingsRepositoryModule::class])
+@Module(includes = [UserSettingsRepositoryModule::class])
 object NotificationSettingsRepositoryModule {
     @Provides
     @SysUISingleton
@@ -48,7 +47,8 @@
             backgroundScope,
             backgroundDispatcher,
             secureSettingsRepository,
-            systemSettingsRepository)
+            systemSettingsRepository,
+        )
 
     @Provides
     @IntoMap
@@ -57,7 +57,7 @@
     fun provideCoreStartable(
         @Application applicationScope: CoroutineScope,
         repository: NotificationSettingsRepository,
-        logger: VisualInterruptionDecisionLogger
+        logger: VisualInterruptionDecisionLogger,
     ) = CoreStartable {
         applicationScope.launch {
             repository.isCooldownEnabled.collect { value -> logger.logCooldownSetting(value) }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
index 697a6ce..cff5bef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
@@ -83,6 +83,9 @@
             // TODO(b/364653005): [ongoingCallNotification] should be incorporated into this flow
             // instead of being separate.
             topLevelRepresentativeNotifications
+                .map { notifs -> notifs.filter { it.isPromoted } }
+                .distinctUntilChanged()
+                .flowOn(backgroundDispatcher)
         } else {
             flowOf(emptyList())
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
index 1008451..23da90d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationsProvider
 import com.android.systemui.statusbar.notification.shared.ActiveNotificationEntryModel
 import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel
 import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
@@ -50,6 +51,7 @@
 constructor(
     private val repository: ActiveNotificationListRepository,
     private val sectionStyleProvider: SectionStyleProvider,
+    private val promotedNotificationsProvider: PromotedNotificationsProvider,
 ) {
     /**
      * Sets the current list of rendered notification entries as displayed in the notification list.
@@ -57,7 +59,11 @@
     fun setRenderedList(entries: List<ListEntry>) {
         traceSection("RenderNotificationListInteractor.setRenderedList") {
             repository.activeNotifications.update { existingModels ->
-                buildActiveNotificationsStore(existingModels, sectionStyleProvider) {
+                buildActiveNotificationsStore(
+                    existingModels,
+                    sectionStyleProvider,
+                    promotedNotificationsProvider,
+                ) {
                     entries.forEach(::addListEntry)
                     setRankingsMap(entries)
                 }
@@ -69,13 +75,21 @@
 private fun buildActiveNotificationsStore(
     existingModels: ActiveNotificationsStore,
     sectionStyleProvider: SectionStyleProvider,
-    block: ActiveNotificationsStoreBuilder.() -> Unit
+    promotedNotificationsProvider: PromotedNotificationsProvider,
+    block: ActiveNotificationsStoreBuilder.() -> Unit,
 ): ActiveNotificationsStore =
-    ActiveNotificationsStoreBuilder(existingModels, sectionStyleProvider).apply(block).build()
+    ActiveNotificationsStoreBuilder(
+            existingModels,
+            sectionStyleProvider,
+            promotedNotificationsProvider,
+        )
+        .apply(block)
+        .build()
 
 private class ActiveNotificationsStoreBuilder(
     private val existingModels: ActiveNotificationsStore,
     private val sectionStyleProvider: SectionStyleProvider,
+    private val promotedNotificationsProvider: PromotedNotificationsProvider,
 ) {
     private val builder = ActiveNotificationsStore.Builder()
 
@@ -96,7 +110,7 @@
                         existingModels.createOrReuse(
                             key = entry.key,
                             summary = summaryModel,
-                            children = childModels
+                            children = childModels,
                         )
                     )
                 }
@@ -141,6 +155,7 @@
             key = key,
             groupKey = sbn.groupKey,
             whenTime = sbn.notification.`when`,
+            isPromoted = promotedNotificationsProvider.shouldPromote(this),
             isAmbient = sectionStyleProvider.isMinimized(this),
             isRowDismissed = isRowDismissed,
             isSilent = sectionStyleProvider.isSilent(this),
@@ -166,6 +181,7 @@
     key: String,
     groupKey: String?,
     whenTime: Long,
+    isPromoted: Boolean,
     isAmbient: Boolean,
     isRowDismissed: Boolean,
     isSilent: Boolean,
@@ -189,6 +205,7 @@
             key = key,
             groupKey = groupKey,
             whenTime = whenTime,
+            isPromoted = isPromoted,
             isAmbient = isAmbient,
             isRowDismissed = isRowDismissed,
             isSilent = isSilent,
@@ -212,6 +229,7 @@
             key = key,
             groupKey = groupKey,
             whenTime = whenTime,
+            isPromoted = isPromoted,
             isAmbient = isAmbient,
             isRowDismissed = isRowDismissed,
             isSilent = isSilent,
@@ -236,6 +254,7 @@
     key: String,
     groupKey: String?,
     whenTime: Long,
+    isPromoted: Boolean,
     isAmbient: Boolean,
     isRowDismissed: Boolean,
     isSilent: Boolean,
@@ -258,6 +277,7 @@
         key != this.key -> false
         groupKey != this.groupKey -> false
         whenTime != this.whenTime -> false
+        isPromoted != this.isPromoted -> false
         isAmbient != this.isAmbient -> false
         isRowDismissed != this.isRowDismissed -> false
         isSilent != this.isSilent -> false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
index 96f47e5..a0515ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
@@ -459,8 +459,12 @@
         Resources.Theme theme = mContext.getTheme();
         final @ColorInt int onSurface = Utils.getColorAttrDefaultColor(mContext,
                 com.android.internal.R.attr.materialColorOnSurface);
+        // Same resource, separate drawables to prevent touch effects from showing on the wrong
+        // button.
         final Drawable clearAllBg = theme.getDrawable(R.drawable.notif_footer_btn_background);
-        final Drawable manageBg = theme.getDrawable(R.drawable.notif_footer_btn_background);
+        final Drawable settingsBg = theme.getDrawable(R.drawable.notif_footer_btn_background);
+        final Drawable historyBg = NotifRedesignFooter.isEnabled()
+                ? theme.getDrawable(R.drawable.notif_footer_btn_background) : null;
         final @ColorInt int scHigh;
         if (!notificationFooterBackgroundTintOptimization()) {
             scHigh = Utils.getColorAttrDefaultColor(mContext,
@@ -468,7 +472,10 @@
             if (scHigh != 0) {
                 final ColorFilter bgColorFilter = new PorterDuffColorFilter(scHigh, SRC_ATOP);
                 clearAllBg.setColorFilter(bgColorFilter);
-                manageBg.setColorFilter(bgColorFilter);
+                settingsBg.setColorFilter(bgColorFilter);
+                if (NotifRedesignFooter.isEnabled()) {
+                    historyBg.setColorFilter(bgColorFilter);
+                }
             }
         } else {
             scHigh = 0;
@@ -476,13 +483,13 @@
         mClearAllButton.setBackground(clearAllBg);
         mClearAllButton.setTextColor(onSurface);
         if (NotifRedesignFooter.isEnabled()) {
-            mSettingsButton.setBackground(manageBg);
+            mSettingsButton.setBackground(settingsBg);
             mSettingsButton.setCompoundDrawableTintList(ColorStateList.valueOf(onSurface));
 
-            mHistoryButton.setBackground(manageBg);
+            mHistoryButton.setBackground(historyBg);
             mHistoryButton.setCompoundDrawableTintList(ColorStateList.valueOf(onSurface));
         } else {
-            mManageOrHistoryButton.setBackground(manageBg);
+            mManageOrHistoryButton.setBackground(settingsBg);
             mManageOrHistoryButton.setTextColor(onSurface);
         }
         mSeenNotifsFooterTextView.setTextColor(onSurface);
@@ -492,7 +499,7 @@
             colorUpdateLogger.logEvent("Footer.updateColors()",
                     "textColor(onSurface)=" + hexColorString(onSurface)
                             + " backgroundTint(surfaceContainerHigh)=" + hexColorString(scHigh)
-                            + " background=" + DrawableDumpKt.dumpToString(manageBg));
+                            + " background=" + DrawableDumpKt.dumpToString(settingsBg));
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
index 71cddc9..52336be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
@@ -75,7 +75,7 @@
     private val bubbles: Optional<Bubbles>,
     @ShadeDisplayAware private val context: Context,
     private val notificationManager: NotificationManager,
-    private val settingsInteractor: NotificationSettingsInteractor
+    private val settingsInteractor: NotificationSettingsInteractor,
 ) : VisualInterruptionDecisionProvider {
 
     init {
@@ -89,7 +89,7 @@
 
     private class DecisionImpl(
         override val shouldInterrupt: Boolean,
-        override val logReason: String
+        override val logReason: String,
     ) : Decision
 
     private data class LoggableDecision
@@ -107,7 +107,7 @@
                 LoggableDecision(
                     DecisionImpl(
                         shouldInterrupt = false,
-                        logReason = "${legacySuppressor.name}.$methodName"
+                        logReason = "${legacySuppressor.name}.$methodName",
                     )
                 )
 
@@ -123,7 +123,7 @@
 
     private class FullScreenIntentDecisionImpl(
         val entry: NotificationEntry,
-        private val fsiDecision: FullScreenIntentDecisionProvider.Decision
+        private val fsiDecision: FullScreenIntentDecisionProvider.Decision,
     ) : FullScreenIntentDecision, Loggable {
         var hasBeenLogged = false
 
@@ -154,7 +154,7 @@
             deviceProvisionedController,
             keyguardStateController,
             powerManager,
-            statusBarStateController
+            statusBarStateController,
         )
 
     private val legacySuppressors = mutableSetOf<NotificationInterruptSuppressor>()
@@ -197,7 +197,7 @@
                     context,
                     notificationManager,
                     logger,
-                    systemSettings
+                    systemSettings,
                 )
             )
             avalancheProvider.register()
@@ -290,7 +290,7 @@
     private fun logDecision(
         type: VisualInterruptionType,
         entry: NotificationEntry,
-        loggableDecision: LoggableDecision
+        loggableDecision: LoggableDecision,
     ) {
         if (!loggableDecision.isSpammy || logger.spew) {
             logger.logDecision(type.name, entry, loggableDecision.decision)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUi.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUi.kt
new file mode 100644
index 0000000..6324219
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUi.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 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.promoted
+
+import android.app.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the promoted ongoing notifications UI flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object PromotedNotificationUi {
+    /** The aconfig flag name */
+    const val FLAG_NAME = Flags.FLAG_UI_RICH_ONGOING
+
+    /** A token used for dependency declaration */
+    val token: FlagToken
+        get() = FlagToken(FLAG_NAME, isEnabled)
+
+    /** Is the refactor enabled */
+    @JvmStatic
+    inline val isEnabled
+        get() = Flags.uiRichOngoing()
+
+    /**
+     * Called to ensure code is only run when the flag is enabled. This protects users from the
+     * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+     * build to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun isUnexpectedlyInLegacyMode() =
+        RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+    /**
+     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+     * the flag is not enabled to ensure that the refactor author catches issues in testing.
+     * Caution!! Using this check incorrectly will cause crashes in nextfood builds!
+     */
+    @JvmStatic
+    inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+
+    /**
+     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+     * the flag is enabled to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsModule.kt
similarity index 61%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsModule.kt
index 94b2bdf..4be12bd 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsModule.kt
@@ -14,9 +14,17 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util.settings
+package com.android.systemui.statusbar.notification.promoted
 
-import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
 
-val Kosmos.userAwareSecureSettingsRepository by
-    Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+@Module
+abstract class PromotedNotificationsModule {
+    @Binds
+    @SysUISingleton
+    abstract fun bindPromotedNotificationsProvider(
+        impl: PromotedNotificationsProviderImpl
+    ): PromotedNotificationsProvider
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProvider.kt
new file mode 100644
index 0000000..691dc6f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProvider.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 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.promoted
+
+import android.app.Notification.FLAG_PROMOTED_ONGOING
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import javax.inject.Inject
+
+/** A provider for making decisions on which notifications should be promoted. */
+interface PromotedNotificationsProvider {
+    /** Returns true if the given notification should be promoted and false otherwise. */
+    fun shouldPromote(entry: NotificationEntry): Boolean
+}
+
+@SysUISingleton
+open class PromotedNotificationsProviderImpl @Inject constructor() : PromotedNotificationsProvider {
+    override fun shouldPromote(entry: NotificationEntry): Boolean {
+        if (!PromotedNotificationUi.isEnabled) {
+            return false
+        }
+        return (entry.sbn.notification.flags and FLAG_PROMOTED_ONGOING) != 0
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt
index e233def..9164145 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt
@@ -25,6 +25,7 @@
 import android.util.Log
 import android.util.Size
 import androidx.annotation.MainThread
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.internal.R
 import com.android.internal.widget.NotificationDrawableConsumer
 import com.android.internal.widget.NotificationIconManager
@@ -45,7 +46,6 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.delay
-import com.android.app.tracing.coroutines.launchTraced as launch
 import kotlinx.coroutines.withContext
 
 private const val TAG = "BigPicImageLoader"
@@ -67,7 +67,7 @@
     private val statsManager: BigPictureStatsManager,
     @Application private val scope: CoroutineScope,
     @Main private val mainDispatcher: CoroutineDispatcher,
-    @Background private val bgDispatcher: CoroutineDispatcher
+    @Background private val bgDispatcher: CoroutineDispatcher,
 ) : NotificationIconManager, Dumpable {
 
     private var lastLoadingJob: Job? = null
@@ -153,7 +153,7 @@
 
     private fun checkPlaceHolderSizeForDrawable(
         displayedState: DrawableState,
-        newDrawable: Drawable
+        newDrawable: Drawable,
     ) {
         if (displayedState is PlaceHolder) {
             val (oldWidth, oldHeight) = displayedState.drawableSize
@@ -163,7 +163,7 @@
                 Log.e(
                     TAG,
                     "Mismatch in dimensions, when replacing PlaceHolder " +
-                        "$oldWidth X $oldHeight with Drawable $newWidth X $newHeight."
+                        "$oldWidth X $oldHeight with Drawable $newWidth X $newHeight.",
                 )
             }
         }
@@ -184,9 +184,8 @@
         displayedState = drawableAndState?.second ?: Empty
     }
 
-    private fun startLoadingJob(icon: Icon): Job = scope.launch {
-        statsManager.measure { loadImage(icon) }
-    }
+    private fun startLoadingJob(icon: Icon): Job =
+        scope.launch { statsManager.measure { loadImage(icon) } }
 
     private suspend fun loadImage(icon: Icon) {
         val drawableAndState = withContext(bgDispatcher) { loadImageSync(icon) }
@@ -254,9 +253,12 @@
 
     private sealed class DrawableState(open val icon: Icon?) {
         data object Initial : DrawableState(null)
+
         data object Empty : DrawableState(null)
+
         data class PlaceHolder(override val icon: Icon, val drawableSize: Size) :
             DrawableState(icon)
+
         data class FullImage(override val icon: Icon, val drawableSize: Size) : DrawableState(icon)
     }
 }
@@ -298,7 +300,7 @@
 }
 
 private val Drawable.intrinsicSize
-    get() = Size(/*width=*/ intrinsicWidth, /*height=*/ intrinsicHeight)
+    get() = Size(/* width= */ intrinsicWidth, /* height= */ intrinsicHeight)
 
 private operator fun Size.component1() = width
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
index b166def..2dcb706 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
@@ -95,7 +95,7 @@
     private val smartReplyStateInflater: SmartReplyStateInflater,
     private val notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider,
     private val headsUpStyleProvider: HeadsUpStyleProvider,
-    private val logger: NotificationRowContentBinderLogger
+    private val logger: NotificationRowContentBinderLogger,
 ) : NotificationRowContentBinder {
 
     init {
@@ -110,7 +110,7 @@
         @InflationFlag contentToBind: Int,
         bindParams: BindParams,
         forceInflate: Boolean,
-        callback: InflationCallback?
+        callback: InflationCallback?,
     ) {
         if (row.isRemoved) {
             // We don't want to reinflate anything for removed notifications. Otherwise views might
@@ -147,7 +147,7 @@
                 /* isMediaFlagEnabled = */ smartReplyStateInflater,
                 notifLayoutInflaterFactoryProvider,
                 headsUpStyleProvider,
-                logger
+                logger,
             )
         if (inflateSynchronously) {
             task.onPostExecute(task.doInBackground())
@@ -165,7 +165,7 @@
         @InflationFlag reInflateFlags: Int,
         builder: Notification.Builder,
         packageContext: Context,
-        smartRepliesInflater: SmartReplyStateInflater
+        smartRepliesInflater: SmartReplyStateInflater,
     ): InflationProgress {
         val systemUIContext = row.context
         val result =
@@ -229,7 +229,7 @@
             row,
             remoteInputManager.remoteViewsOnClickHandler,
             /* callback= */ null,
-            logger
+            logger,
         )
         return result
     }
@@ -246,7 +246,7 @@
     override fun unbindContent(
         entry: NotificationEntry,
         row: ExpandableNotificationRow,
-        @InflationFlag contentToUnbind: Int
+        @InflationFlag contentToUnbind: Int,
     ) {
         logger.logUnbinding(entry, contentToUnbind)
         var curFlag = 1
@@ -268,7 +268,7 @@
     private fun freeNotificationView(
         entry: NotificationEntry,
         row: ExpandableNotificationRow,
-        @InflationFlag inflateFlag: Int
+        @InflationFlag inflateFlag: Int,
     ) {
         when (inflateFlag) {
             FLAG_CONTENT_VIEW_CONTRACTED ->
@@ -319,7 +319,7 @@
      */
     private fun cancelContentViewFrees(
         row: ExpandableNotificationRow,
-        @InflationFlag contentViews: Int
+        @InflationFlag contentViews: Int,
     ) {
         if (contentViews and FLAG_CONTENT_VIEW_CONTRACTED != 0) {
             row.privateLayout.removeContentInactiveRunnable(VISIBLE_TYPE_CONTRACTED)
@@ -372,7 +372,7 @@
         private val smartRepliesInflater: SmartReplyStateInflater,
         private val notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider,
         private val headsUpStyleProvider: HeadsUpStyleProvider,
-        private val logger: NotificationRowContentBinderLogger
+        private val logger: NotificationRowContentBinderLogger,
     ) : AsyncTask<Void, Void, Result<InflationProgress>>(), InflationCallback, InflationTask {
         private val context: Context
             get() = row.context
@@ -393,7 +393,7 @@
                     context.packageManager.getApplicationInfoAsUser(
                         packageName,
                         PackageManager.MATCH_UNINSTALLED_PACKAGES,
-                        userId
+                        userId,
                     )
             } catch (e: PackageManager.NameNotFoundException) {
                 return
@@ -442,11 +442,11 @@
                     notifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider,
                     headsUpStyleProvider = headsUpStyleProvider,
                     conversationProcessor = conversationProcessor,
-                    logger = logger
+                    logger = logger,
                 )
             logger.logAsyncTaskProgress(
                 entry,
-                "getting existing smart reply state (on wrong thread!)"
+                "getting existing smart reply state (on wrong thread!)",
             )
             val previousSmartReplyState: InflatedSmartReplyState? = row.existingSmartReplyState
             logger.logAsyncTaskProgress(entry, "inflating smart reply views")
@@ -469,7 +469,7 @@
                             reInflateFlags,
                             entry,
                             context,
-                            logger
+                            logger,
                         )
                     }
             }
@@ -483,7 +483,7 @@
                             reInflateFlags,
                             entry,
                             context,
-                            logger
+                            logger,
                         )
                     }
             }
@@ -513,7 +513,7 @@
                             row,
                             remoteViewClickHandler,
                             this /* callback */,
-                            logger
+                            logger,
                         )
                 }
                 .onFailure { error -> handleError(error as Exception) }
@@ -530,7 +530,7 @@
             Log.e(TAG, "couldn't inflate view for notification $ident", e)
             callback?.handleInflationException(
                 row.entry,
-                InflationException("Couldn't inflate contentViews$e")
+                InflationException("Couldn't inflate contentViews$e"),
             )
 
             // Cancel any image loading tasks, not useful any more
@@ -618,7 +618,7 @@
             packageContext: Context,
             previousSmartReplyState: InflatedSmartReplyState?,
             inflater: SmartReplyStateInflater,
-            logger: NotificationRowContentBinderLogger
+            logger: NotificationRowContentBinderLogger,
         ) {
             val inflateContracted =
                 (reInflateFlags and FLAG_CONTENT_VIEW_CONTRACTED != 0 &&
@@ -641,7 +641,7 @@
                         packageContext,
                         entry,
                         previousSmartReplyState,
-                        result.inflatedSmartReplyState!!
+                        result.inflatedSmartReplyState!!,
                     )
             }
             if (inflateHeadsUp) {
@@ -652,7 +652,7 @@
                         packageContext,
                         entry,
                         previousSmartReplyState,
-                        result.inflatedSmartReplyState!!
+                        result.inflatedSmartReplyState!!,
                     )
             }
         }
@@ -670,7 +670,7 @@
             notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider,
             headsUpStyleProvider: HeadsUpStyleProvider,
             conversationProcessor: ConversationNotificationProcessor,
-            logger: NotificationRowContentBinderLogger
+            logger: NotificationRowContentBinderLogger,
         ): InflationProgress {
             // process conversations and extract the messaging style
             val messagingStyle =
@@ -713,7 +713,7 @@
                     logger.logAsyncTaskProgress(entry, "inflating public single line view model")
                     SingleLineViewInflater.inflateRedactedSingleLineViewModel(
                         systemUIContext,
-                        entry.ranking.isConversation
+                        entry.ranking.isConversation,
                     )
                 } else null
 
@@ -746,7 +746,7 @@
             row: ExpandableNotificationRow,
             notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider,
             headsUpStyleProvider: HeadsUpStyleProvider,
-            logger: NotificationRowContentBinderLogger
+            logger: NotificationRowContentBinderLogger,
         ): NewRemoteViews {
             return TraceUtils.trace("NotificationContentInflater.createRemoteViews") {
                 val entryForLogging: NotificationEntry = row.entry
@@ -754,7 +754,7 @@
                     if (reInflateFlags and FLAG_CONTENT_VIEW_CONTRACTED != 0) {
                         logger.logAsyncTaskProgress(
                             entryForLogging,
-                            "creating contracted remote view"
+                            "creating contracted remote view",
                         )
                         createContentView(builder, isMinimized, usesIncreasedHeight)
                     } else null
@@ -762,7 +762,7 @@
                     if (reInflateFlags and FLAG_CONTENT_VIEW_EXPANDED != 0) {
                         logger.logAsyncTaskProgress(
                             entryForLogging,
-                            "creating expanded remote view"
+                            "creating expanded remote view",
                         )
                         createExpandedView(builder, isMinimized)
                     } else null
@@ -770,7 +770,7 @@
                     if (reInflateFlags and FLAG_CONTENT_VIEW_HEADS_UP != 0) {
                         logger.logAsyncTaskProgress(
                             entryForLogging,
-                            "creating heads up remote view"
+                            "creating heads up remote view",
                         )
                         val isHeadsUpCompact = headsUpStyleProvider.shouldApplyCompactStyle()
                         if (isHeadsUpCompact) {
@@ -791,7 +791,7 @@
                     ) {
                         logger.logAsyncTaskProgress(
                             entryForLogging,
-                            "creating group summary remote view"
+                            "creating group summary remote view",
                         )
                         builder.makeNotificationGroupHeader()
                     } else null
@@ -802,7 +802,7 @@
                     ) {
                         logger.logAsyncTaskProgress(
                             entryForLogging,
-                            "creating low-priority group summary remote view"
+                            "creating low-priority group summary remote view",
                         )
                         builder.makeLowPriorityContentView(true /* useRegularSubtext */)
                     } else null
@@ -812,7 +812,7 @@
                         expanded = expanded,
                         public = public,
                         normalGroupHeader = normalGroupHeader,
-                        minimizedGroupHeader = minimizedGroupHeader
+                        minimizedGroupHeader = minimizedGroupHeader,
                     )
                     .withLayoutInflaterFactory(row, notifLayoutInflaterFactoryProvider)
             }
@@ -820,7 +820,7 @@
 
         private fun NewRemoteViews.withLayoutInflaterFactory(
             row: ExpandableNotificationRow,
-            provider: NotifLayoutInflaterFactory.Provider
+            provider: NotifLayoutInflaterFactory.Provider,
         ): NewRemoteViews {
             contracted?.let {
                 it.layoutInflaterFactory = provider.provide(row, FLAG_CONTENT_VIEW_CONTRACTED)
@@ -848,7 +848,7 @@
             row: ExpandableNotificationRow,
             remoteViewClickHandler: InteractionHandler?,
             callback: InflationCallback?,
-            logger: NotificationRowContentBinderLogger
+            logger: NotificationRowContentBinderLogger,
         ): CancellationSignal {
             Trace.beginAsyncSection(APPLY_TRACE_METHOD, System.identityHashCode(row))
             val privateLayout = row.privateLayout
@@ -859,7 +859,7 @@
                 val isNewView =
                     !canReapplyRemoteView(
                         newView = result.remoteViews.contracted,
-                        oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)
+                        oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED),
                     )
                 val applyCallback: ApplyCallback =
                     object : ApplyCallback() {
@@ -890,7 +890,7 @@
                     existingWrapper = privateLayout.getVisibleWrapper(VISIBLE_TYPE_CONTRACTED),
                     runningInflations = runningInflations,
                     applyCallback = applyCallback,
-                    logger = logger
+                    logger = logger,
                 )
             }
             flag = FLAG_CONTENT_VIEW_EXPANDED
@@ -898,7 +898,7 @@
                 val isNewView =
                     !canReapplyRemoteView(
                         newView = result.remoteViews.expanded,
-                        oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)
+                        oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED),
                     )
                 val applyCallback: ApplyCallback =
                     object : ApplyCallback() {
@@ -929,7 +929,7 @@
                     existingWrapper = privateLayout.getVisibleWrapper(VISIBLE_TYPE_EXPANDED),
                     runningInflations = runningInflations,
                     applyCallback = applyCallback,
-                    logger = logger
+                    logger = logger,
                 )
             }
             flag = FLAG_CONTENT_VIEW_HEADS_UP
@@ -937,7 +937,7 @@
                 val isNewView =
                     !canReapplyRemoteView(
                         newView = result.remoteViews.headsUp,
-                        oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)
+                        oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP),
                     )
                 val applyCallback: ApplyCallback =
                     object : ApplyCallback() {
@@ -968,7 +968,7 @@
                     existingWrapper = privateLayout.getVisibleWrapper(VISIBLE_TYPE_HEADSUP),
                     runningInflations = runningInflations,
                     applyCallback = applyCallback,
-                    logger = logger
+                    logger = logger,
                 )
             }
             flag = FLAG_CONTENT_VIEW_PUBLIC
@@ -976,7 +976,7 @@
                 val isNewView =
                     !canReapplyRemoteView(
                         newView = result.remoteViews.public,
-                        oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC)
+                        oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC),
                     )
                 val applyCallback: ApplyCallback =
                     object : ApplyCallback() {
@@ -1007,7 +1007,7 @@
                     existingWrapper = publicLayout.getVisibleWrapper(VISIBLE_TYPE_CONTRACTED),
                     runningInflations = runningInflations,
                     applyCallback = applyCallback,
-                    logger = logger
+                    logger = logger,
                 )
             }
             if (AsyncGroupHeaderViewInflation.isEnabled) {
@@ -1018,7 +1018,7 @@
                         !canReapplyRemoteView(
                             newView = result.remoteViews.normalGroupHeader,
                             oldView =
-                                remoteViewCache.getCachedView(entry, FLAG_GROUP_SUMMARY_HEADER)
+                                remoteViewCache.getCachedView(entry, FLAG_GROUP_SUMMARY_HEADER),
                         )
                     val applyCallback: ApplyCallback =
                         object : ApplyCallback() {
@@ -1049,7 +1049,7 @@
                         existingWrapper = childrenContainer.notificationHeaderWrapper,
                         runningInflations = runningInflations,
                         applyCallback = applyCallback,
-                        logger = logger
+                        logger = logger,
                     )
                 }
                 if (reInflateFlags and FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER != 0) {
@@ -1059,15 +1059,15 @@
                             oldView =
                                 remoteViewCache.getCachedView(
                                     entry,
-                                    FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER
-                                )
+                                    FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER,
+                                ),
                         )
                     val applyCallback: ApplyCallback =
                         object : ApplyCallback() {
                             override fun setResultView(v: View) {
                                 logger.logAsyncTaskProgress(
                                     entry,
-                                    "low-priority group header view applied"
+                                    "low-priority group header view applied",
                                 )
                                 result.inflatedMinimizedGroupHeaderView =
                                     v as NotificationHeaderView?
@@ -1095,7 +1095,7 @@
                         existingWrapper = childrenContainer.minimizedGroupHeaderWrapper,
                         runningInflations = runningInflations,
                         applyCallback = applyCallback,
-                        logger = logger
+                        logger = logger,
                     )
                 }
             }
@@ -1110,7 +1110,7 @@
                 callback,
                 entry,
                 row,
-                logger
+                logger,
             )
             val cancellationSignal = CancellationSignal()
             cancellationSignal.setOnCancelListener {
@@ -1142,7 +1142,7 @@
             existingWrapper: NotificationViewWrapper?,
             runningInflations: HashMap<Int, CancellationSignal>,
             applyCallback: ApplyCallback,
-            logger: NotificationRowContentBinderLogger
+            logger: NotificationRowContentBinderLogger,
         ) {
             val newContentView: RemoteViews = applyCallback.remoteView
             if (inflateSynchronously) {
@@ -1152,7 +1152,7 @@
                             newContentView.apply(
                                 result.packageContext,
                                 parentLayout,
-                                remoteViewClickHandler
+                                remoteViewClickHandler,
                             )
                         validateView(v, entry, row.resources)
                         applyCallback.setResultView(v)
@@ -1162,7 +1162,7 @@
                         newContentView.reapply(
                             result.packageContext,
                             existingView,
-                            remoteViewClickHandler
+                            remoteViewClickHandler,
                         )
                         validateView(existingView, entry, row.resources)
                         existingWrapper.onReinflated()
@@ -1174,7 +1174,7 @@
                         row.entry,
                         callback,
                         logger,
-                        "applying view synchronously"
+                        "applying view synchronously",
                     )
                     // Add a running inflation to make sure we don't trigger callbacks.
                     // Safe to do because only happens in tests.
@@ -1199,7 +1199,7 @@
                                 row.entry,
                                 callback,
                                 logger,
-                                "applied invalid view"
+                                "applied invalid view",
                             )
                             runningInflations.remove(inflationId)
                             return
@@ -1219,7 +1219,7 @@
                             callback,
                             entry,
                             row,
-                            logger
+                            logger,
                         )
                     }
 
@@ -1234,20 +1234,20 @@
                                     newContentView.apply(
                                         result.packageContext,
                                         parentLayout,
-                                        remoteViewClickHandler
+                                        remoteViewClickHandler,
                                     )
                                 } else {
                                     newContentView.reapply(
                                         result.packageContext,
                                         existingView,
-                                        remoteViewClickHandler
+                                        remoteViewClickHandler,
                                     )
                                     existingView!!
                                 }
                             Log.wtf(
                                 TAG,
                                 "Async Inflation failed but normal inflation finished normally.",
-                                e
+                                e,
                             )
                             onViewApplied(newView)
                         } catch (anotherException: Exception) {
@@ -1258,7 +1258,7 @@
                                 row.entry,
                                 callback,
                                 logger,
-                                "applying view"
+                                "applying view",
                             )
                         }
                     }
@@ -1270,7 +1270,7 @@
                         parentLayout,
                         inflationExecutor,
                         listener,
-                        remoteViewClickHandler
+                        remoteViewClickHandler,
                     )
                 } else {
                     newContentView.reapplyAsync(
@@ -1278,7 +1278,7 @@
                         existingView,
                         inflationExecutor,
                         listener,
-                        remoteViewClickHandler
+                        remoteViewClickHandler,
                     )
                 }
             runningInflations[inflationId] = cancellationSignal
@@ -1299,7 +1299,7 @@
         private fun satisfiesMinHeightRequirement(
             view: View,
             entry: NotificationEntry,
-            resources: Resources
+            resources: Resources,
         ): Boolean {
             return if (!requiresHeightCheck(entry)) {
                 true
@@ -1353,7 +1353,7 @@
             notification: NotificationEntry,
             callback: InflationCallback?,
             logger: NotificationRowContentBinderLogger,
-            logContext: String
+            logContext: String,
         ) {
             Assert.isMainThread()
             logger.logAsyncTaskException(notification, logContext, e)
@@ -1375,7 +1375,7 @@
             endListener: InflationCallback?,
             entry: NotificationEntry,
             row: ExpandableNotificationRow,
-            logger: NotificationRowContentBinderLogger
+            logger: NotificationRowContentBinderLogger,
         ): Boolean {
             Assert.isMainThread()
             if (runningInflations.isNotEmpty()) {
@@ -1439,19 +1439,19 @@
                 FLAG_CONTENT_VIEW_CONTRACTED,
                 result.remoteViews.contracted,
                 result.inflatedContentView,
-                privateLayout::setContractedChild
+                privateLayout::setContractedChild,
             )
             remoteViewsUpdater.setContentView(
                 FLAG_CONTENT_VIEW_EXPANDED,
                 result.remoteViews.expanded,
                 result.inflatedExpandedView,
-                privateLayout::setExpandedChild
+                privateLayout::setExpandedChild,
             )
             remoteViewsUpdater.setSmartReplies(
                 FLAG_CONTENT_VIEW_EXPANDED,
                 result.remoteViews.expanded,
                 result.expandedInflatedSmartReplies,
-                privateLayout::setExpandedInflatedSmartReplies
+                privateLayout::setExpandedInflatedSmartReplies,
             )
             if (reInflateFlags and FLAG_CONTENT_VIEW_EXPANDED != 0) {
                 row.setExpandable(result.remoteViews.expanded != null)
@@ -1460,19 +1460,19 @@
                 FLAG_CONTENT_VIEW_HEADS_UP,
                 result.remoteViews.headsUp,
                 result.inflatedHeadsUpView,
-                privateLayout::setHeadsUpChild
+                privateLayout::setHeadsUpChild,
             )
             remoteViewsUpdater.setSmartReplies(
                 FLAG_CONTENT_VIEW_HEADS_UP,
                 result.remoteViews.headsUp,
                 result.headsUpInflatedSmartReplies,
-                privateLayout::setHeadsUpInflatedSmartReplies
+                privateLayout::setHeadsUpInflatedSmartReplies,
             )
             remoteViewsUpdater.setContentView(
                 FLAG_CONTENT_VIEW_PUBLIC,
                 result.remoteViews.public,
                 result.inflatedPublicView,
-                publicLayout::setContractedChild
+                publicLayout::setContractedChild,
             )
             if (AsyncGroupHeaderViewInflation.isEnabled) {
                 remoteViewsUpdater.setContentView(
@@ -1540,7 +1540,7 @@
 
         private fun createExpandedView(
             builder: Notification.Builder,
-            isMinimized: Boolean
+            isMinimized: Boolean,
         ): RemoteViews? {
             @Suppress("DEPRECATION")
             val bigContentView: RemoteViews? = builder.createBigContentView()
@@ -1558,7 +1558,7 @@
         private fun createContentView(
             builder: Notification.Builder,
             isMinimized: Boolean,
-            useLarge: Boolean
+            useLarge: Boolean,
         ): RemoteViews {
             return if (isMinimized) {
                 builder.makeLowPriorityContentView(false /* useRegularSubtext */)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt
index 7177a7b..08c1d71 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt
@@ -73,8 +73,8 @@
     private val cache = NotifCollectionCache<Boolean>()
 
     override fun shouldShowAppIcon(notification: StatusBarNotification, context: Context): Boolean {
-        val packageContext = notification.getPackageContext(context)
         return cache.getOrFetch(notification.packageName) {
+            val packageContext = notification.getPackageContext(context)
             !belongsToHeadlessSystemApp(packageContext)
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
index cf19938..19a92a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
@@ -36,6 +36,8 @@
     val groupKey: String?,
     /** When this notification was posted. */
     val whenTime: Long,
+    /** True if this notification should be promoted and false otherwise. */
+    val isPromoted: Boolean,
     /** Is this entry in the ambient / minimized section (lowest priority)? */
     val isAmbient: Boolean,
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
index c9a0010..31e4d2c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
@@ -26,7 +26,14 @@
 import com.android.systemui.statusbar.notification.collection.NotificationClassificationFlag
 import com.android.systemui.statusbar.notification.collection.render.MediaContainerController
 import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController
-import com.android.systemui.statusbar.notification.dagger.*
+import com.android.systemui.statusbar.notification.dagger.AlertingHeader
+import com.android.systemui.statusbar.notification.dagger.IncomingHeader
+import com.android.systemui.statusbar.notification.dagger.NewsHeader
+import com.android.systemui.statusbar.notification.dagger.PeopleHeader
+import com.android.systemui.statusbar.notification.dagger.PromoHeader
+import com.android.systemui.statusbar.notification.dagger.RecsHeader
+import com.android.systemui.statusbar.notification.dagger.SilentHeader
+import com.android.systemui.statusbar.notification.dagger.SocialHeader
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.ExpandableView
 import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.SectionProvider
@@ -54,7 +61,7 @@
     @NewsHeader private val newsHeaderController: SectionHeaderController,
     @SocialHeader private val socialHeaderController: SectionHeaderController,
     @RecsHeader private val recsHeaderController: SectionHeaderController,
-    @PromoHeader private val promoHeaderController: SectionHeaderController
+    @PromoHeader private val promoHeaderController: SectionHeaderController,
 ) : SectionProvider {
 
     private val configurationListener =
@@ -136,14 +143,16 @@
 
     override fun beginsSection(view: View, previous: View?): Boolean =
         view === silentHeaderView ||
-                view === mediaControlsView ||
-                view === peopleHeaderView ||
-                view === alertingHeaderView ||
-                view === incomingHeaderView ||
-                (NotificationClassificationFlag.isEnabled && (view === newsHeaderView
-                        || view === socialHeaderView || view === recsHeaderView
-                        || view === promoHeaderView)) ||
-                getBucket(view) != getBucket(previous)
+            view === mediaControlsView ||
+            view === peopleHeaderView ||
+            view === alertingHeaderView ||
+            view === incomingHeaderView ||
+            (NotificationClassificationFlag.isEnabled &&
+                (view === newsHeaderView ||
+                    view === socialHeaderView ||
+                    view === recsHeaderView ||
+                    view === promoHeaderView)) ||
+            getBucket(view) != getBucket(previous)
 
     private fun getBucket(view: View?): Int? =
         when {
@@ -165,6 +174,7 @@
         data class Many(val first: ExpandableView, val last: ExpandableView) : SectionBounds()
 
         data class One(val lone: ExpandableView) : SectionBounds()
+
         object None : SectionBounds()
 
         fun addNotif(notif: ExpandableView): SectionBounds =
@@ -183,7 +193,7 @@
 
         private fun NotificationSection.setFirstAndLastVisibleChildren(
             first: ExpandableView?,
-            last: ExpandableView?
+            last: ExpandableView?,
         ): Boolean {
             val firstChanged = setFirstVisibleChild(first)
             val lastChanged = setLastVisibleChild(last)
@@ -198,7 +208,7 @@
      */
     fun updateFirstAndLastViewsForAllSections(
         sections: Array<NotificationSection>,
-        children: List<ExpandableView>
+        children: List<ExpandableView>,
     ): Boolean {
         // Create mapping of bucket to section
         val sectionBounds =
@@ -213,7 +223,7 @@
                 .foldToSparseArray(
                     SectionBounds.None,
                     size = sections.size,
-                    operation = SectionBounds::addNotif
+                    operation = SectionBounds::addNotif,
                 )
 
         // Build a set of the old first/last Views of the sections
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 99efba4..7389086 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -171,12 +171,14 @@
 import com.android.systemui.shade.NotificationShadeWindowViewController;
 import com.android.systemui.shade.QuickSettingsController;
 import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeExpandsOnStatusBarLongPress;
 import com.android.systemui.shade.ShadeExpansionChangeEvent;
 import com.android.systemui.shade.ShadeExpansionListener;
 import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.shade.ShadeLogger;
 import com.android.systemui.shade.ShadeSurface;
 import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.StatusBarLongPressGestureDetector;
 import com.android.systemui.shared.recents.utilities.Utilities;
 import com.android.systemui.shared.statusbar.phone.BarTransitions;
 import com.android.systemui.statusbar.AutoHideUiElement;
@@ -295,7 +297,7 @@
             };
 
     void onStatusBarWindowStateChanged(@WindowVisibleState int state) {
-        StatusBarSimpleFragment.assertInLegacyMode();
+        StatusBarConnectedDisplays.assertInLegacyMode();
         mStatusBarWindowState = state;
         updateBubblesVisibility();
     }
@@ -366,6 +368,7 @@
 
     private PhoneStatusBarViewController mPhoneStatusBarViewController;
     private PhoneStatusBarTransitions mStatusBarTransitions;
+    private final Provider<StatusBarLongPressGestureDetector> mStatusBarLongPressGestureDetector;
     private final AuthRippleController mAuthRippleController;
     @WindowVisibleState private int mStatusBarWindowState = WINDOW_STATE_SHOWING;
     private final NotificationShadeWindowController mNotificationShadeWindowController;
@@ -671,6 +674,7 @@
             ShadeController shadeController,
             WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+            Provider<StatusBarLongPressGestureDetector> statusBarLongPressGestureDetector,
             ViewMediatorCallback viewMediatorCallback,
             InitController initController,
             @Named(TIME_TICK_HANDLER_NAME) Handler timeTickHandler,
@@ -778,6 +782,7 @@
         mShadeController = shadeController;
         mWindowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+        mStatusBarLongPressGestureDetector = statusBarLongPressGestureDetector;
         mKeyguardViewMediatorCallback = viewMediatorCallback;
         mInitController = initController;
         mPluginDependencyProvider = pluginDependencyProvider;
@@ -1527,6 +1532,11 @@
                 // to touch outside the customizer to close it, such as on the status or nav bar.
                 mShadeController.onStatusBarTouch(event);
             }
+            if (ShadeExpandsOnStatusBarLongPress.isEnabled()
+                    && mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
+                mStatusBarLongPressGestureDetector.get().handleTouch(event);
+            }
+
             return getNotificationShadeWindowView().onTouchEvent(event);
         };
     }
@@ -1589,8 +1599,6 @@
                 .setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
         mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
         mRemoteInputManager.addControllerCallback(mStatusBarKeyguardViewManager);
-
-        mLightBarController.setBiometricUnlockController(mBiometricUnlockController);
         Trace.endSection();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index be2fb68..2433b78 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -50,6 +50,8 @@
 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToLockscreenTransitionViewModel;
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenToGlanceableHubTransitionViewModel;
 import com.android.systemui.log.core.LogLevel;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.res.R;
@@ -82,6 +84,8 @@
 
 import kotlin.Unit;
 
+import kotlinx.coroutines.CoroutineDispatcher;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
@@ -108,6 +112,7 @@
             R.id.keyguard_hun_animator_end_tag,
             R.id.keyguard_hun_animator_start_tag);
 
+    private final CoroutineDispatcher mCoroutineDispatcher;
     private final CarrierTextController mCarrierTextController;
     private final ConfigurationController mConfigurationController;
     private final SystemStatusAnimationScheduler mAnimationScheduler;
@@ -133,6 +138,8 @@
     private final Object mLock = new Object();
     private final KeyguardLogger mLogger;
     private final CommunalSceneInteractor mCommunalSceneInteractor;
+    private final GlanceableHubToLockscreenTransitionViewModel mHubToLockscreenTransitionViewModel;
+    private final LockscreenToGlanceableHubTransitionViewModel mLockscreenToHubTransitionViewModel;
 
     private View mSystemIconsContainer;
     private final StatusOverlayHoverListenerFactory mStatusOverlayHoverListenerFactory;
@@ -249,10 +256,21 @@
     private boolean mCommunalShowing;
 
     private final Consumer<Boolean> mCommunalConsumer = (communalShowing) -> {
-        mCommunalShowing = communalShowing;
-        updateViewState();
+        updateCommunalShowing(communalShowing);
     };
 
+    @VisibleForTesting
+    void updateCommunalShowing(boolean communalShowing) {
+        mCommunalShowing = communalShowing;
+
+        // When communal is hidden (either by transition or state change), set alpha to fully
+        // visible.
+        if (!mCommunalShowing) {
+            setAlpha(-1f);
+        }
+        updateViewState();
+    }
+
     private final DisableStateTracker mDisableStateTracker;
 
     private final List<String> mBlockedIcons = new ArrayList<>();
@@ -277,6 +295,15 @@
     private boolean mShowingKeyguardHeadsUp;
     private StatusBarSystemEventDefaultAnimator mSystemEventAnimator;
     private float mSystemEventAnimatorAlpha = 1;
+    private final Consumer<Float> mToGlanceableHubStatusBarAlphaConsumer = (alpha) ->
+            updateCommunalAlphaTransition(alpha);
+
+    private final Consumer<Float> mFromGlanceableHubStatusBarAlphaConsumer = (alpha) ->
+            updateCommunalAlphaTransition(alpha);
+
+    @VisibleForTesting  void updateCommunalAlphaTransition(float alpha) {
+        setAlpha(!mCommunalShowing || alpha == 0 ? -1 : alpha);
+    }
 
     /**
      * The alpha value to be set on the View. If -1, this value is to be ignored.
@@ -285,6 +312,7 @@
 
     @Inject
     public KeyguardStatusBarViewController(
+            @Main CoroutineDispatcher dispatcher,
             KeyguardStatusBarView view,
             CarrierTextController carrierTextController,
             ConfigurationController configurationController,
@@ -310,9 +338,14 @@
             @Background Executor backgroundExecutor,
             KeyguardLogger logger,
             StatusOverlayHoverListenerFactory statusOverlayHoverListenerFactory,
-            CommunalSceneInteractor communalSceneInteractor
+            CommunalSceneInteractor communalSceneInteractor,
+            GlanceableHubToLockscreenTransitionViewModel
+                    glanceableHubToLockscreenTransitionViewModel,
+            LockscreenToGlanceableHubTransitionViewModel
+                    lockscreenToGlanceableHubTransitionViewModel
     ) {
         super(view);
+        mCoroutineDispatcher = dispatcher;
         mCarrierTextController = carrierTextController;
         mConfigurationController = configurationController;
         mAnimationScheduler = animationScheduler;
@@ -337,6 +370,8 @@
         mBackgroundExecutor = backgroundExecutor;
         mLogger = logger;
         mCommunalSceneInteractor = communalSceneInteractor;
+        mHubToLockscreenTransitionViewModel = glanceableHubToLockscreenTransitionViewModel;
+        mLockscreenToHubTransitionViewModel = lockscreenToGlanceableHubTransitionViewModel;
 
         mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled();
         mKeyguardStateController.addCallback(
@@ -418,7 +453,12 @@
                 UserHandle.USER_ALL);
         updateUserSwitcher();
         onThemeChanged();
-        collectFlow(mView, mCommunalSceneInteractor.isCommunalVisible(), mCommunalConsumer);
+        collectFlow(mView, mCommunalSceneInteractor.isCommunalVisible(), mCommunalConsumer,
+                mCoroutineDispatcher);
+        collectFlow(mView, mLockscreenToHubTransitionViewModel.getStatusBarAlpha(),
+                mToGlanceableHubStatusBarAlphaConsumer, mCoroutineDispatcher);
+        collectFlow(mView, mHubToLockscreenTransitionViewModel.getStatusBarAlpha(),
+                mFromGlanceableHubStatusBarAlphaConsumer, mCoroutineDispatcher);
     }
 
     @Override
@@ -573,7 +613,7 @@
                         && !mDozing
                         && !hideForBypass
                         && !mDisableStateTracker.isDisabled()
-                        && !mCommunalShowing
+                        && (!mCommunalShowing || mExplicitAlpha != -1)
                         ? View.VISIBLE : View.INVISIBLE;
 
         updateViewState(newAlpha, newVisibility);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.kt
new file mode 100644
index 0000000..a6374a6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import android.view.WindowInsetsController
+import com.android.internal.colorextraction.ColorExtractor
+import com.android.internal.view.AppearanceRegion
+import com.android.systemui.CoreStartable
+
+/** Controls how light status bar flag applies to the icons. */
+interface LightBarController : CoreStartable {
+
+    fun stop()
+
+    fun setNavigationBar(navigationBar: LightBarTransitionsController)
+
+    fun onNavigationBarAppearanceChanged(
+        @WindowInsetsController.Appearance appearance: Int,
+        nbModeChanged: Boolean,
+        navigationBarMode: Int,
+        navbarColorManagedByIme: Boolean,
+    )
+
+    fun onNavigationBarModeChanged(newBarMode: Int)
+
+    fun setQsCustomizing(customizing: Boolean)
+
+    /** Set if Quick Settings is fully expanded, which affects notification scrim visibility. */
+    fun setQsExpanded(expanded: Boolean)
+
+    /** Set if Global Actions dialog is visible, which requires dark mode (light buttons). */
+    fun setGlobalActionsVisible(visible: Boolean)
+
+    /**
+     * Controls the light status bar temporarily for back navigation.
+     *
+     * @param appearance the customized appearance.
+     */
+    fun customizeStatusBarAppearance(appearance: AppearanceRegion)
+
+    /**
+     * Sets whether the direct-reply is in use or not.
+     *
+     * @param directReplying `true` when the direct-reply is in-use.
+     */
+    fun setDirectReplying(directReplying: Boolean)
+
+    fun setScrimState(
+        scrimState: ScrimState,
+        scrimBehindAlpha: Float,
+        scrimInFrontColor: ColorExtractor.GradientColors,
+    )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java
similarity index 81%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java
index a33996b..edc1f88 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java
@@ -11,7 +11,7 @@
  * 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
+ * limitations under the License.
  */
 
 package com.android.systemui.statusbar.phone;
@@ -22,9 +22,9 @@
 import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
 import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
 
-import android.content.Context;
 import android.graphics.Rect;
 import android.util.Log;
+import android.view.Display;
 import android.view.InsetsFlags;
 import android.view.ViewDebug;
 import android.view.WindowInsetsController.Appearance;
@@ -34,30 +34,32 @@
 
 import com.android.internal.colorextraction.ColorExtractor.GradientColors;
 import com.android.internal.view.AppearanceRegion;
-import com.android.systemui.CoreStartable;
-import com.android.systemui.Dumpable;
-import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.DarkIconDispatcher;
-import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.statusbar.data.model.StatusBarAppearance;
-import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore;
+import com.android.systemui.statusbar.data.repository.StatusBarModePerDisplayRepository;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.util.Compile;
-import com.android.systemui.util.kotlin.JavaAdapter;
+import com.android.systemui.util.kotlin.JavaAdapterKt;
+
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
+
+import kotlin.coroutines.CoroutineContext;
+
+import kotlinx.coroutines.CoroutineScope;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
 
-import javax.inject.Inject;
-
 /**
  * Controls how light status bar flag applies to the icons.
  */
-@SysUISingleton
-public class LightBarController implements
-        BatteryController.BatteryStateChangeCallback, Dumpable, CoreStartable {
+public class LightBarControllerImpl implements
+        BatteryController.BatteryStateChangeCallback, LightBarController {
 
     private static final String TAG = "LightBarController";
     private static final boolean DEBUG_NAVBAR = Compile.IS_DEBUG;
@@ -65,11 +67,14 @@
 
     private static final float NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD = 0.1f;
 
-    private final JavaAdapter mJavaAdapter;
+    private final CoroutineScope mCoroutineScope;
     private final SysuiDarkIconDispatcher mStatusBarIconController;
     private final BatteryController mBatteryController;
-    private final StatusBarModeRepositoryStore mStatusBarModeRepository;
-    private BiometricUnlockController mBiometricUnlockController;
+    private final NavigationModeController mNavModeController;
+    private final DumpManager mDumpManager;
+    private final StatusBarModePerDisplayRepository mStatusBarModeRepository;
+    private final CoroutineContext mMainContext;
+    private final BiometricUnlockController mBiometricUnlockController;
 
     private LightBarTransitionsController mNavigationBarController;
     private @Appearance int mAppearance;
@@ -119,47 +124,60 @@
     private String mLastNavigationBarAppearanceChangedLog;
     private StringBuilder mLogStringBuilder = null;
 
-    @Inject
-    public LightBarController(
-            Context ctx,
-            JavaAdapter javaAdapter,
+    private final String mDumpableName;
+
+    private final NavigationModeController.ModeChangedListener mNavigationModeListener =
+            (mode) -> mNavigationMode = mode;
+
+    @AssistedInject
+    public LightBarControllerImpl(
+            @Assisted int displayId,
+            @Assisted CoroutineScope coroutineScope,
             DarkIconDispatcher darkIconDispatcher,
             BatteryController batteryController,
             NavigationModeController navModeController,
-            StatusBarModeRepositoryStore statusBarModeRepository,
+            @Assisted StatusBarModePerDisplayRepository statusBarModeRepository,
             DumpManager dumpManager,
-            DisplayTracker displayTracker) {
-        mJavaAdapter = javaAdapter;
+            @Main CoroutineContext mainContext,
+            BiometricUnlockController biometricUnlockController) {
+        mCoroutineScope = coroutineScope;
         mStatusBarIconController = (SysuiDarkIconDispatcher) darkIconDispatcher;
         mBatteryController = batteryController;
-        mBatteryController.addCallback(this);
+        mNavModeController = navModeController;
+        mDumpManager = dumpManager;
         mStatusBarModeRepository = statusBarModeRepository;
-        mNavigationMode = navModeController.addListener((mode) -> {
-            mNavigationMode = mode;
-        });
-
-        if (ctx.getDisplayId() == displayTracker.getDefaultDisplayId()) {
-            dumpManager.registerDumpable(getClass().getSimpleName(), this);
-        }
+        mMainContext = mainContext;
+        mBiometricUnlockController = biometricUnlockController;
+        String dumpableNameSuffix =
+                displayId == Display.DEFAULT_DISPLAY ? "" : String.valueOf(displayId);
+        mDumpableName = getClass().getSimpleName() + dumpableNameSuffix;
     }
 
     @Override
     public void start() {
-        mJavaAdapter.alwaysCollectFlow(
-                mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance(),
+        mDumpManager.registerCriticalDumpable(mDumpableName, this);
+        mBatteryController.addCallback(this);
+        mNavigationMode = mNavModeController.addListener(mNavigationModeListener);
+        JavaAdapterKt.collectFlow(
+                mCoroutineScope,
+                mMainContext,
+                mStatusBarModeRepository.getStatusBarAppearance(),
                 this::onStatusBarAppearanceChanged);
     }
 
+    @Override
+    public void stop() {
+        mDumpManager.unregisterDumpable(mDumpableName);
+        mBatteryController.removeCallback(this);
+        mNavModeController.removeListener(mNavigationModeListener);
+    }
+
+    @Override
     public void setNavigationBar(LightBarTransitionsController navigationBar) {
         mNavigationBarController = navigationBar;
         updateNavigation();
     }
 
-    public void setBiometricUnlockController(
-            BiometricUnlockController biometricUnlockController) {
-        mBiometricUnlockController = biometricUnlockController;
-    }
-
     private void onStatusBarAppearanceChanged(@Nullable StatusBarAppearance params) {
         if (params == null) {
             return;
@@ -202,6 +220,7 @@
         mNavbarColorManagedByIme = navbarColorManagedByIme;
     }
 
+    @Override
     public void onNavigationBarAppearanceChanged(@Appearance int appearance, boolean nbModeChanged,
             int navigationBarMode, boolean navbarColorManagedByIme) {
         int diff = appearance ^ mAppearance;
@@ -244,6 +263,7 @@
         mNavbarColorManagedByIme = navbarColorManagedByIme;
     }
 
+    @Override
     public void onNavigationBarModeChanged(int newBarMode) {
         mHasLightNavigationBar = isLight(mAppearance, newBarMode, APPEARANCE_LIGHT_NAVIGATION_BARS);
     }
@@ -258,30 +278,28 @@
                 mNavigationBarMode, mNavbarColorManagedByIme);
     }
 
+    @Override
     public void setQsCustomizing(boolean customizing) {
         if (mQsCustomizing == customizing) return;
         mQsCustomizing = customizing;
         reevaluate();
     }
 
-    /** Set if Quick Settings is fully expanded, which affects notification scrim visibility */
+    @Override
     public void setQsExpanded(boolean expanded) {
         if (mQsExpanded == expanded) return;
         mQsExpanded = expanded;
         reevaluate();
     }
 
-    /** Set if Global Actions dialog is visible, which requires dark mode (light buttons) */
+    @Override
     public void setGlobalActionsVisible(boolean visible) {
         if (mGlobalActionsVisible == visible) return;
         mGlobalActionsVisible = visible;
         reevaluate();
     }
 
-    /**
-     * Controls the light status bar temporarily for back navigation.
-     * @param appearance the custmoized appearance.
-     */
+    @Override
     public void customizeStatusBarAppearance(AppearanceRegion appearance) {
         if (appearance != null) {
             final ArrayList<AppearanceRegion> appearancesList = new ArrayList<>();
@@ -303,16 +321,14 @@
         }
     }
 
-    /**
-     * Sets whether the direct-reply is in use or not.
-     * @param directReplying {@code true} when the direct-reply is in-use.
-     */
+    @Override
     public void setDirectReplying(boolean directReplying) {
         if (mDirectReplying == directReplying) return;
         mDirectReplying = directReplying;
         reevaluate();
     }
 
+    @Override
     public void setScrimState(ScrimState scrimState, float scrimBehindAlpha,
             GradientColors scrimInFrontColor) {
         boolean bouncerVisibleLast = mBouncerVisible;
@@ -368,9 +384,6 @@
     }
 
     private boolean animateChange() {
-        if (mBiometricUnlockController == null) {
-            return false;
-        }
         int unlockMode = mBiometricUnlockController.getMode();
         return unlockMode != BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
                 && unlockMode != BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
@@ -387,20 +400,17 @@
             }
         }
 
-        // If no one is light, all icons become white.
         if (lightBarBounds.isEmpty()) {
-            mStatusBarIconController.getTransitionsController().setIconsDark(
-                    false, animateChange());
-        }
-
-        // If all stacks are light, all icons get dark.
-        else if (lightBarBounds.size() == numStacks) {
+            // If no one is light, all icons become white.
+            mStatusBarIconController
+                    .getTransitionsController()
+                    .setIconsDark(false, animateChange());
+        } else if (lightBarBounds.size() == numStacks) {
+            // If all stacks are light, all icons get dark.
             mStatusBarIconController.setIconsDarkArea(null);
             mStatusBarIconController.getTransitionsController().setIconsDark(true, animateChange());
-        }
-
-        // Not the same for every stack, magic!
-        else {
+        } else {
+            // Not the same for every stack, magic!
             mStatusBarIconController.setIconsDarkArea(lightBarBounds);
             mStatusBarIconController.getTransitionsController().setIconsDark(true, animateChange());
         }
@@ -468,47 +478,15 @@
         }
     }
 
-    /**
-     * Injectable factory for creating a {@link LightBarController}.
-     */
-    public static class Factory {
-        private final JavaAdapter mJavaAdapter;
-        private final DarkIconDispatcher mDarkIconDispatcher;
-        private final BatteryController mBatteryController;
-        private final NavigationModeController mNavModeController;
-        private final StatusBarModeRepositoryStore mStatusBarModeRepository;
-        private final DumpManager mDumpManager;
-        private final DisplayTracker mDisplayTracker;
+    /** Injectable factory for creating a {@link LightBarControllerImpl}. */
+    @AssistedFactory
+    @FunctionalInterface
+    public interface Factory {
 
-        @Inject
-        public Factory(
-                JavaAdapter javaAdapter,
-                DarkIconDispatcher darkIconDispatcher,
-                BatteryController batteryController,
-                NavigationModeController navModeController,
-                StatusBarModeRepositoryStore statusBarModeRepository,
-                DumpManager dumpManager,
-                DisplayTracker displayTracker) {
-            mJavaAdapter = javaAdapter;
-            mDarkIconDispatcher = darkIconDispatcher;
-            mBatteryController = batteryController;
-            mNavModeController = navModeController;
-            mStatusBarModeRepository = statusBarModeRepository;
-            mDumpManager = dumpManager;
-            mDisplayTracker = displayTracker;
-        }
-
-        /** Create an {@link LightBarController} */
-        public LightBarController create(Context context) {
-            return new LightBarController(
-                    context,
-                    mJavaAdapter,
-                    mDarkIconDispatcher,
-                    mBatteryController,
-                    mNavModeController,
-                    mStatusBarModeRepository,
-                    mDumpManager,
-                    mDisplayTracker);
-        }
+        /** Creates a {@link LightBarControllerImpl}. */
+        LightBarControllerImpl create(
+                int displayId,
+                CoroutineScope coroutineScope,
+                StatusBarModePerDisplayRepository statusBarModePerDisplayRepository);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 91c43dd..176dd8d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -39,8 +39,8 @@
 import com.android.systemui.Flags;
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.res.R;
-import com.android.systemui.shade.LongPressGestureDetector;
 import com.android.systemui.shade.ShadeExpandsOnStatusBarLongPress;
+import com.android.systemui.shade.StatusBarLongPressGestureDetector;
 import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
 import com.android.systemui.statusbar.window.StatusBarWindowControllerStore;
 import com.android.systemui.user.ui.binder.StatusBarUserChipViewBinder;
@@ -69,7 +69,7 @@
     private InsetsFetcher mInsetsFetcher;
     private int mDensity;
     private float mFontScale;
-    private LongPressGestureDetector mLongPressGestureDetector;
+    private StatusBarLongPressGestureDetector mStatusBarLongPressGestureDetector;
 
     /**
      * Draw this many pixels into the left/right side of the cutout to optimally use the space
@@ -81,9 +81,10 @@
         mStatusBarWindowControllerStore = Dependency.get(StatusBarWindowControllerStore.class);
     }
 
-    void setLongPressGestureDetector(LongPressGestureDetector longPressGestureDetector) {
+    void setLongPressGestureDetector(
+            StatusBarLongPressGestureDetector statusBarLongPressGestureDetector) {
         if (ShadeExpandsOnStatusBarLongPress.isEnabled()) {
-            mLongPressGestureDetector = longPressGestureDetector;
+            mStatusBarLongPressGestureDetector = statusBarLongPressGestureDetector;
         }
     }
 
@@ -207,8 +208,9 @@
 
     @Override
     public boolean onTouchEvent(MotionEvent event) {
-        if (ShadeExpandsOnStatusBarLongPress.isEnabled() && mLongPressGestureDetector != null) {
-            mLongPressGestureDetector.handleTouch(event);
+        if (ShadeExpandsOnStatusBarLongPress.isEnabled()
+                && mStatusBarLongPressGestureDetector != null) {
+            mStatusBarLongPressGestureDetector.handleTouch(event);
         }
         if (mTouchEventHandler == null) {
             Log.w(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index c24f432..4245494 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -33,11 +33,11 @@
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.ui.view.WindowRootView
-import com.android.systemui.shade.LongPressGestureDetector
 import com.android.systemui.shade.ShadeController
 import com.android.systemui.shade.ShadeExpandsOnStatusBarLongPress
 import com.android.systemui.shade.ShadeLogger
 import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shade.StatusBarLongPressGestureDetector
 import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
 import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
 import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore
@@ -68,7 +68,7 @@
     private val shadeController: ShadeController,
     private val shadeViewController: ShadeViewController,
     private val panelExpansionInteractor: PanelExpansionInteractor,
-    private val longPressGestureDetector: Provider<LongPressGestureDetector>,
+    private val statusBarLongPressGestureDetector: Provider<StatusBarLongPressGestureDetector>,
     private val windowRootView: Provider<WindowRootView>,
     private val shadeLogger: ShadeLogger,
     private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?,
@@ -118,7 +118,7 @@
         addCursorSupportToIconContainers()
 
         if (ShadeExpandsOnStatusBarLongPress.isEnabled) {
-            mView.setLongPressGestureDetector(longPressGestureDetector.get())
+            mView.setLongPressGestureDetector(statusBarLongPressGestureDetector.get())
         }
 
         progressProvider?.setReadyToHandleTransition(true)
@@ -335,7 +335,7 @@
         private val shadeController: ShadeController,
         private val shadeViewController: ShadeViewController,
         private val panelExpansionInteractor: PanelExpansionInteractor,
-        private val longPressGestureDetector: Provider<LongPressGestureDetector>,
+        private val statusBarLongPressGestureDetector: Provider<StatusBarLongPressGestureDetector>,
         private val windowRootView: Provider<WindowRootView>,
         private val shadeLogger: ShadeLogger,
         private val viewUtil: ViewUtil,
@@ -360,7 +360,7 @@
                 shadeController,
                 shadeViewController,
                 panelExpansionInteractor,
-                longPressGestureDetector,
+                statusBarLongPressGestureDetector,
                 windowRootView,
                 shadeLogger,
                 statusBarMoveFromCenterAnimationController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
index 94de3510..ba878ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
@@ -103,8 +103,12 @@
         fun statusBarInitializerImpl(
             implFactory: StatusBarInitializerImpl.Factory,
             statusBarWindowControllerStore: StatusBarWindowControllerStore,
+            statusBarModeRepositoryStore: StatusBarModeRepositoryStore,
         ): StatusBarInitializerImpl {
-            return implFactory.create(statusBarWindowControllerStore.defaultDisplay)
+            return implFactory.create(
+                statusBarWindowControllerStore.defaultDisplay,
+                statusBarModeRepositoryStore.defaultDisplay,
+            )
         }
 
         @Provides
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepository.kt
index 1d08f2b..98eed84 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepository.kt
@@ -37,6 +37,9 @@
 
     /** Clients must observe this property, as device-based satellite is location-dependent */
     val isSatelliteAllowedForCurrentLocation: StateFlow<Boolean>
+
+    /** When enabled, a satellite icon will display when all other connections are OOS */
+    val isOpportunisticSatelliteIconEnabled: Boolean
 }
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcher.kt
index 58c30e0..de42b92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcher.kt
@@ -97,6 +97,9 @@
             }
             .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl)
 
+    override val isOpportunisticSatelliteIconEnabled: Boolean
+        get() = activeRepo.value.isOpportunisticSatelliteIconEnabled
+
     override val isSatelliteProvisioned: StateFlow<Boolean> =
         activeRepo
             .flatMapLatest { it.isSatelliteProvisioned }
@@ -118,6 +121,6 @@
             .stateIn(
                 scope,
                 SharingStarted.WhileSubscribed(),
-                realImpl.isSatelliteAllowedForCurrentLocation.value
+                realImpl.isSatelliteAllowedForCurrentLocation.value,
             )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepository.kt
index d557bbf..755899f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepository.kt
@@ -16,15 +16,18 @@
 
 package com.android.systemui.statusbar.pipeline.satellite.data.demo
 
+import android.content.res.Resources
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository
 import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.flow.MutableStateFlow
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /** A satellite repository that represents the latest satellite values sent via demo mode. */
 @SysUISingleton
@@ -33,9 +36,13 @@
 constructor(
     private val dataSource: DemoDeviceBasedSatelliteDataSource,
     @Application private val scope: CoroutineScope,
+    @Main resources: Resources,
 ) : DeviceBasedSatelliteRepository {
     private var demoCommandJob: Job? = null
 
+    override val isOpportunisticSatelliteIconEnabled =
+        resources.getBoolean(R.bool.config_showOpportunisticSatelliteIcon)
+
     override val isSatelliteProvisioned = MutableStateFlow(true)
     override val connectionState = MutableStateFlow(SatelliteConnectionState.Unknown)
     override val signalStrength = MutableStateFlow(0)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
index 7686338..a36ef56 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.pipeline.satellite.data.prod
 
+import android.content.res.Resources
 import android.os.OutcomeReceiver
 import android.telephony.TelephonyCallback
 import android.telephony.TelephonyManager
@@ -27,14 +28,17 @@
 import android.telephony.satellite.SatelliteProvisionStateCallback
 import android.telephony.satellite.SatelliteSupportedStateCallback
 import androidx.annotation.VisibleForTesting
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.core.MessageInitializer
 import com.android.systemui.log.core.MessagePrinter
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.pipeline.dagger.DeviceBasedSatelliteInputLog
 import com.android.systemui.statusbar.pipeline.dagger.VerboseDeviceBasedSatelliteInputLog
 import com.android.systemui.statusbar.pipeline.satellite.data.RealDeviceBasedSatelliteRepository
@@ -66,7 +70,6 @@
 import kotlinx.coroutines.flow.mapNotNull
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
 import kotlinx.coroutines.suspendCancellableCoroutine
 import kotlinx.coroutines.withContext
 
@@ -146,10 +149,14 @@
     @DeviceBasedSatelliteInputLog private val logBuffer: LogBuffer,
     @VerboseDeviceBasedSatelliteInputLog private val verboseLogBuffer: LogBuffer,
     private val systemClock: SystemClock,
+    @Main resources: Resources,
 ) : RealDeviceBasedSatelliteRepository {
 
     private val satelliteManager: SatelliteManager?
 
+    override val isOpportunisticSatelliteIconEnabled: Boolean =
+        resources.getBoolean(R.bool.config_showOpportunisticSatelliteIcon)
+
     // Some calls into satellite manager will throw exceptions if it is not supported.
     // This is never expected to change after boot, but may need to be retried in some cases
     @get:VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
index f1a444f..08a98c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
@@ -53,6 +53,9 @@
     @DeviceBasedSatelliteInputLog private val logBuffer: LogBuffer,
     @DeviceBasedSatelliteTableLog private val tableLog: TableLogBuffer,
 ) {
+    /** Whether or not we should show the satellite icon when all connections are OOS */
+    val isOpportunisticSatelliteIconEnabled = repo.isOpportunisticSatelliteIconEnabled
+
     /** Must be observed by any UI showing Satellite iconography */
     val isSatelliteAllowed =
         if (Flags.oemEnabledSatelliteFlag()) {
@@ -93,12 +96,7 @@
                 flowOf(0)
             }
             .distinctUntilChanged()
-            .logDiffsForTable(
-                tableLog,
-                columnPrefix = "",
-                columnName = COL_LEVEL,
-                initialValue = 0,
-            )
+            .logDiffsForTable(tableLog, columnPrefix = "", columnName = COL_LEVEL, initialValue = 0)
             .stateIn(scope, SharingStarted.WhileSubscribed(), 0)
 
     val isSatelliteProvisioned = repo.isSatelliteProvisioned
@@ -132,10 +130,9 @@
     /** When all connections are considered OOS, satellite connectivity is potentially valid */
     val areAllConnectionsOutOfService =
         if (Flags.oemEnabledSatelliteFlag()) {
-                combine(
-                    allConnectionsOos,
-                    iconsInteractor.isDeviceInEmergencyCallsOnlyMode,
-                ) { connectionsOos, deviceEmergencyOnly ->
+                combine(allConnectionsOos, iconsInteractor.isDeviceInEmergencyCallsOnlyMode) {
+                    connectionsOos,
+                    deviceEmergencyOnly ->
                     logBuffer.log(
                         TAG,
                         LogLevel.INFO,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
index 37f2f19..13ac321 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
@@ -77,35 +77,38 @@
 
     // This adds a 10 seconds delay before showing the icon
     private val shouldShowIconForOosAfterHysteresis: StateFlow<Boolean> =
-        interactor.areAllConnectionsOutOfService
-            .flatMapLatest { shouldShow ->
-                if (shouldShow) {
-                    logBuffer.log(
-                        TAG,
-                        LogLevel.INFO,
-                        { long1 = DELAY_DURATION.inWholeSeconds },
-                        { "Waiting $long1 seconds before showing the satellite icon" }
+        if (interactor.isOpportunisticSatelliteIconEnabled) {
+                interactor.areAllConnectionsOutOfService
+                    .flatMapLatest { shouldShow ->
+                        if (shouldShow) {
+                            logBuffer.log(
+                                TAG,
+                                LogLevel.INFO,
+                                { long1 = DELAY_DURATION.inWholeSeconds },
+                                { "Waiting $long1 seconds before showing the satellite icon" },
+                            )
+                            delay(DELAY_DURATION)
+                            flowOf(true)
+                        } else {
+                            flowOf(false)
+                        }
+                    }
+                    .distinctUntilChanged()
+                    .logDiffsForTable(
+                        tableLog,
+                        columnPrefix = "vm",
+                        columnName = COL_VISIBLE_FOR_OOS,
+                        initialValue = false,
                     )
-                    delay(DELAY_DURATION)
-                    flowOf(true)
-                } else {
-                    flowOf(false)
-                }
+            } else {
+                flowOf(false)
             }
-            .distinctUntilChanged()
-            .logDiffsForTable(
-                tableLog,
-                columnPrefix = "vm",
-                columnName = COL_VISIBLE_FOR_OOS,
-                initialValue = false,
-            )
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     private val canShowIcon =
-        combine(
-            interactor.isSatelliteAllowed,
-            interactor.isSatelliteProvisioned,
-        ) { allowed, provisioned ->
+        combine(interactor.isSatelliteAllowed, interactor.isSatelliteProvisioned) {
+            allowed,
+            provisioned ->
             allowed && provisioned
         }
 
@@ -141,11 +144,10 @@
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     override val icon: StateFlow<Icon?> =
-        combine(
-                showIcon,
-                interactor.connectionState,
-                interactor.signalStrength,
-            ) { shouldShow, state, signalStrength ->
+        combine(showIcon, interactor.connectionState, interactor.signalStrength) {
+                shouldShow,
+                state,
+                signalStrength ->
                 if (shouldShow) {
                     SatelliteIconModel.fromConnectionState(state, signalStrength)
                 } else {
@@ -155,10 +157,7 @@
             .stateIn(scope, SharingStarted.WhileSubscribed(), null)
 
     override val carrierText: StateFlow<String?> =
-        combine(
-                showIcon,
-                interactor.connectionState,
-            ) { shouldShow, connectionState ->
+        combine(showIcon, interactor.connectionState) { shouldShow, connectionState ->
                 logBuffer.log(
                     TAG,
                     LogLevel.INFO,
@@ -166,7 +165,7 @@
                         bool1 = shouldShow
                         str1 = connectionState.name
                     },
-                    { "Updating carrier text. shouldShow=$bool1 connectionState=$str1" }
+                    { "Updating carrier text. shouldShow=$bool1 connectionState=$str1" },
                 )
                 if (shouldShow) {
                     when (connectionState) {
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 9c8ef04..1c3fece 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -242,10 +242,16 @@
         }
     };
 
-    private int getLatestWallpaperType(int userId) {
-        return mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, userId)
-                > mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, userId)
-                ? WallpaperManager.FLAG_LOCK : WallpaperManager.FLAG_SYSTEM;
+    private int getDefaultWallpaperColorsSource(int userId) {
+        if (com.android.systemui.shared.Flags.newCustomizationPickerUi()) {
+            // The wallpaper colors source is always the home wallpaper.
+            return WallpaperManager.FLAG_SYSTEM;
+        } else {
+            // The wallpaper colors source is based on the last set wallpaper.
+            return mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, userId)
+                    > mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, userId)
+                    ? WallpaperManager.FLAG_LOCK : WallpaperManager.FLAG_SYSTEM;
+        }
     }
 
     private boolean isSeedColorSet(JSONObject jsonObject, WallpaperColors newWallpaperColors) {
@@ -279,9 +285,9 @@
     private void handleWallpaperColors(WallpaperColors wallpaperColors, int flags, int userId) {
         final int currentUser = mUserTracker.getUserId();
         final boolean hadWallpaperColors = mCurrentColors.get(userId) != null;
-        int latestWallpaperType = getLatestWallpaperType(userId);
-        boolean eventForLatestWallpaper = (flags & latestWallpaperType) != 0;
-        if (eventForLatestWallpaper) {
+        int wallpaperColorsSource = getDefaultWallpaperColorsSource(userId);
+        boolean wallpaperColorsNeedUpdate = (flags & wallpaperColorsSource) != 0;
+        if (wallpaperColorsNeedUpdate) {
             mCurrentColors.put(userId, wallpaperColors);
             if (DEBUG) Log.d(TAG, "got new colors: " + wallpaperColors + " where: " + flags);
         }
@@ -328,7 +334,7 @@
             boolean userChoseLockScreenColor = COLOR_SOURCE_LOCK.equals(wallpaperPickerColorSource);
             boolean preserveLockScreenColor = isDestinationHomeOnly && userChoseLockScreenColor;
 
-            if (!userChosePresetColor && !preserveLockScreenColor && eventForLatestWallpaper
+            if (!userChosePresetColor && !preserveLockScreenColor && wallpaperColorsNeedUpdate
                     && !isSeedColorSet(jsonObject, wallpaperColors)) {
                 mSkipSettingChange = true;
                 if (jsonObject.has(OVERLAY_CATEGORY_ACCENT_COLOR) || jsonObject.has(
@@ -494,7 +500,7 @@
         // Upon boot, make sure we have the most up to date colors
         Runnable updateColors = () -> {
             WallpaperColors systemColor = mWallpaperManager.getWallpaperColors(
-                    getLatestWallpaperType(mUserTracker.getUserId()));
+                    getDefaultWallpaperColorsSource(mUserTracker.getUserId()));
             Runnable applyColors = () -> {
                 if (DEBUG) Log.d(TAG, "Boot colors: " + systemColor);
                 mCurrentColors.put(mUserTracker.getUserId(), systemColor);
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
index 3662c78..163288b 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
@@ -32,6 +32,7 @@
 import android.os.UserManager
 import android.provider.Settings
 import android.util.Log
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.internal.logging.UiEventLogger
 import com.android.internal.util.UserIcons
 import com.android.keyguard.KeyguardUpdateMonitor
@@ -81,7 +82,6 @@
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
 import kotlinx.coroutines.sync.Mutex
 import kotlinx.coroutines.sync.withLock
 import kotlinx.coroutines.withContext
@@ -109,7 +109,7 @@
     private val guestUserInteractor: GuestUserInteractor,
     private val uiEventLogger: UiEventLogger,
     private val userRestrictionChecker: UserRestrictionChecker,
-    private val processWrapper: ProcessWrapper
+    private val processWrapper: ProcessWrapper,
 ) {
     /**
      * Defines interface for classes that can be notified when the state of users on the device is
@@ -137,11 +137,10 @@
     /** List of current on-device users to select from. */
     val users: Flow<List<UserModel>>
         get() =
-            combine(
+            combine(userInfos, repository.selectedUserInfo, repository.userSwitcherSettings) {
                 userInfos,
-                repository.selectedUserInfo,
-                repository.userSwitcherSettings,
-            ) { userInfos, selectedUserInfo, settings ->
+                selectedUserInfo,
+                settings ->
                 toUserModels(
                     userInfos = userInfos,
                     selectedUserId = selectedUserInfo.id,
@@ -157,7 +156,7 @@
                 toUserModel(
                     userInfo = selectedUserInfo,
                     selectedUserId = selectedUserId,
-                    canSwitchUsers = canSwitchUsers(selectedUserId)
+                    canSwitchUsers = canSwitchUsers(selectedUserId),
                 )
             }
 
@@ -211,7 +210,7 @@
                                                 manager,
                                                 repository,
                                                 settings.isUserSwitcherEnabled,
-                                                canAccessUserSwitcher
+                                                canAccessUserSwitcher,
                                             )
 
                                         if (canCreateUsers) {
@@ -238,7 +237,7 @@
                         if (
                             UserActionsUtil.canManageUsers(
                                 repository,
-                                settings.isUserSwitcherEnabled
+                                settings.isUserSwitcherEnabled,
                             )
                         ) {
                             add(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
@@ -248,18 +247,14 @@
                 .flowOn(backgroundDispatcher)
 
     val userRecords: StateFlow<ArrayList<UserRecord>> =
-        combine(
+        combine(userInfos, repository.selectedUserInfo, actions, repository.userSwitcherSettings) {
                 userInfos,
-                repository.selectedUserInfo,
-                actions,
-                repository.userSwitcherSettings,
-            ) { userInfos, selectedUserInfo, actionModels, settings ->
+                selectedUserInfo,
+                actionModels,
+                settings ->
                 ArrayList(
                     userInfos.map {
-                        toRecord(
-                            userInfo = it,
-                            selectedUserId = selectedUserInfo.id,
-                        )
+                        toRecord(userInfo = it, selectedUserId = selectedUserInfo.id)
                     } +
                         actionModels.map {
                             toRecord(
@@ -298,7 +293,8 @@
     val isGuestUserResetting: Boolean = guestUserInteractor.isGuestUserResetting
 
     /** Whether to enable the user chip in the status bar */
-    val isStatusBarUserChipEnabled: Boolean = repository.isStatusBarUserChipEnabled
+    val isStatusBarUserChipEnabled: Boolean
+        get() = repository.isStatusBarUserChipEnabled
 
     private val _dialogShowRequests = MutableStateFlow<ShowDialogRequestModel?>(null)
     val dialogShowRequests: Flow<ShowDialogRequestModel?> = _dialogShowRequests.asStateFlow()
@@ -467,10 +463,8 @@
         when (action) {
             UserActionModel.ENTER_GUEST_MODE -> {
                 uiEventLogger.log(MultiUserActionsEvent.CREATE_GUEST_FROM_USER_SWITCHER)
-                guestUserInteractor.createAndSwitchTo(
-                    this::showDialog,
-                    this::dismissDialog,
-                ) { userId ->
+                guestUserInteractor.createAndSwitchTo(this::showDialog, this::dismissDialog) {
+                    userId ->
                     selectUser(userId, dialogShower)
                 }
             }
@@ -481,7 +475,7 @@
                 activityStarter.startActivity(
                     CreateUserActivity.createIntentForStart(
                         applicationContext,
-                        keyguardInteractor.isKeyguardShowing()
+                        keyguardInteractor.isKeyguardShowing(),
                     ),
                     /* dismissShade= */ true,
                     /* animationController */ null,
@@ -523,17 +517,14 @@
         )
     }
 
-    fun removeGuestUser(
-        @UserIdInt guestUserId: Int,
-        @UserIdInt targetUserId: Int,
-    ) {
+    fun removeGuestUser(@UserIdInt guestUserId: Int, @UserIdInt targetUserId: Int) {
         applicationScope.launch {
             guestUserInteractor.remove(
                 guestUserId = guestUserId,
                 targetUserId = targetUserId,
                 ::showDialog,
                 ::dismissDialog,
-                ::switchUser
+                ::switchUser,
             )
         }
     }
@@ -570,10 +561,7 @@
         }
     }
 
-    private suspend fun toRecord(
-        userInfo: UserInfo,
-        selectedUserId: Int,
-    ): UserRecord {
+    private suspend fun toRecord(userInfo: UserInfo, selectedUserId: Int): UserRecord {
         return LegacyUserDataHelper.createRecord(
             context = applicationContext,
             manager = manager,
@@ -595,10 +583,7 @@
             actionType = action,
             isRestricted = isRestricted,
             isSwitchToEnabled =
-                canSwitchUsers(
-                    selectedUserId = selectedUserId,
-                    isAction = true,
-                ) &&
+                canSwitchUsers(selectedUserId = selectedUserId, isAction = true) &&
                     // If the user is auto-created is must not be currently resetting.
                     !(isGuestUserAutoCreated && isGuestUserResetting),
             userRestrictionChecker = userRestrictionChecker,
@@ -623,10 +608,7 @@
         }
     }
 
-    private suspend fun onBroadcastReceived(
-        intent: Intent,
-        previousUserInfo: UserInfo?,
-    ) {
+    private suspend fun onBroadcastReceived(intent: Intent, previousUserInfo: UserInfo?) {
         val shouldRefreshAllUsers =
             when (intent.action) {
                 Intent.ACTION_LOCALE_CHANGED -> true
@@ -645,10 +627,8 @@
                 Intent.ACTION_USER_INFO_CHANGED -> true
                 Intent.ACTION_USER_UNLOCKED -> {
                     // If we unlocked the system user, we should refresh all users.
-                    intent.getIntExtra(
-                        Intent.EXTRA_USER_HANDLE,
-                        UserHandle.USER_NULL,
-                    ) == UserHandle.USER_SYSTEM
+                    intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL) ==
+                        UserHandle.USER_SYSTEM
                 }
                 else -> true
             }
@@ -668,20 +648,14 @@
         // Disconnect from the old secondary user's service
         val secondaryUserId = repository.secondaryUserId
         if (secondaryUserId != UserHandle.USER_NULL) {
-            applicationContext.stopServiceAsUser(
-                intent,
-                UserHandle.of(secondaryUserId),
-            )
+            applicationContext.stopServiceAsUser(intent, UserHandle.of(secondaryUserId))
             repository.secondaryUserId = UserHandle.USER_NULL
         }
 
         // Connect to the new secondary user's service (purely to ensure that a persistent
         // SystemUI application is created for that user)
         if (userId != processWrapper.myUserHandle().identifier && !processWrapper.isSystemUser) {
-            applicationContext.startServiceAsUser(
-                intent,
-                UserHandle.of(userId),
-            )
+            applicationContext.startServiceAsUser(intent, UserHandle.of(userId))
             repository.secondaryUserId = userId
         }
     }
@@ -732,7 +706,7 @@
     private suspend fun toUserModel(
         userInfo: UserInfo,
         selectedUserId: Int,
-        canSwitchUsers: Boolean
+        canSwitchUsers: Boolean,
     ): UserModel {
         val userId = userInfo.id
         val isSelected = userId == selectedUserId
@@ -740,11 +714,7 @@
             UserModel(
                 id = userId,
                 name = Text.Loaded(userInfo.name),
-                image =
-                    getUserImage(
-                        isGuest = true,
-                        userId = userId,
-                    ),
+                image = getUserImage(isGuest = true, userId = userId),
                 isSelected = isSelected,
                 isSelectable = canSwitchUsers,
                 isGuest = true,
@@ -753,11 +723,7 @@
             UserModel(
                 id = userId,
                 name = Text.Loaded(userInfo.name),
-                image =
-                    getUserImage(
-                        isGuest = false,
-                        userId = userId,
-                    ),
+                image = getUserImage(isGuest = false, userId = userId),
                 isSelected = isSelected,
                 isSelectable = canSwitchUsers || isSelected,
                 isGuest = false,
@@ -765,10 +731,7 @@
         }
     }
 
-    private suspend fun canSwitchUsers(
-        selectedUserId: Int,
-        isAction: Boolean = false,
-    ): Boolean {
+    private suspend fun canSwitchUsers(selectedUserId: Int, isAction: Boolean = false): Boolean {
         val isHeadlessSystemUserMode =
             withContext(backgroundDispatcher) { headlessSystemUserMode.isHeadlessSystemUserMode() }
         // Whether menu item should be active. True if item is a user or if any user has
@@ -785,7 +748,7 @@
             .getUsers(
                 /* excludePartial= */ true,
                 /* excludeDying= */ true,
-                /* excludePreCreated= */ true
+                /* excludePreCreated= */ true,
             )
             .any { user ->
                 user.id != UserHandle.USER_SYSTEM &&
@@ -794,10 +757,7 @@
     }
 
     @SuppressLint("UseCompatLoadingForDrawables")
-    private suspend fun getUserImage(
-        isGuest: Boolean,
-        userId: Int,
-    ): Drawable {
+    private suspend fun getUserImage(isGuest: Boolean, userId: Int): Drawable {
         if (isGuest) {
             return checkNotNull(
                 applicationContext.getDrawable(com.android.settingslib.R.drawable.ic_account_circle)
@@ -823,13 +783,13 @@
         return UserIcons.getDefaultUserIcon(
             applicationContext.resources,
             userId,
-            /* light= */ false
+            /* light= */ false,
         )
     }
 
     private fun canCreateGuestUser(
         settings: UserSwitcherSettingsModel,
-        canAccessUserSwitcher: Boolean
+        canAccessUserSwitcher: Boolean,
     ): Boolean {
         return guestUserInteractor.isGuestUserAutoCreated ||
             UserActionsUtil.canCreateGuest(
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt
index 2c425b19..53c2d88 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt
@@ -30,11 +30,10 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 class StatusBarUserChipViewModel
 @Inject
-constructor(
-    interactor: UserSwitcherInteractor,
-) {
+constructor(private val interactor: UserSwitcherInteractor) {
     /** Whether the status bar chip ui should be available */
-    val chipEnabled: Boolean = interactor.isStatusBarUserChipEnabled
+    val chipEnabled: Boolean
+        get() = interactor.isStatusBarUserChipEnabled
 
     /** Whether or not the chip should be showing, based on the number of users */
     val isChipVisible: Flow<Boolean> =
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
index 3159124..63a5b3f 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
@@ -20,6 +20,7 @@
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.coroutineScope
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.lifecycle.repeatWhenAttached
@@ -35,7 +36,7 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
+import kotlinx.coroutines.plus
 
 /** A class allowing Java classes to collect on Kotlin flows. */
 @SysUISingleton
@@ -102,6 +103,22 @@
     }
 }
 
+/**
+ * Collect information for the given [flow], calling [consumer] for each emitted event on the
+ * specified [collectContext].
+ *
+ * Collection will continue until the given [scope] is cancelled.
+ */
+@JvmOverloads
+fun <T> collectFlow(
+    scope: CoroutineScope,
+    collectContext: CoroutineContext = scope.coroutineContext,
+    flow: Flow<T>,
+    consumer: Consumer<T>,
+): Job {
+    return scope.plus(collectContext).launch { flow.collect { consumer.accept(it) } }
+}
+
 fun <A, B, R> combineFlows(flow1: Flow<A>, flow2: Flow<B>, bifunction: (A, B) -> R): Flow<R> {
     return combine(flow1, flow2, bifunction)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java
index d509b2d..f36c335e 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java
@@ -16,9 +16,6 @@
 
 package com.android.systemui.util.settings;
 
-import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository;
-import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepositoryImpl;
-
 import dagger.Binds;
 import dagger.Module;
 
@@ -39,9 +36,4 @@
     /** Bind GlobalSettingsImpl to GlobalSettings. */
     @Binds
     GlobalSettings bindsGlobalSettings(GlobalSettingsImpl impl);
-
-    /** Bind UserAwareSecureSettingsRepositoryImpl to UserAwareSecureSettingsRepository. */
-    @Binds
-    UserAwareSecureSettingsRepository bindsUserAwareSecureSettingsRepository(
-            UserAwareSecureSettingsRepositoryImpl impl);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.kt
index d3e5080..71335ec 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.kt
@@ -19,52 +19,25 @@
 import android.provider.Settings
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
 import com.android.systemui.user.data.repository.UserRepository
 import com.android.systemui.util.settings.SecureSettings
-import com.android.systemui.util.settings.SettingsProxy
-import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onStart
 import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineDispatcher
 
 /**
  * Repository for observing values of [Settings.Secure] for the currently active user. That means
  * when user is switched and the new user has different value, flow will emit new value.
  */
-interface UserAwareSecureSettingsRepository {
-
-    /**
-     * Emits boolean value of the setting for active user. Also emits starting value when
-     * subscribed.
-     * See: [SettingsProxy.getBool].
-     */
-    fun boolSettingForActiveUser(name: String, defaultValue: Boolean = false): Flow<Boolean>
-}
-
 @SysUISingleton
-@OptIn(ExperimentalCoroutinesApi::class)
-class UserAwareSecureSettingsRepositoryImpl @Inject constructor(
-    private val secureSettings: SecureSettings,
-    private val userRepository: UserRepository,
-    @Background private val backgroundDispatcher: CoroutineDispatcher,
-) : UserAwareSecureSettingsRepository {
-
-    override fun boolSettingForActiveUser(name: String, defaultValue: Boolean): Flow<Boolean> =
-        userRepository.selectedUserInfo
-            .flatMapLatest { userInfo -> settingObserver(name, defaultValue, userInfo.id) }
-            .distinctUntilChanged()
-            .flowOn(backgroundDispatcher)
-
-    private fun settingObserver(name: String, defaultValue: Boolean, userId: Int): Flow<Boolean> {
-        return secureSettings
-            .observerFlow(userId, name)
-            .onStart { emit(Unit) }
-            .map { secureSettings.getBoolForUser(name, defaultValue, userId) }
-    }
-}
\ No newline at end of file
+class UserAwareSecureSettingsRepository
+@Inject
+constructor(
+    secureSettings: SecureSettings,
+    userRepository: UserRepository,
+    @Background backgroundDispatcher: CoroutineDispatcher,
+    @Background bgContext: CoroutineContext,
+) :
+    UserAwareSettingsRepository(secureSettings, userRepository, backgroundDispatcher, bgContext),
+    SecureSettingsRepository
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSettingsRepository.kt
new file mode 100644
index 0000000..a31b8d9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSettingsRepository.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2024 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.util.settings.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import com.android.systemui.util.settings.UserSettingsProxy
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.withContext
+
+/**
+ * Repository for observing values of a [UserSettingsProxy], for the currently active user. That
+ * means that when user is switched and the new user has a different value, the flow will emit the
+ * new value.
+ */
+@SysUISingleton
+@OptIn(ExperimentalCoroutinesApi::class)
+abstract class UserAwareSettingsRepository(
+    private val userSettings: UserSettingsProxy,
+    private val userRepository: UserRepository,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+    @Background private val bgContext: CoroutineContext,
+) {
+
+    fun boolSetting(name: String, defaultValue: Boolean): Flow<Boolean> =
+        userRepository.selectedUserInfo
+            .flatMapLatest { userInfo ->
+                settingObserver(name, userInfo.id) {
+                    userSettings.getBoolForUser(name, defaultValue, userInfo.id)
+                }
+            }
+            .distinctUntilChanged()
+            .flowOn(backgroundDispatcher)
+
+    fun intSetting(name: String, defaultValue: Int): Flow<Int> {
+        return userRepository.selectedUserInfo
+            .flatMapLatest { userInfo ->
+                settingObserver(name, userInfo.id) {
+                    userSettings.getIntForUser(name, defaultValue, userInfo.id)
+                }
+            }
+            .distinctUntilChanged()
+            .flowOn(backgroundDispatcher)
+    }
+
+    private fun <T> settingObserver(name: String, userId: Int, settingsReader: () -> T): Flow<T> {
+        return userSettings
+            .observerFlow(userId, name)
+            .onStart { emit(Unit) }
+            .map { settingsReader.invoke() }
+    }
+
+    suspend fun setInt(name: String, value: Int) {
+        withContext(bgContext) {
+            userSettings.putIntForUser(name, value, userRepository.getSelectedUserInfo().id)
+        }
+    }
+
+    suspend fun getInt(name: String, defaultValue: Int): Int {
+        return withContext(bgContext) {
+            userSettings.getIntForUser(name, defaultValue, userRepository.getSelectedUserInfo().id)
+        }
+    }
+
+    suspend fun getString(name: String): String? {
+        return withContext(bgContext) {
+            userSettings.getStringForUser(name, userRepository.getSelectedUserInfo().id)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSystemSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSystemSettingsRepository.kt
new file mode 100644
index 0000000..8b1fca5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSystemSettingsRepository.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 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.util.settings.repository
+
+import android.provider.Settings
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.shared.settings.data.repository.SystemSettingsRepository
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.settings.SystemSettings
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineDispatcher
+
+/**
+ * Repository for observing values of [Settings.Secure] for the currently active user. That means
+ * when user is switched and the new user has different value, flow will emit new value.
+ */
+@SysUISingleton
+class UserAwareSystemSettingsRepository
+@Inject
+constructor(
+    systemSettings: SystemSettings,
+    userRepository: UserRepository,
+    @Background backgroundDispatcher: CoroutineDispatcher,
+    @Background bgContext: CoroutineContext,
+) :
+    UserAwareSettingsRepository(systemSettings, userRepository, backgroundDispatcher, bgContext),
+    SystemSettingsRepository
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
index 617aaa7..d5b8597 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
@@ -53,7 +53,9 @@
         fun provideAudioManagerIntentsReceiver(
             @Application context: Context,
             @Application coroutineScope: CoroutineScope,
-        ): AudioManagerEventsReceiver = AudioManagerEventsReceiverImpl(context, coroutineScope)
+            @Background coroutineContext: CoroutineContext,
+        ): AudioManagerEventsReceiver =
+            AudioManagerEventsReceiverImpl(context, coroutineScope, coroutineContext)
 
         @Provides
         @SysUISingleton
@@ -82,7 +84,7 @@
             localBluetoothManager: LocalBluetoothManager?,
             @Application coroutineScope: CoroutineScope,
             @Background coroutineContext: CoroutineContext,
-            volumeLogger: VolumeLogger
+            volumeLogger: VolumeLogger,
         ): AudioSharingRepository =
             if (Flags.enableLeAudioSharing() && localBluetoothManager != null) {
                 AudioSharingRepositoryImpl(
@@ -90,7 +92,7 @@
                     localBluetoothManager,
                     coroutineScope,
                     coroutineContext,
-                    volumeLogger
+                    volumeLogger,
                 )
             } else {
                 AudioSharingRepositoryEmptyImpl()
@@ -111,8 +113,7 @@
 
         @Provides
         @SysUISingleton
-        fun provideAudioSystemRepository(
-            @Application context: Context,
-        ): AudioSystemRepository = AudioSystemRepositoryImpl(context)
+        fun provideAudioSystemRepository(@Application context: Context): AudioSystemRepository =
+            AudioSystemRepositoryImpl(context)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt
index f7ad320..9440a93 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt
@@ -18,6 +18,7 @@
 
 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
+import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderComponent
 import dagger.BindsInstance
 import dagger.Subcomponent
 import kotlinx.coroutines.CoroutineScope
@@ -40,6 +41,8 @@
 
     @VolumeDialogScope fun volumeDialog(): com.android.systemui.volume.dialog.VolumeDialog
 
+    fun sliderComponentFactory(): VolumeDialogSliderComponent.Factory
+
     @Subcomponent.Factory
     interface Factory {
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt
new file mode 100644
index 0000000..538ee47
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.sliders.dagger
+
+import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType
+import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderViewBinder
+import dagger.BindsInstance
+import dagger.Subcomponent
+
+/**
+ * This component hosts all the stuff, that Volume Dialog sliders need. It's recreated alongside
+ * each slider view.
+ */
+@VolumeDialogSliderScope
+@Subcomponent
+interface VolumeDialogSliderComponent {
+
+    fun sliderViewBinder(): VolumeDialogSliderViewBinder
+
+    @Subcomponent.Factory
+    interface Factory {
+
+        fun create(@BindsInstance sliderType: VolumeDialogSliderType): VolumeDialogSliderComponent
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderScope.kt
similarity index 65%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderScope.kt
index 94b2bdf..9f5e0f6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderScope.kt
@@ -14,9 +14,15 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util.settings
+package com.android.systemui.volume.dialog.sliders.dagger
 
-import com.android.systemui.kosmos.Kosmos
+import javax.inject.Scope
 
-val Kosmos.userAwareSecureSettingsRepository by
-    Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+/**
+ * Volume Panel Slider dependency injection scope. This scope is created for each of the volume
+ * sliders in the dialog.
+ */
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+@Scope
+annotation class VolumeDialogSliderScope
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt
index 876bf2c..2967fe8 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt
@@ -17,22 +17,21 @@
 package com.android.systemui.volume.dialog.sliders.domain.interactor
 
 import com.android.systemui.plugins.VolumeDialogController
-import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
 import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogStateInteractor
 import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel
+import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope
 import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType
-import dagger.assisted.Assisted
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
+import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.mapNotNull
 
 /** Operates a state of particular slider of the Volume Dialog. */
+@VolumeDialogSliderScope
 class VolumeDialogSliderInteractor
-@AssistedInject
+@Inject
 constructor(
-    @Assisted private val sliderType: VolumeDialogSliderType,
+    private val sliderType: VolumeDialogSliderType,
     volumeDialogStateInteractor: VolumeDialogStateInteractor,
     private val volumeDialogController: VolumeDialogController,
 ) {
@@ -56,11 +55,4 @@
             setActiveStream(sliderType.audioStream)
         }
     }
-
-    @VolumeDialogScope
-    @AssistedFactory
-    interface Factory {
-
-        fun create(sliderType: VolumeDialogSliderType): VolumeDialogSliderInteractor
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
index 3bf8c54..1c231b5 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
@@ -24,16 +24,14 @@
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.lifecycle.viewModel
 import com.android.systemui.res.R
-import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
 import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel
+import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope
 import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderViewModel
 import com.android.systemui.volume.dialog.ui.utils.JankListenerFactory
 import com.android.systemui.volume.dialog.ui.utils.awaitAnimation
 import com.google.android.material.slider.LabelFormatter
 import com.google.android.material.slider.Slider
-import dagger.assisted.Assisted
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
+import javax.inject.Inject
 import kotlin.math.roundToInt
 import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.flow.launchIn
@@ -41,10 +39,11 @@
 
 private const val PROGRESS_CHANGE_ANIMATION_DURATION_MS = 80L
 
+@VolumeDialogSliderScope
 class VolumeDialogSliderViewBinder
-@AssistedInject
+@Inject
 constructor(
-    @Assisted private val viewModelProvider: () -> VolumeDialogSliderViewModel,
+    private val viewModelFactory: VolumeDialogSliderViewModel.Factory,
     private val jankListenerFactory: JankListenerFactory,
 ) {
 
@@ -58,7 +57,7 @@
                 viewModel(
                     traceName = "VolumeDialogSliderViewBinder",
                     minWindowLifecycleState = WindowLifecycleState.ATTACHED,
-                    factory = { viewModelProvider() },
+                    factory = { viewModelFactory.create() },
                 ) { viewModel ->
                     sliderView.addOnChangeListener { _, value, fromUser ->
                         viewModel.setStreamVolume(value.roundToInt(), fromUser)
@@ -85,15 +84,6 @@
             )
         }
     }
-
-    @AssistedFactory
-    @VolumeDialogScope
-    interface Factory {
-
-        fun create(
-            viewModelProvider: () -> VolumeDialogSliderViewModel
-        ): VolumeDialogSliderViewBinder
-    }
 }
 
 private suspend fun Slider.setValueAnimated(
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
index 0a4e3f4..a17c1e5 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
@@ -28,7 +28,6 @@
 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
 import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSlidersViewModel
 import javax.inject.Inject
-import kotlin.math.abs
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 
@@ -50,15 +49,17 @@
                 ) { viewModel ->
                     viewModel.sliders
                         .onEach { uiModel ->
-                            uiModel.sliderViewBinder.bind(volumeDialog)
+                            uiModel.sliderComponent.sliderViewBinder().bind(volumeDialog)
 
-                            val floatingSliderViewBinders = uiModel.floatingSliderViewBinders
+                            val floatingSliderViewBinders = uiModel.floatingSliderComponent
                             floatingSlidersContainer.ensureChildCount(
                                 viewLayoutId = R.layout.volume_dialog_slider_floating,
                                 count = floatingSliderViewBinders.size,
                             )
-                            floatingSliderViewBinders.fastForEachIndexed { index, viewBinder ->
-                                viewBinder.bind(floatingSlidersContainer.getChildAt(index))
+                            floatingSliderViewBinders.fastForEachIndexed { index, sliderComponent ->
+                                sliderComponent
+                                    .sliderViewBinder()
+                                    .bind(floatingSlidersContainer.getChildAt(index))
                             }
                         }
                         .launchIn(this)
@@ -76,7 +77,7 @@
         }
         childCountDelta < 0 -> {
             val inflater = LayoutInflater.from(context)
-            repeat(abs(childCountDelta)) { inflater.inflate(viewLayoutId, this, true) }
+            repeat(-childCountDelta) { inflater.inflate(viewLayoutId, this, true) }
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
index ea0b49d..cf04d45 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
@@ -22,7 +22,6 @@
 import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
 import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel
 import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInteractor
-import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import kotlinx.coroutines.CoroutineScope
@@ -53,7 +52,7 @@
 class VolumeDialogSliderViewModel
 @AssistedInject
 constructor(
-    @Assisted private val interactor: VolumeDialogSliderInteractor,
+    private val interactor: VolumeDialogSliderInteractor,
     private val visibilityInteractor: VolumeDialogVisibilityInteractor,
     @VolumeDialog private val coroutineScope: CoroutineScope,
     private val systemClock: SystemClock,
@@ -90,11 +89,11 @@
 
     private fun getTimestampMillis(): Long = systemClock.uptimeMillis()
 
+    private data class VolumeUpdate(val newVolumeLevel: Int, val timestampMillis: Long)
+
     @AssistedFactory
     interface Factory {
 
-        fun create(interactor: VolumeDialogSliderInteractor): VolumeDialogSliderViewModel
+        fun create(): VolumeDialogSliderViewModel
     }
-
-    private data class VolumeUpdate(val newVolumeLevel: Int, val timestampMillis: Long)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt
index b5b292f..d197223 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt
@@ -17,16 +17,13 @@
 package com.android.systemui.volume.dialog.sliders.ui.viewmodel
 
 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
-import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInteractor
+import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderComponent
 import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSlidersInteractor
-import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType
-import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderViewBinder
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
@@ -36,29 +33,21 @@
 constructor(
     @VolumeDialog coroutineScope: CoroutineScope,
     private val slidersInteractor: VolumeDialogSlidersInteractor,
-    private val sliderInteractorFactory: VolumeDialogSliderInteractor.Factory,
-    private val sliderViewModelFactory: VolumeDialogSliderViewModel.Factory,
-    private val sliderViewBinderFactory: VolumeDialogSliderViewBinder.Factory,
+    private val sliderComponentFactory: VolumeDialogSliderComponent.Factory,
 ) {
 
     val sliders: Flow<VolumeDialogSliderUiModel> =
         slidersInteractor.sliders
-            .distinctUntilChanged()
             .map { slidersModel ->
                 VolumeDialogSliderUiModel(
-                    sliderViewBinder = createSliderViewBinder(slidersModel.slider),
-                    floatingSliderViewBinders =
-                        slidersModel.floatingSliders.map(::createSliderViewBinder),
+                    sliderComponent = sliderComponentFactory.create(slidersModel.slider),
+                    floatingSliderComponent =
+                        slidersModel.floatingSliders.map(sliderComponentFactory::create),
                 )
             }
             .stateIn(coroutineScope, SharingStarted.Eagerly, null)
             .filterNotNull()
 
-    private fun createSliderViewBinder(type: VolumeDialogSliderType): VolumeDialogSliderViewBinder =
-        sliderViewBinderFactory.create {
-            sliderViewModelFactory.create(sliderInteractorFactory.create(type))
-        }
-
     @AssistedFactory
     interface Factory {
 
@@ -68,6 +57,6 @@
 
 /** Models slider ui */
 data class VolumeDialogSliderUiModel(
-    val sliderViewBinder: VolumeDialogSliderViewBinder,
-    val floatingSliderViewBinders: List<VolumeDialogSliderViewBinder>,
+    val sliderComponent: VolumeDialogSliderComponent,
+    val floatingSliderComponent: List<VolumeDialogSliderComponent>,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt
index 9be669f..869a6a2 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt
@@ -18,16 +18,19 @@
 
 import android.content.Context
 import com.android.systemui.res.R
+import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogStateInteractor
 import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
+import com.android.systemui.volume.dialog.shared.model.VolumeDialogStateModel
 import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityModel
 import com.android.systemui.volume.dialog.shared.model.streamLabel
-import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInteractor
 import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSlidersInteractor
+import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.map
 
 /** Provides a state for the Volume Dialog. */
@@ -38,18 +41,21 @@
     private val context: Context,
     dialogVisibilityInteractor: VolumeDialogVisibilityInteractor,
     volumeDialogSlidersInteractor: VolumeDialogSlidersInteractor,
-    private val volumeDialogSliderInteractorFactory: VolumeDialogSliderInteractor.Factory,
+    volumeDialogStateInteractor: VolumeDialogStateInteractor,
 ) {
 
     val dialogVisibilityModel: Flow<VolumeDialogVisibilityModel> =
         dialogVisibilityInteractor.dialogVisibility
     val dialogTitle: Flow<String> =
-        volumeDialogSlidersInteractor.sliders.flatMapLatest { slidersModel ->
-            val interactor = volumeDialogSliderInteractorFactory.create(slidersModel.slider)
-            interactor.slider.map { sliderModel ->
-                context.getString(R.string.volume_dialog_title, sliderModel.streamLabel(context))
+        combine(
+                volumeDialogStateInteractor.volumeDialogState,
+                volumeDialogSlidersInteractor.sliders.map { it.slider },
+            ) { state: VolumeDialogStateModel, sliderType: VolumeDialogSliderType ->
+                state.streamModels[sliderType.audioStream]?.let { model ->
+                    context.getString(R.string.volume_dialog_title, model.streamLabel(context))
+                }
             }
-        }
+            .filterNotNull()
 
     @AssistedFactory
     interface Factory {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractor.kt
index dacd6c7..b9f47d7 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractor.kt
@@ -22,15 +22,17 @@
 import android.media.session.PlaybackState
 import android.os.Bundle
 import android.os.Handler
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
 import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaControllerChangeModel
 import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.channels.ProducerScope
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
-import com.android.app.tracing.coroutines.launchTraced as launch
+import kotlinx.coroutines.flow.flowOn
 
 interface MediaControllerInteractor {
 
@@ -43,14 +45,16 @@
 @Inject
 constructor(
     @Background private val backgroundHandler: Handler,
+    @Background private val backgroundCoroutineContext: CoroutineContext,
 ) : MediaControllerInteractor {
 
     override fun stateChanges(mediaController: MediaController): Flow<MediaControllerChangeModel> {
         return conflatedCallbackFlow {
-            val callback = MediaControllerCallbackProducer(this)
-            mediaController.registerCallback(callback, backgroundHandler)
-            awaitClose { mediaController.unregisterCallback(callback) }
-        }
+                val callback = MediaControllerCallbackProducer(this)
+                mediaController.registerCallback(callback, backgroundHandler)
+                awaitClose { mediaController.unregisterCallback(callback) }
+            }
+            .flowOn(backgroundCoroutineContext)
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
index aa07cfd..b3848a6 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
@@ -20,10 +20,12 @@
 import android.media.VolumeProvider
 import android.media.session.MediaController
 import android.util.Log
+import androidx.annotation.WorkerThread
 import com.android.settingslib.media.MediaDevice
 import com.android.settingslib.volume.data.repository.LocalMediaRepository
 import com.android.settingslib.volume.data.repository.MediaControllerRepository
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.concurrency.Execution
 import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactory
 import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSessions
 import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
@@ -62,6 +64,7 @@
     @Background private val backgroundCoroutineContext: CoroutineContext,
     mediaControllerRepository: MediaControllerRepository,
     private val mediaControllerInteractor: MediaControllerInteractor,
+    private val execution: Execution,
 ) {
 
     private val activeMediaControllers: Flow<MediaControllers> =
@@ -82,9 +85,10 @@
             .map {
                 MediaDeviceSessions(
                     local = it.local?.mediaDeviceSession(),
-                    remote = it.remote?.mediaDeviceSession()
+                    remote = it.remote?.mediaDeviceSession(),
                 )
             }
+            .flowOn(backgroundCoroutineContext)
             .stateIn(coroutineScope, SharingStarted.Eagerly, MediaDeviceSessions(null, null))
 
     /** Returns the default [MediaDeviceSession] from [activeMediaDeviceSessions] */
@@ -115,55 +119,43 @@
     val currentConnectedDevice: Flow<MediaDevice?> =
         localMediaRepository.flatMapLatest { it.currentConnectedDevice }.distinctUntilChanged()
 
-    private suspend fun getApplicationLabel(packageName: String): CharSequence? {
-        return try {
-            withContext(backgroundCoroutineContext) {
-                val appInfo =
-                    packageManager.getApplicationInfo(
-                        packageName,
-                        PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_ANY_USER
-                    )
-                appInfo.loadLabel(packageManager)
-            }
-        } catch (e: PackageManager.NameNotFoundException) {
-            Log.e(TAG, "Unable to find info for package: $packageName")
-            null
-        }
-    }
-
     /** Finds local and remote media controllers. */
-    private fun getMediaControllers(
-        controllers: Collection<MediaController>,
-    ): MediaControllers {
-        var localController: MediaController? = null
-        var remoteController: MediaController? = null
-        val remoteMediaSessions: MutableSet<String> = mutableSetOf()
-        for (controller in controllers) {
-            val playbackInfo: MediaController.PlaybackInfo = controller.playbackInfo ?: continue
-            when (playbackInfo.playbackType) {
-                MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE -> {
-                    // MediaController can't be local if there is a remote one for the same package
-                    if (localController?.packageName.equals(controller.packageName)) {
-                        localController = null
+    private suspend fun getMediaControllers(
+        controllers: Collection<MediaController>
+    ): MediaControllers =
+        withContext(backgroundCoroutineContext) {
+            var localController: MediaController? = null
+            var remoteController: MediaController? = null
+            val remoteMediaSessions: MutableSet<String> = mutableSetOf()
+            for (controller in controllers) {
+                val playbackInfo: MediaController.PlaybackInfo = controller.playbackInfo ?: continue
+                when (playbackInfo.playbackType) {
+                    MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE -> {
+                        // MediaController can't be local if there is a remote one for the same
+                        // package
+                        if (localController?.packageName.equals(controller.packageName)) {
+                            localController = null
+                        }
+                        if (!remoteMediaSessions.contains(controller.packageName)) {
+                            remoteMediaSessions.add(controller.packageName)
+                            remoteController = chooseController(remoteController, controller)
+                        }
                     }
-                    if (!remoteMediaSessions.contains(controller.packageName)) {
-                        remoteMediaSessions.add(controller.packageName)
-                        remoteController = chooseController(remoteController, controller)
+                    MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL -> {
+                        if (controller.packageName in remoteMediaSessions) continue
+                        localController = chooseController(localController, controller)
                     }
                 }
-                MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL -> {
-                    if (controller.packageName in remoteMediaSessions) continue
-                    localController = chooseController(localController, controller)
-                }
             }
+            MediaControllers(local = localController, remote = remoteController)
         }
-        return MediaControllers(local = localController, remote = remoteController)
-    }
 
+    @WorkerThread
     private fun chooseController(
         currentController: MediaController?,
         newController: MediaController,
     ): MediaController {
+        require(!execution.isMainThread())
         if (currentController == null) {
             return newController
         }
@@ -175,12 +167,26 @@
         return currentController
     }
 
-    private suspend fun MediaController.mediaDeviceSession(): MediaDeviceSession? {
+    @WorkerThread
+    private fun MediaController.mediaDeviceSession(): MediaDeviceSession? {
+        require(!execution.isMainThread())
+        val applicationLabel =
+            try {
+                packageManager
+                    .getApplicationInfo(
+                        packageName,
+                        PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_ANY_USER,
+                    )
+                    .loadLabel(packageManager)
+            } catch (e: PackageManager.NameNotFoundException) {
+                Log.e(TAG, "Unable to find info for package: $packageName")
+                null
+            } ?: return null
         return MediaDeviceSession(
             packageName = packageName,
             sessionToken = sessionToken,
             canAdjustVolume = playbackInfo.volumeControl != VolumeProvider.VOLUME_CONTROL_FIXED,
-            appLabel = getApplicationLabel(packageName) ?: return null
+            appLabel = applicationLabel,
         )
     }
 
@@ -195,10 +201,7 @@
             .onStart { emit(this@stateChanges) }
     }
 
-    private data class MediaControllers(
-        val local: MediaController?,
-        val remote: MediaController?,
-    )
+    private data class MediaControllers(val local: MediaController?, val remote: MediaController?)
 
     private companion object {
         const val TAG = "MediaOutputInteractor"
diff --git a/packages/SystemUI/tests/goldens/backgroundAnimation_whenLaunching.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenLaunching_withAnimator.json
similarity index 100%
rename from packages/SystemUI/tests/goldens/backgroundAnimation_whenLaunching.json
rename to packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenLaunching_withAnimator.json
diff --git a/packages/SystemUI/tests/goldens/backgroundAnimation_whenLaunching_withSpring.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenLaunching_withSpring.json
similarity index 82%
rename from packages/SystemUI/tests/goldens/backgroundAnimation_whenLaunching_withSpring.json
rename to packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenLaunching_withSpring.json
index a840d3c..7abff2c 100644
--- a/packages/SystemUI/tests/goldens/backgroundAnimation_whenLaunching_withSpring.json
+++ b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenLaunching_withSpring.json
@@ -33,118 +33,118 @@
           "bottom": 0
         },
         {
-          "left": 94,
-          "top": 284,
-          "right": 206,
+          "left": 104,
+          "top": 285,
+          "right": 215,
           "bottom": 414
         },
         {
-          "left": 83,
-          "top": 251,
-          "right": 219,
+          "left": 92,
+          "top": 252,
+          "right": 227,
           "bottom": 447
         },
         {
-          "left": 70,
-          "top": 212,
-          "right": 234,
-          "bottom": 485
+          "left": 77,
+          "top": 213,
+          "right": 242,
+          "bottom": 486
         },
         {
-          "left": 57,
-          "top": 173,
-          "right": 250,
-          "bottom": 522
+          "left": 63,
+          "top": 175,
+          "right": 256,
+          "bottom": 524
         },
         {
-          "left": 46,
-          "top": 139,
-          "right": 264,
-          "bottom": 555
+          "left": 50,
+          "top": 141,
+          "right": 269,
+          "bottom": 558
         },
         {
-          "left": 36,
-          "top": 109,
-          "right": 276,
-          "bottom": 584
+          "left": 40,
+          "top": 112,
+          "right": 279,
+          "bottom": 587
         },
         {
-          "left": 28,
-          "top": 84,
-          "right": 285,
-          "bottom": 608
+          "left": 31,
+          "top": 88,
+          "right": 288,
+          "bottom": 611
         },
         {
-          "left": 21,
-          "top": 65,
-          "right": 293,
-          "bottom": 627
+          "left": 23,
+          "top": 68,
+          "right": 296,
+          "bottom": 631
         },
         {
-          "left": 16,
-          "top": 49,
-          "right": 300,
-          "bottom": 642
+          "left": 18,
+          "top": 53,
+          "right": 301,
+          "bottom": 646
         },
         {
-          "left": 12,
-          "top": 36,
-          "right": 305,
-          "bottom": 653
+          "left": 13,
+          "top": 41,
+          "right": 306,
+          "bottom": 658
         },
         {
-          "left": 9,
-          "top": 27,
-          "right": 308,
-          "bottom": 662
+          "left": 10,
+          "top": 31,
+          "right": 309,
+          "bottom": 667
         },
         {
           "left": 7,
-          "top": 20,
+          "top": 24,
           "right": 312,
-          "bottom": 669
+          "bottom": 673
         },
         {
           "left": 5,
-          "top": 14,
+          "top": 18,
           "right": 314,
-          "bottom": 675
-        },
-        {
-          "left": 4,
-          "top": 11,
-          "right": 315,
           "bottom": 678
         },
         {
-          "left": 3,
-          "top": 8,
-          "right": 316,
+          "left": 4,
+          "top": 13,
+          "right": 315,
           "bottom": 681
         },
         {
-          "left": 2,
-          "top": 5,
-          "right": 317,
+          "left": 3,
+          "top": 10,
+          "right": 316,
           "bottom": 684
         },
         {
+          "left": 2,
+          "top": 7,
+          "right": 317,
+          "bottom": 685
+        },
+        {
+          "left": 1,
+          "top": 5,
+          "right": 318,
+          "bottom": 687
+        },
+        {
           "left": 1,
           "top": 4,
           "right": 318,
-          "bottom": 685
-        },
-        {
-          "left": 1,
-          "top": 3,
-          "right": 318,
-          "bottom": 686
+          "bottom": 688
         },
         {
           "left": 0,
-          "top": 2,
+          "top": 3,
           "right": 319,
-          "bottom": 687
+          "bottom": 688
         }
       ]
     },
@@ -371,5 +371,14 @@
         0
       ]
     }
-  ]
+  ],
+  "\/\/metadata": {
+    "goldenRepoPath": "frameworks\/base\/packages\/SystemUI\/tests\/goldens\/backgroundAnimation_whenLaunching_withSpring.json",
+    "goldenIdentifier": "backgroundAnimation_whenLaunching_withSpring",
+    "testClassName": "TransitionAnimatorTest",
+    "testMethodName": "backgroundAnimation_whenLaunching[true]",
+    "deviceLocalPath": "\/data\/user\/0\/com.android.systemui.tests\/files\/platform_screenshots",
+    "result": "FAILED",
+    "videoLocation": "TransitionAnimatorTest\/backgroundAnimation_whenLaunching_withSpring.actual.mp4"
+  }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/backgroundAnimation_whenLaunching.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenReturning_withAnimator.json
similarity index 100%
copy from packages/SystemUI/tests/goldens/backgroundAnimation_whenLaunching.json
copy to packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenReturning_withAnimator.json
diff --git a/packages/SystemUI/tests/goldens/backgroundAnimation_whenReturning_withSpring.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenReturning_withSpring.json
similarity index 82%
rename from packages/SystemUI/tests/goldens/backgroundAnimation_whenReturning_withSpring.json
rename to packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenReturning_withSpring.json
index a840d3c..5619611 100644
--- a/packages/SystemUI/tests/goldens/backgroundAnimation_whenReturning_withSpring.json
+++ b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenReturning_withSpring.json
@@ -33,118 +33,118 @@
           "bottom": 0
         },
         {
-          "left": 94,
-          "top": 284,
-          "right": 206,
+          "left": 104,
+          "top": 285,
+          "right": 215,
           "bottom": 414
         },
         {
-          "left": 83,
-          "top": 251,
-          "right": 219,
+          "left": 92,
+          "top": 252,
+          "right": 227,
           "bottom": 447
         },
         {
-          "left": 70,
-          "top": 212,
-          "right": 234,
-          "bottom": 485
+          "left": 77,
+          "top": 213,
+          "right": 242,
+          "bottom": 486
         },
         {
-          "left": 57,
-          "top": 173,
-          "right": 250,
-          "bottom": 522
+          "left": 63,
+          "top": 175,
+          "right": 256,
+          "bottom": 524
         },
         {
-          "left": 46,
-          "top": 139,
-          "right": 264,
-          "bottom": 555
+          "left": 50,
+          "top": 141,
+          "right": 269,
+          "bottom": 558
         },
         {
-          "left": 36,
-          "top": 109,
-          "right": 276,
-          "bottom": 584
+          "left": 40,
+          "top": 112,
+          "right": 279,
+          "bottom": 587
         },
         {
-          "left": 28,
-          "top": 84,
-          "right": 285,
-          "bottom": 608
+          "left": 31,
+          "top": 88,
+          "right": 288,
+          "bottom": 611
         },
         {
-          "left": 21,
-          "top": 65,
-          "right": 293,
-          "bottom": 627
+          "left": 23,
+          "top": 68,
+          "right": 296,
+          "bottom": 631
         },
         {
-          "left": 16,
-          "top": 49,
-          "right": 300,
-          "bottom": 642
+          "left": 18,
+          "top": 53,
+          "right": 301,
+          "bottom": 646
         },
         {
-          "left": 12,
-          "top": 36,
-          "right": 305,
-          "bottom": 653
+          "left": 13,
+          "top": 41,
+          "right": 306,
+          "bottom": 658
         },
         {
-          "left": 9,
-          "top": 27,
-          "right": 308,
-          "bottom": 662
+          "left": 10,
+          "top": 31,
+          "right": 309,
+          "bottom": 667
         },
         {
           "left": 7,
-          "top": 20,
+          "top": 24,
           "right": 312,
-          "bottom": 669
+          "bottom": 673
         },
         {
           "left": 5,
-          "top": 14,
+          "top": 18,
           "right": 314,
-          "bottom": 675
-        },
-        {
-          "left": 4,
-          "top": 11,
-          "right": 315,
           "bottom": 678
         },
         {
-          "left": 3,
-          "top": 8,
-          "right": 316,
+          "left": 4,
+          "top": 13,
+          "right": 315,
           "bottom": 681
         },
         {
-          "left": 2,
-          "top": 5,
-          "right": 317,
+          "left": 3,
+          "top": 10,
+          "right": 316,
           "bottom": 684
         },
         {
+          "left": 2,
+          "top": 7,
+          "right": 317,
+          "bottom": 685
+        },
+        {
+          "left": 1,
+          "top": 5,
+          "right": 318,
+          "bottom": 687
+        },
+        {
           "left": 1,
           "top": 4,
           "right": 318,
-          "bottom": 685
-        },
-        {
-          "left": 1,
-          "top": 3,
-          "right": 318,
-          "bottom": 686
+          "bottom": 688
         },
         {
           "left": 0,
-          "top": 2,
+          "top": 3,
           "right": 319,
-          "bottom": 687
+          "bottom": 688
         }
       ]
     },
@@ -371,5 +371,14 @@
         0
       ]
     }
-  ]
+  ],
+  "\/\/metadata": {
+    "goldenRepoPath": "frameworks\/base\/packages\/SystemUI\/tests\/goldens\/backgroundAnimation_whenReturning_withSpring.json",
+    "goldenIdentifier": "backgroundAnimation_whenReturning_withSpring",
+    "testClassName": "TransitionAnimatorTest",
+    "testMethodName": "backgroundAnimation_whenReturning[true]",
+    "deviceLocalPath": "\/data\/user\/0\/com.android.systemui.tests\/files\/platform_screenshots",
+    "result": "FAILED",
+    "videoLocation": "TransitionAnimatorTest\/backgroundAnimation_whenReturning_withSpring.actual.mp4"
+  }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenLaunching.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenLaunching_withAnimator.json
similarity index 100%
rename from packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenLaunching.json
rename to packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenLaunching_withAnimator.json
diff --git a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenLaunching_withSpring.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenLaunching_withSpring.json
similarity index 81%
copy from packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenLaunching_withSpring.json
copy to packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenLaunching_withSpring.json
index 18eedd4..825190b 100644
--- a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenLaunching_withSpring.json
+++ b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenLaunching_withSpring.json
@@ -33,118 +33,118 @@
           "bottom": 0
         },
         {
-          "left": 94,
-          "top": 284,
-          "right": 206,
+          "left": 104,
+          "top": 285,
+          "right": 215,
           "bottom": 414
         },
         {
-          "left": 83,
-          "top": 251,
-          "right": 219,
+          "left": 92,
+          "top": 252,
+          "right": 227,
           "bottom": 447
         },
         {
-          "left": 70,
-          "top": 212,
-          "right": 234,
-          "bottom": 485
+          "left": 77,
+          "top": 213,
+          "right": 242,
+          "bottom": 486
         },
         {
-          "left": 57,
-          "top": 173,
-          "right": 250,
-          "bottom": 522
+          "left": 63,
+          "top": 175,
+          "right": 256,
+          "bottom": 524
         },
         {
-          "left": 46,
-          "top": 139,
-          "right": 264,
-          "bottom": 555
+          "left": 50,
+          "top": 141,
+          "right": 269,
+          "bottom": 558
         },
         {
-          "left": 36,
-          "top": 109,
-          "right": 276,
-          "bottom": 584
+          "left": 40,
+          "top": 112,
+          "right": 279,
+          "bottom": 587
         },
         {
-          "left": 28,
-          "top": 84,
-          "right": 285,
-          "bottom": 608
+          "left": 31,
+          "top": 88,
+          "right": 288,
+          "bottom": 611
         },
         {
-          "left": 21,
-          "top": 65,
-          "right": 293,
-          "bottom": 627
+          "left": 23,
+          "top": 68,
+          "right": 296,
+          "bottom": 631
         },
         {
-          "left": 16,
-          "top": 49,
-          "right": 300,
-          "bottom": 642
+          "left": 18,
+          "top": 53,
+          "right": 301,
+          "bottom": 646
         },
         {
-          "left": 12,
-          "top": 36,
-          "right": 305,
-          "bottom": 653
+          "left": 13,
+          "top": 41,
+          "right": 306,
+          "bottom": 658
         },
         {
-          "left": 9,
-          "top": 27,
-          "right": 308,
-          "bottom": 662
+          "left": 10,
+          "top": 31,
+          "right": 309,
+          "bottom": 667
         },
         {
           "left": 7,
-          "top": 20,
+          "top": 24,
           "right": 312,
-          "bottom": 669
+          "bottom": 673
         },
         {
           "left": 5,
-          "top": 14,
+          "top": 18,
           "right": 314,
-          "bottom": 675
-        },
-        {
-          "left": 4,
-          "top": 11,
-          "right": 315,
           "bottom": 678
         },
         {
-          "left": 3,
-          "top": 8,
-          "right": 316,
+          "left": 4,
+          "top": 13,
+          "right": 315,
           "bottom": 681
         },
         {
-          "left": 2,
-          "top": 5,
-          "right": 317,
+          "left": 3,
+          "top": 10,
+          "right": 316,
           "bottom": 684
         },
         {
+          "left": 2,
+          "top": 7,
+          "right": 317,
+          "bottom": 685
+        },
+        {
+          "left": 1,
+          "top": 5,
+          "right": 318,
+          "bottom": 687
+        },
+        {
           "left": 1,
           "top": 4,
           "right": 318,
-          "bottom": 685
-        },
-        {
-          "left": 1,
-          "top": 3,
-          "right": 318,
-          "bottom": 686
+          "bottom": 688
         },
         {
           "left": 0,
-          "top": 2,
+          "top": 3,
           "right": 319,
-          "bottom": 687
+          "bottom": 688
         }
       ]
     },
@@ -371,5 +371,14 @@
         0
       ]
     }
-  ]
+  ],
+  "\/\/metadata": {
+    "goldenRepoPath": "frameworks\/base\/packages\/SystemUI\/tests\/goldens\/backgroundAnimationWithoutFade_whenLaunching_withSpring.json",
+    "goldenIdentifier": "backgroundAnimationWithoutFade_whenLaunching_withSpring",
+    "testClassName": "TransitionAnimatorTest",
+    "testMethodName": "backgroundAnimationWithoutFade_whenLaunching[true]",
+    "deviceLocalPath": "\/data\/user\/0\/com.android.systemui.tests\/files\/platform_screenshots",
+    "result": "FAILED",
+    "videoLocation": "TransitionAnimatorTest\/backgroundAnimationWithoutFade_whenLaunching_withSpring.actual.mp4"
+  }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenReturning.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenReturning_withAnimator.json
similarity index 100%
rename from packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenReturning.json
rename to packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenReturning_withAnimator.json
diff --git a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenLaunching_withSpring.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenReturning_withSpring.json
similarity index 81%
rename from packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenLaunching_withSpring.json
rename to packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenReturning_withSpring.json
index 18eedd4..63c2631 100644
--- a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenLaunching_withSpring.json
+++ b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenReturning_withSpring.json
@@ -33,118 +33,118 @@
           "bottom": 0
         },
         {
-          "left": 94,
-          "top": 284,
-          "right": 206,
+          "left": 104,
+          "top": 285,
+          "right": 215,
           "bottom": 414
         },
         {
-          "left": 83,
-          "top": 251,
-          "right": 219,
+          "left": 92,
+          "top": 252,
+          "right": 227,
           "bottom": 447
         },
         {
-          "left": 70,
-          "top": 212,
-          "right": 234,
-          "bottom": 485
+          "left": 77,
+          "top": 213,
+          "right": 242,
+          "bottom": 486
         },
         {
-          "left": 57,
-          "top": 173,
-          "right": 250,
-          "bottom": 522
+          "left": 63,
+          "top": 175,
+          "right": 256,
+          "bottom": 524
         },
         {
-          "left": 46,
-          "top": 139,
-          "right": 264,
-          "bottom": 555
+          "left": 50,
+          "top": 141,
+          "right": 269,
+          "bottom": 558
         },
         {
-          "left": 36,
-          "top": 109,
-          "right": 276,
-          "bottom": 584
+          "left": 40,
+          "top": 112,
+          "right": 279,
+          "bottom": 587
         },
         {
-          "left": 28,
-          "top": 84,
-          "right": 285,
-          "bottom": 608
+          "left": 31,
+          "top": 88,
+          "right": 288,
+          "bottom": 611
         },
         {
-          "left": 21,
-          "top": 65,
-          "right": 293,
-          "bottom": 627
+          "left": 23,
+          "top": 68,
+          "right": 296,
+          "bottom": 631
         },
         {
-          "left": 16,
-          "top": 49,
-          "right": 300,
-          "bottom": 642
+          "left": 18,
+          "top": 53,
+          "right": 301,
+          "bottom": 646
         },
         {
-          "left": 12,
-          "top": 36,
-          "right": 305,
-          "bottom": 653
+          "left": 13,
+          "top": 41,
+          "right": 306,
+          "bottom": 658
         },
         {
-          "left": 9,
-          "top": 27,
-          "right": 308,
-          "bottom": 662
+          "left": 10,
+          "top": 31,
+          "right": 309,
+          "bottom": 667
         },
         {
           "left": 7,
-          "top": 20,
+          "top": 24,
           "right": 312,
-          "bottom": 669
+          "bottom": 673
         },
         {
           "left": 5,
-          "top": 14,
+          "top": 18,
           "right": 314,
-          "bottom": 675
-        },
-        {
-          "left": 4,
-          "top": 11,
-          "right": 315,
           "bottom": 678
         },
         {
-          "left": 3,
-          "top": 8,
-          "right": 316,
+          "left": 4,
+          "top": 13,
+          "right": 315,
           "bottom": 681
         },
         {
-          "left": 2,
-          "top": 5,
-          "right": 317,
+          "left": 3,
+          "top": 10,
+          "right": 316,
           "bottom": 684
         },
         {
+          "left": 2,
+          "top": 7,
+          "right": 317,
+          "bottom": 685
+        },
+        {
+          "left": 1,
+          "top": 5,
+          "right": 318,
+          "bottom": 687
+        },
+        {
           "left": 1,
           "top": 4,
           "right": 318,
-          "bottom": 685
-        },
-        {
-          "left": 1,
-          "top": 3,
-          "right": 318,
-          "bottom": 686
+          "bottom": 688
         },
         {
           "left": 0,
-          "top": 2,
+          "top": 3,
           "right": 319,
-          "bottom": 687
+          "bottom": 688
         }
       ]
     },
@@ -371,5 +371,14 @@
         0
       ]
     }
-  ]
+  ],
+  "\/\/metadata": {
+    "goldenRepoPath": "frameworks\/base\/packages\/SystemUI\/tests\/goldens\/backgroundAnimationWithoutFade_whenReturning_withSpring.json",
+    "goldenIdentifier": "backgroundAnimationWithoutFade_whenReturning_withSpring",
+    "testClassName": "TransitionAnimatorTest",
+    "testMethodName": "backgroundAnimationWithoutFade_whenReturning[true]",
+    "deviceLocalPath": "\/data\/user\/0\/com.android.systemui.tests\/files\/platform_screenshots",
+    "result": "FAILED",
+    "videoLocation": "TransitionAnimatorTest\/backgroundAnimationWithoutFade_whenReturning_withSpring.actual.mp4"
+  }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenReturning_withSpring.json b/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenReturning_withSpring.json
deleted file mode 100644
index 18eedd4..0000000
--- a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenReturning_withSpring.json
+++ /dev/null
@@ -1,375 +0,0 @@
-{
-  "frame_ids": [
-    0,
-    16,
-    32,
-    48,
-    64,
-    80,
-    96,
-    112,
-    128,
-    144,
-    160,
-    176,
-    192,
-    208,
-    224,
-    240,
-    256,
-    272,
-    288,
-    304
-  ],
-  "features": [
-    {
-      "name": "bounds",
-      "type": "rect",
-      "data_points": [
-        {
-          "left": 0,
-          "top": 0,
-          "right": 0,
-          "bottom": 0
-        },
-        {
-          "left": 94,
-          "top": 284,
-          "right": 206,
-          "bottom": 414
-        },
-        {
-          "left": 83,
-          "top": 251,
-          "right": 219,
-          "bottom": 447
-        },
-        {
-          "left": 70,
-          "top": 212,
-          "right": 234,
-          "bottom": 485
-        },
-        {
-          "left": 57,
-          "top": 173,
-          "right": 250,
-          "bottom": 522
-        },
-        {
-          "left": 46,
-          "top": 139,
-          "right": 264,
-          "bottom": 555
-        },
-        {
-          "left": 36,
-          "top": 109,
-          "right": 276,
-          "bottom": 584
-        },
-        {
-          "left": 28,
-          "top": 84,
-          "right": 285,
-          "bottom": 608
-        },
-        {
-          "left": 21,
-          "top": 65,
-          "right": 293,
-          "bottom": 627
-        },
-        {
-          "left": 16,
-          "top": 49,
-          "right": 300,
-          "bottom": 642
-        },
-        {
-          "left": 12,
-          "top": 36,
-          "right": 305,
-          "bottom": 653
-        },
-        {
-          "left": 9,
-          "top": 27,
-          "right": 308,
-          "bottom": 662
-        },
-        {
-          "left": 7,
-          "top": 20,
-          "right": 312,
-          "bottom": 669
-        },
-        {
-          "left": 5,
-          "top": 14,
-          "right": 314,
-          "bottom": 675
-        },
-        {
-          "left": 4,
-          "top": 11,
-          "right": 315,
-          "bottom": 678
-        },
-        {
-          "left": 3,
-          "top": 8,
-          "right": 316,
-          "bottom": 681
-        },
-        {
-          "left": 2,
-          "top": 5,
-          "right": 317,
-          "bottom": 684
-        },
-        {
-          "left": 1,
-          "top": 4,
-          "right": 318,
-          "bottom": 685
-        },
-        {
-          "left": 1,
-          "top": 3,
-          "right": 318,
-          "bottom": 686
-        },
-        {
-          "left": 0,
-          "top": 2,
-          "right": 319,
-          "bottom": 687
-        }
-      ]
-    },
-    {
-      "name": "corner_radii",
-      "type": "cornerRadii",
-      "data_points": [
-        null,
-        {
-          "top_left_x": 9.492916,
-          "top_left_y": 9.492916,
-          "top_right_x": 9.492916,
-          "top_right_y": 9.492916,
-          "bottom_right_x": 18.985832,
-          "bottom_right_y": 18.985832,
-          "bottom_left_x": 18.985832,
-          "bottom_left_y": 18.985832
-        },
-        {
-          "top_left_x": 8.381761,
-          "top_left_y": 8.381761,
-          "top_right_x": 8.381761,
-          "top_right_y": 8.381761,
-          "bottom_right_x": 16.763521,
-          "bottom_right_y": 16.763521,
-          "bottom_left_x": 16.763521,
-          "bottom_left_y": 16.763521
-        },
-        {
-          "top_left_x": 7.07397,
-          "top_left_y": 7.07397,
-          "top_right_x": 7.07397,
-          "top_right_y": 7.07397,
-          "bottom_right_x": 14.14794,
-          "bottom_right_y": 14.14794,
-          "bottom_left_x": 14.14794,
-          "bottom_left_y": 14.14794
-        },
-        {
-          "top_left_x": 5.7880254,
-          "top_left_y": 5.7880254,
-          "top_right_x": 5.7880254,
-          "top_right_y": 5.7880254,
-          "bottom_right_x": 11.576051,
-          "bottom_right_y": 11.576051,
-          "bottom_left_x": 11.576051,
-          "bottom_left_y": 11.576051
-        },
-        {
-          "top_left_x": 4.6295347,
-          "top_left_y": 4.6295347,
-          "top_right_x": 4.6295347,
-          "top_right_y": 4.6295347,
-          "bottom_right_x": 9.259069,
-          "bottom_right_y": 9.259069,
-          "bottom_left_x": 9.259069,
-          "bottom_left_y": 9.259069
-        },
-        {
-          "top_left_x": 3.638935,
-          "top_left_y": 3.638935,
-          "top_right_x": 3.638935,
-          "top_right_y": 3.638935,
-          "bottom_right_x": 7.27787,
-          "bottom_right_y": 7.27787,
-          "bottom_left_x": 7.27787,
-          "bottom_left_y": 7.27787
-        },
-        {
-          "top_left_x": 2.8209057,
-          "top_left_y": 2.8209057,
-          "top_right_x": 2.8209057,
-          "top_right_y": 2.8209057,
-          "bottom_right_x": 5.6418114,
-          "bottom_right_y": 5.6418114,
-          "bottom_left_x": 5.6418114,
-          "bottom_left_y": 5.6418114
-        },
-        {
-          "top_left_x": 2.1620893,
-          "top_left_y": 2.1620893,
-          "top_right_x": 2.1620893,
-          "top_right_y": 2.1620893,
-          "bottom_right_x": 4.3241787,
-          "bottom_right_y": 4.3241787,
-          "bottom_left_x": 4.3241787,
-          "bottom_left_y": 4.3241787
-        },
-        {
-          "top_left_x": 1.6414614,
-          "top_left_y": 1.6414614,
-          "top_right_x": 1.6414614,
-          "top_right_y": 1.6414614,
-          "bottom_right_x": 3.2829227,
-          "bottom_right_y": 3.2829227,
-          "bottom_left_x": 3.2829227,
-          "bottom_left_y": 3.2829227
-        },
-        {
-          "top_left_x": 1.2361269,
-          "top_left_y": 1.2361269,
-          "top_right_x": 1.2361269,
-          "top_right_y": 1.2361269,
-          "bottom_right_x": 2.4722538,
-          "bottom_right_y": 2.4722538,
-          "bottom_left_x": 2.4722538,
-          "bottom_left_y": 2.4722538
-        },
-        {
-          "top_left_x": 0.92435074,
-          "top_left_y": 0.92435074,
-          "top_right_x": 0.92435074,
-          "top_right_y": 0.92435074,
-          "bottom_right_x": 1.8487015,
-          "bottom_right_y": 1.8487015,
-          "bottom_left_x": 1.8487015,
-          "bottom_left_y": 1.8487015
-        },
-        {
-          "top_left_x": 0.68693924,
-          "top_left_y": 0.68693924,
-          "top_right_x": 0.68693924,
-          "top_right_y": 0.68693924,
-          "bottom_right_x": 1.3738785,
-          "bottom_right_y": 1.3738785,
-          "bottom_left_x": 1.3738785,
-          "bottom_left_y": 1.3738785
-        },
-        {
-          "top_left_x": 0.5076904,
-          "top_left_y": 0.5076904,
-          "top_right_x": 0.5076904,
-          "top_right_y": 0.5076904,
-          "bottom_right_x": 1.0153809,
-          "bottom_right_y": 1.0153809,
-          "bottom_left_x": 1.0153809,
-          "bottom_left_y": 1.0153809
-        },
-        {
-          "top_left_x": 0.3733511,
-          "top_left_y": 0.3733511,
-          "top_right_x": 0.3733511,
-          "top_right_y": 0.3733511,
-          "bottom_right_x": 0.7467022,
-          "bottom_right_y": 0.7467022,
-          "bottom_left_x": 0.7467022,
-          "bottom_left_y": 0.7467022
-        },
-        {
-          "top_left_x": 0.27331638,
-          "top_left_y": 0.27331638,
-          "top_right_x": 0.27331638,
-          "top_right_y": 0.27331638,
-          "bottom_right_x": 0.54663277,
-          "bottom_right_y": 0.54663277,
-          "bottom_left_x": 0.54663277,
-          "bottom_left_y": 0.54663277
-        },
-        {
-          "top_left_x": 0.19925308,
-          "top_left_y": 0.19925308,
-          "top_right_x": 0.19925308,
-          "top_right_y": 0.19925308,
-          "bottom_right_x": 0.39850616,
-          "bottom_right_y": 0.39850616,
-          "bottom_left_x": 0.39850616,
-          "bottom_left_y": 0.39850616
-        },
-        {
-          "top_left_x": 0.14470005,
-          "top_left_y": 0.14470005,
-          "top_right_x": 0.14470005,
-          "top_right_y": 0.14470005,
-          "bottom_right_x": 0.2894001,
-          "bottom_right_y": 0.2894001,
-          "bottom_left_x": 0.2894001,
-          "bottom_left_y": 0.2894001
-        },
-        {
-          "top_left_x": 0.10470486,
-          "top_left_y": 0.10470486,
-          "top_right_x": 0.10470486,
-          "top_right_y": 0.10470486,
-          "bottom_right_x": 0.20940971,
-          "bottom_right_y": 0.20940971,
-          "bottom_left_x": 0.20940971,
-          "bottom_left_y": 0.20940971
-        },
-        {
-          "top_left_x": 0.07550812,
-          "top_left_y": 0.07550812,
-          "top_right_x": 0.07550812,
-          "top_right_y": 0.07550812,
-          "bottom_right_x": 0.15101624,
-          "bottom_right_y": 0.15101624,
-          "bottom_left_x": 0.15101624,
-          "bottom_left_y": 0.15101624
-        }
-      ]
-    },
-    {
-      "name": "alpha",
-      "type": "int",
-      "data_points": [
-        0,
-        255,
-        255,
-        255,
-        255,
-        255,
-        255,
-        255,
-        255,
-        255,
-        249,
-        226,
-        192,
-        153,
-        112,
-        72,
-        34,
-        0,
-        0,
-        0
-      ]
-    }
-  ]
-}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/backgroundAnimation_whenReturning.json b/packages/SystemUI/tests/goldens/backgroundAnimation_whenReturning.json
deleted file mode 100644
index aa80445..0000000
--- a/packages/SystemUI/tests/goldens/backgroundAnimation_whenReturning.json
+++ /dev/null
@@ -1,492 +0,0 @@
-{
-  "frame_ids": [
-    0,
-    20,
-    40,
-    60,
-    80,
-    100,
-    120,
-    140,
-    160,
-    180,
-    200,
-    220,
-    240,
-    260,
-    280,
-    300,
-    320,
-    340,
-    360,
-    380,
-    400,
-    420,
-    440,
-    460,
-    480,
-    500
-  ],
-  "features": [
-    {
-      "name": "bounds",
-      "type": "rect",
-      "data_points": [
-        {
-          "left": 100,
-          "top": 300,
-          "right": 200,
-          "bottom": 400
-        },
-        {
-          "left": 99,
-          "top": 296,
-          "right": 202,
-          "bottom": 404
-        },
-        {
-          "left": 95,
-          "top": 283,
-          "right": 207,
-          "bottom": 417
-        },
-        {
-          "left": 86,
-          "top": 256,
-          "right": 219,
-          "bottom": 443
-        },
-        {
-          "left": 68,
-          "top": 198,
-          "right": 243,
-          "bottom": 499
-        },
-        {
-          "left": 39,
-          "top": 110,
-          "right": 278,
-          "bottom": 584
-        },
-        {
-          "left": 26,
-          "top": 74,
-          "right": 292,
-          "bottom": 618
-        },
-        {
-          "left": 19,
-          "top": 55,
-          "right": 299,
-          "bottom": 637
-        },
-        {
-          "left": 15,
-          "top": 42,
-          "right": 304,
-          "bottom": 649
-        },
-        {
-          "left": 12,
-          "top": 33,
-          "right": 307,
-          "bottom": 658
-        },
-        {
-          "left": 9,
-          "top": 27,
-          "right": 310,
-          "bottom": 664
-        },
-        {
-          "left": 7,
-          "top": 21,
-          "right": 312,
-          "bottom": 669
-        },
-        {
-          "left": 6,
-          "top": 17,
-          "right": 314,
-          "bottom": 674
-        },
-        {
-          "left": 5,
-          "top": 13,
-          "right": 315,
-          "bottom": 677
-        },
-        {
-          "left": 4,
-          "top": 10,
-          "right": 316,
-          "bottom": 680
-        },
-        {
-          "left": 3,
-          "top": 8,
-          "right": 317,
-          "bottom": 682
-        },
-        {
-          "left": 2,
-          "top": 6,
-          "right": 318,
-          "bottom": 684
-        },
-        {
-          "left": 2,
-          "top": 5,
-          "right": 318,
-          "bottom": 685
-        },
-        {
-          "left": 1,
-          "top": 4,
-          "right": 319,
-          "bottom": 687
-        },
-        {
-          "left": 1,
-          "top": 2,
-          "right": 319,
-          "bottom": 688
-        },
-        {
-          "left": 1,
-          "top": 2,
-          "right": 319,
-          "bottom": 688
-        },
-        {
-          "left": 0,
-          "top": 1,
-          "right": 320,
-          "bottom": 689
-        },
-        {
-          "left": 0,
-          "top": 1,
-          "right": 320,
-          "bottom": 689
-        },
-        {
-          "left": 0,
-          "top": 0,
-          "right": 320,
-          "bottom": 690
-        },
-        {
-          "left": 0,
-          "top": 0,
-          "right": 320,
-          "bottom": 690
-        },
-        {
-          "left": 0,
-          "top": 0,
-          "right": 320,
-          "bottom": 690
-        }
-      ]
-    },
-    {
-      "name": "corner_radii",
-      "type": "cornerRadii",
-      "data_points": [
-        {
-          "top_left_x": 10,
-          "top_left_y": 10,
-          "top_right_x": 10,
-          "top_right_y": 10,
-          "bottom_right_x": 20,
-          "bottom_right_y": 20,
-          "bottom_left_x": 20,
-          "bottom_left_y": 20
-        },
-        {
-          "top_left_x": 9.865689,
-          "top_left_y": 9.865689,
-          "top_right_x": 9.865689,
-          "top_right_y": 9.865689,
-          "bottom_right_x": 19.731379,
-          "bottom_right_y": 19.731379,
-          "bottom_left_x": 19.731379,
-          "bottom_left_y": 19.731379
-        },
-        {
-          "top_left_x": 9.419104,
-          "top_left_y": 9.419104,
-          "top_right_x": 9.419104,
-          "top_right_y": 9.419104,
-          "bottom_right_x": 18.838207,
-          "bottom_right_y": 18.838207,
-          "bottom_left_x": 18.838207,
-          "bottom_left_y": 18.838207
-        },
-        {
-          "top_left_x": 8.533693,
-          "top_left_y": 8.533693,
-          "top_right_x": 8.533693,
-          "top_right_y": 8.533693,
-          "bottom_right_x": 17.067387,
-          "bottom_right_y": 17.067387,
-          "bottom_left_x": 17.067387,
-          "bottom_left_y": 17.067387
-        },
-        {
-          "top_left_x": 6.5919456,
-          "top_left_y": 6.5919456,
-          "top_right_x": 6.5919456,
-          "top_right_y": 6.5919456,
-          "bottom_right_x": 13.183891,
-          "bottom_right_y": 13.183891,
-          "bottom_left_x": 13.183891,
-          "bottom_left_y": 13.183891
-        },
-        {
-          "top_left_x": 3.6674318,
-          "top_left_y": 3.6674318,
-          "top_right_x": 3.6674318,
-          "top_right_y": 3.6674318,
-          "bottom_right_x": 7.3348637,
-          "bottom_right_y": 7.3348637,
-          "bottom_left_x": 7.3348637,
-          "bottom_left_y": 7.3348637
-        },
-        {
-          "top_left_x": 2.4832253,
-          "top_left_y": 2.4832253,
-          "top_right_x": 2.4832253,
-          "top_right_y": 2.4832253,
-          "bottom_right_x": 4.9664507,
-          "bottom_right_y": 4.9664507,
-          "bottom_left_x": 4.9664507,
-          "bottom_left_y": 4.9664507
-        },
-        {
-          "top_left_x": 1.8252907,
-          "top_left_y": 1.8252907,
-          "top_right_x": 1.8252907,
-          "top_right_y": 1.8252907,
-          "bottom_right_x": 3.6505814,
-          "bottom_right_y": 3.6505814,
-          "bottom_left_x": 3.6505814,
-          "bottom_left_y": 3.6505814
-        },
-        {
-          "top_left_x": 1.4077549,
-          "top_left_y": 1.4077549,
-          "top_right_x": 1.4077549,
-          "top_right_y": 1.4077549,
-          "bottom_right_x": 2.8155098,
-          "bottom_right_y": 2.8155098,
-          "bottom_left_x": 2.8155098,
-          "bottom_left_y": 2.8155098
-        },
-        {
-          "top_left_x": 1.1067667,
-          "top_left_y": 1.1067667,
-          "top_right_x": 1.1067667,
-          "top_right_y": 1.1067667,
-          "bottom_right_x": 2.2135334,
-          "bottom_right_y": 2.2135334,
-          "bottom_left_x": 2.2135334,
-          "bottom_left_y": 2.2135334
-        },
-        {
-          "top_left_x": 0.88593864,
-          "top_left_y": 0.88593864,
-          "top_right_x": 0.88593864,
-          "top_right_y": 0.88593864,
-          "bottom_right_x": 1.7718773,
-          "bottom_right_y": 1.7718773,
-          "bottom_left_x": 1.7718773,
-          "bottom_left_y": 1.7718773
-        },
-        {
-          "top_left_x": 0.7069988,
-          "top_left_y": 0.7069988,
-          "top_right_x": 0.7069988,
-          "top_right_y": 0.7069988,
-          "bottom_right_x": 1.4139977,
-          "bottom_right_y": 1.4139977,
-          "bottom_left_x": 1.4139977,
-          "bottom_left_y": 1.4139977
-        },
-        {
-          "top_left_x": 0.55613136,
-          "top_left_y": 0.55613136,
-          "top_right_x": 0.55613136,
-          "top_right_y": 0.55613136,
-          "bottom_right_x": 1.1122627,
-          "bottom_right_y": 1.1122627,
-          "bottom_left_x": 1.1122627,
-          "bottom_left_y": 1.1122627
-        },
-        {
-          "top_left_x": 0.44889355,
-          "top_left_y": 0.44889355,
-          "top_right_x": 0.44889355,
-          "top_right_y": 0.44889355,
-          "bottom_right_x": 0.8977871,
-          "bottom_right_y": 0.8977871,
-          "bottom_left_x": 0.8977871,
-          "bottom_left_y": 0.8977871
-        },
-        {
-          "top_left_x": 0.34557533,
-          "top_left_y": 0.34557533,
-          "top_right_x": 0.34557533,
-          "top_right_y": 0.34557533,
-          "bottom_right_x": 0.69115067,
-          "bottom_right_y": 0.69115067,
-          "bottom_left_x": 0.69115067,
-          "bottom_left_y": 0.69115067
-        },
-        {
-          "top_left_x": 0.27671337,
-          "top_left_y": 0.27671337,
-          "top_right_x": 0.27671337,
-          "top_right_y": 0.27671337,
-          "bottom_right_x": 0.55342674,
-          "bottom_right_y": 0.55342674,
-          "bottom_left_x": 0.55342674,
-          "bottom_left_y": 0.55342674
-        },
-        {
-          "top_left_x": 0.20785141,
-          "top_left_y": 0.20785141,
-          "top_right_x": 0.20785141,
-          "top_right_y": 0.20785141,
-          "bottom_right_x": 0.41570282,
-          "bottom_right_y": 0.41570282,
-          "bottom_left_x": 0.41570282,
-          "bottom_left_y": 0.41570282
-        },
-        {
-          "top_left_x": 0.1601448,
-          "top_left_y": 0.1601448,
-          "top_right_x": 0.1601448,
-          "top_right_y": 0.1601448,
-          "bottom_right_x": 0.3202896,
-          "bottom_right_y": 0.3202896,
-          "bottom_left_x": 0.3202896,
-          "bottom_left_y": 0.3202896
-        },
-        {
-          "top_left_x": 0.117860794,
-          "top_left_y": 0.117860794,
-          "top_right_x": 0.117860794,
-          "top_right_y": 0.117860794,
-          "bottom_right_x": 0.23572159,
-          "bottom_right_y": 0.23572159,
-          "bottom_left_x": 0.23572159,
-          "bottom_left_y": 0.23572159
-        },
-        {
-          "top_left_x": 0.08036041,
-          "top_left_y": 0.08036041,
-          "top_right_x": 0.08036041,
-          "top_right_y": 0.08036041,
-          "bottom_right_x": 0.16072083,
-          "bottom_right_y": 0.16072083,
-          "bottom_left_x": 0.16072083,
-          "bottom_left_y": 0.16072083
-        },
-        {
-          "top_left_x": 0.05836296,
-          "top_left_y": 0.05836296,
-          "top_right_x": 0.05836296,
-          "top_right_y": 0.05836296,
-          "bottom_right_x": 0.11672592,
-          "bottom_right_y": 0.11672592,
-          "bottom_left_x": 0.11672592,
-          "bottom_left_y": 0.11672592
-        },
-        {
-          "top_left_x": 0.03636551,
-          "top_left_y": 0.03636551,
-          "top_right_x": 0.03636551,
-          "top_right_y": 0.03636551,
-          "bottom_right_x": 0.07273102,
-          "bottom_right_y": 0.07273102,
-          "bottom_left_x": 0.07273102,
-          "bottom_left_y": 0.07273102
-        },
-        {
-          "top_left_x": 0.018137932,
-          "top_left_y": 0.018137932,
-          "top_right_x": 0.018137932,
-          "top_right_y": 0.018137932,
-          "bottom_right_x": 0.036275864,
-          "bottom_right_y": 0.036275864,
-          "bottom_left_x": 0.036275864,
-          "bottom_left_y": 0.036275864
-        },
-        {
-          "top_left_x": 0.0082063675,
-          "top_left_y": 0.0082063675,
-          "top_right_x": 0.0082063675,
-          "top_right_y": 0.0082063675,
-          "bottom_right_x": 0.016412735,
-          "bottom_right_y": 0.016412735,
-          "bottom_left_x": 0.016412735,
-          "bottom_left_y": 0.016412735
-        },
-        {
-          "top_left_x": 0.0031013489,
-          "top_left_y": 0.0031013489,
-          "top_right_x": 0.0031013489,
-          "top_right_y": 0.0031013489,
-          "bottom_right_x": 0.0062026978,
-          "bottom_right_y": 0.0062026978,
-          "bottom_left_x": 0.0062026978,
-          "bottom_left_y": 0.0062026978
-        },
-        {
-          "top_left_x": 0,
-          "top_left_y": 0,
-          "top_right_x": 0,
-          "top_right_y": 0,
-          "bottom_right_x": 0,
-          "bottom_right_y": 0,
-          "bottom_left_x": 0,
-          "bottom_left_y": 0
-        }
-      ]
-    },
-    {
-      "name": "alpha",
-      "type": "int",
-      "data_points": [
-        0,
-        96,
-        153,
-        192,
-        220,
-        238,
-        249,
-        254,
-        233,
-        191,
-        153,
-        117,
-        85,
-        57,
-        33,
-        14,
-        3,
-        0,
-        0,
-        0,
-        0,
-        0,
-        0,
-        0,
-        0,
-        0
-      ]
-    }
-  ]
-}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java b/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
index a95735e..82cfab6 100644
--- a/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
+++ b/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
@@ -56,7 +56,6 @@
 @RunWith(AndroidTestingRunner.class)
 @SmallTest
 public class AAAPlusPlusVerifySysuiRequiredTestPropertiesTest extends SysuiTestCase {
-
     private static final String TAG = "AAA++VerifyTest";
 
     private static final Class[] BASE_CLS_TO_INCLUDE = {
@@ -149,6 +148,9 @@
      */
     private boolean isTestClass(Class<?> loadedClass) {
         try {
+            if (loadedClass.getAnnotation(SkipSysuiVerification.class) != null) {
+                return false;
+            }
             if (Modifier.isAbstract(loadedClass.getModifiers())) {
                 logDebug(String.format("Skipping abstract class %s: not a test",
                         loadedClass.getName()));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
index c6e4e0d..fa88f62 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
@@ -46,7 +46,6 @@
 import android.view.SurfaceControlViewHost;
 import android.view.View;
 import android.view.WindowManager;
-import android.view.WindowManagerGlobal;
 import android.view.accessibility.IRemoteMagnificationAnimationCallback;
 import android.view.animation.AccelerateInterpolator;
 import android.window.InputTransferToken;
@@ -1030,10 +1029,7 @@
                     callback,
                     sysUiState,
                     secureSettings,
-                    scvhSupplier,
-                    sfVsyncFrameProvider,
-                    WindowManagerGlobal::getWindowSession,
-                    viewCaptureAwareWindowManager);
+                    scvhSupplier);
             mSpyController = Mockito.mock(WindowMagnificationController.class);
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java
deleted file mode 100644
index 9b09ec2..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java
+++ /dev/null
@@ -1,1594 +0,0 @@
-/*
- * Copyright (C) 2024 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.accessibility;
-
-import static android.content.pm.PackageManager.FEATURE_WINDOW_MAGNIFICATION;
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
-import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
-import static android.view.MotionEvent.ACTION_DOWN;
-import static android.view.MotionEvent.ACTION_UP;
-import static android.view.WindowInsets.Type.systemGestures;
-import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
-
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.AdditionalAnswers.returnsSecondArg;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyFloat;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atLeast;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import static java.util.Arrays.asList;
-
-import android.animation.ValueAnimator;
-import android.annotation.IdRes;
-import android.annotation.Nullable;
-import android.app.Instrumentation;
-import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.Insets;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
-import android.provider.Settings;
-import android.testing.TestableLooper;
-import android.testing.TestableResources;
-import android.util.Size;
-import android.view.AttachedSurfaceControl;
-import android.view.Display;
-import android.view.Gravity;
-import android.view.MotionEvent;
-import android.view.Surface;
-import android.view.SurfaceControl;
-import android.view.SurfaceControlViewHost;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewRootImpl;
-import android.view.WindowInsets;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.IRemoteMagnificationAnimationCallback;
-import android.widget.FrameLayout;
-import android.window.InputTransferToken;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
-
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
-import com.android.systemui.Flags;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.animation.AnimatorTestRule;
-import com.android.systemui.kosmos.KosmosJavaAdapter;
-import com.android.systemui.model.SysUiState;
-import com.android.systemui.res.R;
-import com.android.systemui.settings.FakeDisplayTracker;
-import com.android.systemui.util.FakeSharedPreferences;
-import com.android.systemui.util.leak.ReferenceTestUtils;
-import com.android.systemui.util.settings.SecureSettings;
-import com.android.systemui.utils.os.FakeHandler;
-
-import com.google.common.util.concurrent.AtomicDouble;
-
-import org.junit.After;
-import org.junit.Assume;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Rule;
-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.ArrayList;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.Supplier;
-
-@LargeTest
-@TestableLooper.RunWithLooper
-@RunWith(AndroidJUnit4.class)
-@EnableFlags(Flags.FLAG_CREATE_WINDOWLESS_WINDOW_MAGNIFIER)
-public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiTestCase {
-
-    @Rule
-    // NOTE: pass 'null' to allow this test advances time on the main thread.
-    public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(/* test= */ null);
-
-    private static final int LAYOUT_CHANGE_TIMEOUT_MS = 5000;
-    @Mock
-    private MirrorWindowControl mMirrorWindowControl;
-    @Mock
-    private WindowMagnifierCallback mWindowMagnifierCallback;
-    @Mock
-    IRemoteMagnificationAnimationCallback mAnimationCallback;
-    @Mock
-    IRemoteMagnificationAnimationCallback mAnimationCallback2;
-
-    private SurfaceControl.Transaction mTransaction;
-    @Mock
-    private SecureSettings mSecureSettings;
-    @Mock
-    private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
-
-    private long mWaitAnimationDuration;
-    private long mWaitBounceEffectDuration;
-
-    private Handler mHandler;
-    private TestableWindowManager mWindowManager;
-    private SysUiState mSysUiState;
-    private Resources mResources;
-    private WindowMagnificationAnimationController mWindowMagnificationAnimationController;
-    private WindowMagnificationController mWindowMagnificationController;
-    private Instrumentation mInstrumentation;
-    private final ValueAnimator mValueAnimator = ValueAnimator.ofFloat(0, 1.0f).setDuration(0);
-    private final FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
-
-    private View mSpyView;
-    private View.OnTouchListener mTouchListener;
-
-    private MotionEventHelper mMotionEventHelper = new MotionEventHelper();
-
-    // This list contains all SurfaceControlViewHosts created during a given test. If the
-    // magnification window is recreated during a test, the list will contain more than a single
-    // element.
-    private List<SurfaceControlViewHost> mSurfaceControlViewHosts = new ArrayList<>();
-    // The most recently created SurfaceControlViewHost.
-    private SurfaceControlViewHost mSurfaceControlViewHost;
-    private KosmosJavaAdapter mKosmos;
-    private FakeSharedPreferences mSharedPreferences;
-
-    /**
-     *  return whether window magnification is supported for current test context.
-     */
-    private boolean isWindowModeSupported() {
-        return getContext().getPackageManager().hasSystemFeature(FEATURE_WINDOW_MAGNIFICATION);
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        mContext = spy(mContext);
-        mKosmos = new KosmosJavaAdapter(this);
-        mContext = Mockito.spy(getContext());
-        mHandler = new FakeHandler(TestableLooper.get(this).getLooper());
-        mInstrumentation = InstrumentationRegistry.getInstrumentation();
-        final WindowManager wm = mContext.getSystemService(WindowManager.class);
-        mWindowManager = spy(new TestableWindowManager(wm));
-
-        mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
-        mSysUiState = new SysUiState(mDisplayTracker, mKosmos.getSceneContainerPlugin());
-        mSysUiState.addCallback(Mockito.mock(SysUiState.SysUiStateCallback.class));
-        when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).then(
-                returnsSecondArg());
-        when(mSecureSettings.getFloatForUser(anyString(), anyFloat(), anyInt())).then(
-                returnsSecondArg());
-
-        mResources = getContext().getOrCreateTestableResources().getResources();
-        // prevent the config orientation from undefined, which may cause config.diff method
-        // neglecting the orientation update.
-        if (mResources.getConfiguration().orientation == ORIENTATION_UNDEFINED) {
-            mResources.getConfiguration().orientation = ORIENTATION_PORTRAIT;
-        }
-
-        // Using the animation duration in WindowMagnificationAnimationController for testing.
-        mWaitAnimationDuration = mResources.getInteger(
-                com.android.internal.R.integer.config_longAnimTime);
-        // Using the bounce effect duration in WindowMagnificationController for testing.
-        mWaitBounceEffectDuration = mResources.getInteger(
-                com.android.internal.R.integer.config_shortAnimTime);
-
-        mWindowMagnificationAnimationController = new WindowMagnificationAnimationController(
-                mContext, mValueAnimator);
-        Supplier<SurfaceControlViewHost> scvhSupplier = () -> {
-            mSurfaceControlViewHost = spy(new SurfaceControlViewHost(
-                    mContext, mContext.getDisplay(), new InputTransferToken(),
-                    "WindowMagnification"));
-            ViewRootImpl viewRoot = mock(ViewRootImpl.class);
-            when(mSurfaceControlViewHost.getRootSurfaceControl()).thenReturn(viewRoot);
-            mSurfaceControlViewHosts.add(mSurfaceControlViewHost);
-            return mSurfaceControlViewHost;
-        };
-        mTransaction = spy(new SurfaceControl.Transaction());
-        mSharedPreferences = new FakeSharedPreferences();
-        when(mContext.getSharedPreferences(
-                eq("window_magnification_preferences"), anyInt()))
-                .thenReturn(mSharedPreferences);
-        mWindowMagnificationController =
-                new WindowMagnificationController(
-                        mContext,
-                        mHandler,
-                        mWindowMagnificationAnimationController,
-                        mMirrorWindowControl,
-                        mTransaction,
-                        mWindowMagnifierCallback,
-                        mSysUiState,
-                        mSecureSettings,
-                        scvhSupplier,
-                        /* sfVsyncFrameProvider= */ null,
-                        /* globalWindowSessionSupplier= */ null,
-                        mViewCaptureAwareWindowManager);
-
-        verify(mMirrorWindowControl).setWindowDelegate(
-                any(MirrorWindowControl.MirrorWindowDelegate.class));
-        mSpyView = Mockito.spy(new View(mContext));
-        doAnswer((invocation) -> {
-            mTouchListener = invocation.getArgument(0);
-            return null;
-        }).when(mSpyView).setOnTouchListener(
-                any(View.OnTouchListener.class));
-
-        // skip test if window magnification is not supported to prevent fail results. (b/279820875)
-        Assume.assumeTrue(isWindowModeSupported());
-    }
-
-    @After
-    public void tearDown() {
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mWindowMagnificationController.deleteWindowMagnification();
-                });
-        mValueAnimator.cancel();
-    }
-
-    @Test
-    public void initWindowMagnificationController_checkAllowDiagonalScrollingWithSecureSettings() {
-        verify(mSecureSettings).getIntForUser(
-                eq(Settings.Secure.ACCESSIBILITY_ALLOW_DIAGONAL_SCROLLING),
-                /* def */ eq(1), /* userHandle= */ anyInt());
-        assertThat(mWindowMagnificationController.isDiagonalScrollingEnabled()).isTrue();
-    }
-
-    @Test
-    public void enableWindowMagnification_showControlAndNotifyBoundsChanged() {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-        });
-
-        verify(mMirrorWindowControl).showControl();
-        verify(mWindowMagnifierCallback,
-                timeout(LAYOUT_CHANGE_TIMEOUT_MS).atLeastOnce()).onWindowMagnifierBoundsChanged(
-                eq(mContext.getDisplayId()), any(Rect.class));
-    }
-
-    @Test
-    public void enableWindowMagnification_notifySourceBoundsChanged() {
-        mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
-                        Float.NaN, /* magnificationFrameOffsetRatioX= */ 0,
-                        /* magnificationFrameOffsetRatioY= */ 0, null));
-
-        // Waits for the surface created
-        verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS)).onSourceBoundsChanged(
-                (eq(mContext.getDisplayId())), any());
-    }
-
-    @Test
-    public void enableWindowMagnification_disabled_notifySourceBoundsChanged() {
-        enableWindowMagnification_notifySourceBoundsChanged();
-        mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.deleteWindowMagnification(null));
-        Mockito.reset(mWindowMagnifierCallback);
-
-        enableWindowMagnification_notifySourceBoundsChanged();
-    }
-
-    @Test
-    public void enableWindowMagnification_withAnimation_schedulesFrame() {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnification(2.0f, 10,
-                    10, /* magnificationFrameOffsetRatioX= */ 0,
-                    /* magnificationFrameOffsetRatioY= */ 0,
-                    Mockito.mock(IRemoteMagnificationAnimationCallback.class));
-        });
-        advanceTimeBy(LAYOUT_CHANGE_TIMEOUT_MS);
-
-        verify(mTransaction, atLeastOnce()).setGeometry(any(), any(), any(),
-                eq(Surface.ROTATION_0));
-    }
-
-    @Test
-    public void moveWindowMagnifier_enabled_notifySourceBoundsChanged() {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
-                    Float.NaN, 0, 0, null);
-        });
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.moveWindowMagnifier(10, 10);
-        });
-
-        final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class);
-        verify(mWindowMagnifierCallback, atLeast(2)).onSourceBoundsChanged(
-                (eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
-        assertThat(mWindowMagnificationController.getCenterX())
-                .isEqualTo(sourceBoundsCaptor.getValue().exactCenterX());
-        assertThat(mWindowMagnificationController.getCenterY())
-                .isEqualTo(sourceBoundsCaptor.getValue().exactCenterY());
-    }
-
-    @Test
-    public void enableWindowMagnification_systemGestureExclusionRectsIsSet() {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-        });
-        // Wait for Rects updated.
-        waitForIdleSync();
-
-        List<Rect> rects = mSurfaceControlViewHost.getView().getSystemGestureExclusionRects();
-        assertThat(rects).isNotEmpty();
-    }
-
-    @Ignore("The default window size should be constrained after fixing b/288056772")
-    @Test
-    public void enableWindowMagnification_LargeScreen_windowSizeIsConstrained() {
-        final int screenSize = mWindowManager.getCurrentWindowMetrics().getBounds().width() * 10;
-        mWindowManager.setWindowBounds(new Rect(0, 0, screenSize, screenSize));
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-        });
-
-        final int halfScreenSize = screenSize / 2;
-        ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams();
-        // The frame size should be the half of smaller value of window height/width unless it
-        //exceed the max frame size.
-        assertThat(params.width).isLessThan(halfScreenSize);
-        assertThat(params.height).isLessThan(halfScreenSize);
-    }
-
-    @Test
-    public void deleteWindowMagnification_destroyControlAndUnregisterComponentCallback() {
-        mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
-                        Float.NaN,
-                        Float.NaN));
-
-        mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.deleteWindowMagnification());
-
-        verify(mMirrorWindowControl).destroyControl();
-        verify(mContext).unregisterComponentCallbacks(mWindowMagnificationController);
-    }
-
-    @Test
-    public void deleteWindowMagnification_enableAtTheBottom_overlapFlagIsFalse() {
-        final WindowManager wm = mContext.getSystemService(WindowManager.class);
-        final Rect bounds = wm.getCurrentWindowMetrics().getBounds();
-        setSystemGestureInsets();
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    bounds.bottom);
-        });
-        ReferenceTestUtils.waitForCondition(this::hasMagnificationOverlapFlag);
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.deleteWindowMagnification();
-        });
-
-        verify(mMirrorWindowControl).destroyControl();
-        assertThat(hasMagnificationOverlapFlag()).isFalse();
-    }
-
-    @Test
-    public void deleteWindowMagnification_notifySourceBoundsChanged() {
-        mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
-                        Float.NaN,
-                        Float.NaN));
-
-        mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.deleteWindowMagnification());
-
-        // The first time is for notifying magnification enabled and the second time is for
-        // notifying magnification disabled.
-        verify(mWindowMagnifierCallback, times(2)).onSourceBoundsChanged(
-                (eq(mContext.getDisplayId())), any());
-    }
-
-    @Test
-    public void moveMagnifier_schedulesFrame() {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-        });
-
-        waitForIdleSync();
-        mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.moveWindowMagnifier(100f, 100f));
-
-        verify(mTransaction, atLeastOnce()).setGeometry(any(), any(), any(),
-                eq(Surface.ROTATION_0));
-    }
-
-    @Test
-    public void moveWindowMagnifierToPositionWithAnimation_expectedValuesAndInvokeCallback()
-            throws RemoteException {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
-                    Float.NaN, 0, 0, null);
-        });
-
-        final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class);
-        verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
-                .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
-        final float targetCenterX = sourceBoundsCaptor.getValue().exactCenterX() + 10;
-        final float targetCenterY = sourceBoundsCaptor.getValue().exactCenterY() + 10;
-
-        reset(mWindowMagnifierCallback);
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.moveWindowMagnifierToPosition(
-                    targetCenterX, targetCenterY, mAnimationCallback);
-        });
-        advanceTimeBy(mWaitAnimationDuration);
-
-        verify(mAnimationCallback, times(1)).onResult(eq(true));
-        verify(mAnimationCallback, never()).onResult(eq(false));
-        verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
-                .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
-        assertThat(mWindowMagnificationController.getCenterX())
-                .isEqualTo(sourceBoundsCaptor.getValue().exactCenterX());
-        assertThat(mWindowMagnificationController.getCenterY())
-                .isEqualTo(sourceBoundsCaptor.getValue().exactCenterY());
-        assertThat(mWindowMagnificationController.getCenterX()).isEqualTo(targetCenterX);
-        assertThat(mWindowMagnificationController.getCenterY()).isEqualTo(targetCenterY);
-    }
-
-    @Test
-    public void moveWindowMagnifierToPositionMultipleTimes_expectedValuesAndInvokeCallback()
-            throws RemoteException {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
-                    Float.NaN, 0, 0, null);
-        });
-
-        final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class);
-        verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
-                .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
-        final float centerX = sourceBoundsCaptor.getValue().exactCenterX();
-        final float centerY = sourceBoundsCaptor.getValue().exactCenterY();
-
-        reset(mWindowMagnifierCallback);
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.moveWindowMagnifierToPosition(
-                    centerX + 10, centerY + 10, mAnimationCallback);
-            mWindowMagnificationController.moveWindowMagnifierToPosition(
-                    centerX + 20, centerY + 20, mAnimationCallback);
-            mWindowMagnificationController.moveWindowMagnifierToPosition(
-                    centerX + 30, centerY + 30, mAnimationCallback);
-            mWindowMagnificationController.moveWindowMagnifierToPosition(
-                    centerX + 40, centerY + 40, mAnimationCallback2);
-        });
-        advanceTimeBy(mWaitAnimationDuration);
-
-        // only the last one callback will return true
-        verify(mAnimationCallback2).onResult(eq(true));
-        // the others will return false
-        verify(mAnimationCallback, times(3)).onResult(eq(false));
-        verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
-                .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
-        assertThat(mWindowMagnificationController.getCenterX())
-                .isEqualTo(sourceBoundsCaptor.getValue().exactCenterX());
-        assertThat(mWindowMagnificationController.getCenterY())
-                .isEqualTo(sourceBoundsCaptor.getValue().exactCenterY());
-        assertThat(mWindowMagnificationController.getCenterX()).isEqualTo(centerX + 40);
-        assertThat(mWindowMagnificationController.getCenterY()).isEqualTo(centerY + 40);
-    }
-
-    @Test
-    public void setScale_enabled_expectedValueAndUpdateStateDescription() {
-        mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.updateWindowMagnificationInternal(2.0f,
-                        Float.NaN, Float.NaN));
-
-        mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.setScale(3.0f));
-
-        assertThat(mWindowMagnificationController.getScale()).isEqualTo(3.0f);
-        final View mirrorView = mSurfaceControlViewHost.getView();
-        assertThat(mirrorView).isNotNull();
-        assertThat(mirrorView.getStateDescription().toString()).contains("300");
-    }
-
-    @Test
-    public void onConfigurationChanged_disabled_withoutException() {
-        Display display = Mockito.spy(mContext.getDisplay());
-        when(display.getRotation()).thenReturn(Surface.ROTATION_90);
-        when(mContext.getDisplay()).thenReturn(display);
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_DENSITY);
-            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_ORIENTATION);
-            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_LOCALE);
-            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE);
-        });
-    }
-
-    @Test
-    public void onOrientationChanged_enabled_updateDisplayRotationAndCenterStayAtSamePosition() {
-        final int newRotation = simulateRotateTheDevice();
-        final Rect windowBounds = new Rect(mWindowManager.getCurrentWindowMetrics().getBounds());
-        final float center = Math.min(windowBounds.exactCenterX(), windowBounds.exactCenterY());
-        final float displayWidth = windowBounds.width();
-        final PointF magnifiedCenter = new PointF(center, center + 5f);
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
-                    magnifiedCenter.x, magnifiedCenter.y);
-            // Get the center again in case the center we set is out of screen.
-            magnifiedCenter.set(mWindowMagnificationController.getCenterX(),
-                    mWindowMagnificationController.getCenterY());
-        });
-        // Rotate the window clockwise 90 degree.
-        windowBounds.set(windowBounds.top, windowBounds.left, windowBounds.bottom,
-                windowBounds.right);
-        mWindowManager.setWindowBounds(windowBounds);
-
-        mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.onConfigurationChanged(
-                ActivityInfo.CONFIG_ORIENTATION));
-
-        assertThat(mWindowMagnificationController.mRotation).isEqualTo(newRotation);
-        final PointF expectedCenter = new PointF(magnifiedCenter.y,
-                displayWidth - magnifiedCenter.x);
-        final PointF actualCenter = new PointF(mWindowMagnificationController.getCenterX(),
-                mWindowMagnificationController.getCenterY());
-        assertThat(actualCenter).isEqualTo(expectedCenter);
-    }
-
-    @Test
-    public void onOrientationChanged_disabled_updateDisplayRotation() {
-        final Rect windowBounds = new Rect(mWindowManager.getCurrentWindowMetrics().getBounds());
-        // Rotate the window clockwise 90 degree.
-        windowBounds.set(windowBounds.top, windowBounds.left, windowBounds.bottom,
-                windowBounds.right);
-        mWindowManager.setWindowBounds(windowBounds);
-        final int newRotation = simulateRotateTheDevice();
-
-        mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.onConfigurationChanged(
-                ActivityInfo.CONFIG_ORIENTATION));
-
-        assertThat(mWindowMagnificationController.mRotation).isEqualTo(newRotation);
-    }
-
-    @Test
-    public void onScreenSizeAndDensityChanged_enabledAtTheCenterOfScreen_keepSameWindowSizeRatio() {
-        // The default position is at the center of the screen.
-        final float expectedRatio = 0.5f;
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-        });
-
-        // Screen size and density change
-        mContext.getResources().getConfiguration().smallestScreenWidthDp =
-                mContext.getResources().getConfiguration().smallestScreenWidthDp * 2;
-        final Rect testWindowBounds = new Rect(
-                mWindowManager.getCurrentWindowMetrics().getBounds());
-        testWindowBounds.set(testWindowBounds.left, testWindowBounds.top,
-                testWindowBounds.right + 100, testWindowBounds.bottom + 100);
-        mWindowManager.setWindowBounds(testWindowBounds);
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE);
-        });
-
-        // The ratio of center to window size should be the same.
-        assertThat(mWindowMagnificationController.getCenterX() / testWindowBounds.width())
-                .isEqualTo(expectedRatio);
-        assertThat(mWindowMagnificationController.getCenterY() / testWindowBounds.height())
-                .isEqualTo(expectedRatio);
-    }
-
-    @DisableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS)
-    @Test
-    public void onScreenSizeAndDensityChanged_enabled_restoreSavedMagnifierWindow() {
-        int newSmallestScreenWidthDp =
-                mContext.getResources().getConfiguration().smallestScreenWidthDp * 2;
-        int windowFrameSize = mResources.getDimensionPixelSize(
-                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
-        Size preferredWindowSize = new Size(windowFrameSize, windowFrameSize);
-        mSharedPreferences
-                .edit()
-                .putString(String.valueOf(newSmallestScreenWidthDp),
-                        preferredWindowSize.toString())
-                .commit();
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-        });
-
-        // Screen density and size change
-        mContext.getResources().getConfiguration().smallestScreenWidthDp = newSmallestScreenWidthDp;
-        final Rect testWindowBounds = new Rect(
-                mWindowManager.getCurrentWindowMetrics().getBounds());
-        testWindowBounds.set(testWindowBounds.left, testWindowBounds.top,
-                testWindowBounds.right + 100, testWindowBounds.bottom + 100);
-        mWindowManager.setWindowBounds(testWindowBounds);
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE);
-        });
-
-        // wait for rect update
-        waitForIdleSync();
-        ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams();
-        final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
-                R.dimen.magnification_mirror_surface_margin);
-        // The width and height of the view include the magnification frame and the margins.
-        assertThat(params.width).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin);
-        assertThat(params.height).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin);
-    }
-
-    @EnableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS)
-    @Test
-    public void onScreenSizeAndDensityChanged_enabled_restoreSavedMagnifierIndexAndWindow() {
-        int newSmallestScreenWidthDp =
-                mContext.getResources().getConfiguration().smallestScreenWidthDp * 2;
-        int windowFrameSize = mResources.getDimensionPixelSize(
-                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
-        Size preferredWindowSize = new Size(windowFrameSize, windowFrameSize);
-        mSharedPreferences
-                .edit()
-                .putString(String.valueOf(newSmallestScreenWidthDp),
-                        WindowMagnificationFrameSpec.serialize(
-                                WindowMagnificationSettings.MagnificationSize.CUSTOM,
-                                preferredWindowSize))
-                .commit();
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-        });
-
-        // Screen density and size change
-        mContext.getResources().getConfiguration().smallestScreenWidthDp = newSmallestScreenWidthDp;
-        final Rect testWindowBounds = new Rect(
-                mWindowManager.getCurrentWindowMetrics().getBounds());
-        testWindowBounds.set(testWindowBounds.left, testWindowBounds.top,
-                testWindowBounds.right + 100, testWindowBounds.bottom + 100);
-        mWindowManager.setWindowBounds(testWindowBounds);
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE);
-        });
-
-        // wait for rect update
-        waitForIdleSync();
-        verify(mWindowMagnifierCallback).onWindowMagnifierBoundsRestored(
-                eq(mContext.getDisplayId()),
-                eq(WindowMagnificationSettings.MagnificationSize.CUSTOM));
-        ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams();
-        final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
-                R.dimen.magnification_mirror_surface_margin);
-        // The width and height of the view include the magnification frame and the margins.
-        assertThat(params.width).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin);
-        assertThat(params.height).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin);
-    }
-
-    @Test
-    public void screenSizeIsChangedToLarge_enabled_defaultWindowSize() {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-        });
-        final int screenSize = mWindowManager.getCurrentWindowMetrics().getBounds().width() * 10;
-        // Screen size and density change
-        mContext.getResources().getConfiguration().smallestScreenWidthDp =
-                mContext.getResources().getConfiguration().smallestScreenWidthDp * 2;
-        mWindowManager.setWindowBounds(new Rect(0, 0, screenSize, screenSize));
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE);
-        });
-
-        final int defaultWindowSize =
-                mWindowMagnificationController.getMagnificationWindowSizeFromIndex(
-                        WindowMagnificationSettings.MagnificationSize.MEDIUM);
-        ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams();
-
-        assertThat(params.width).isEqualTo(defaultWindowSize);
-        assertThat(params.height).isEqualTo(defaultWindowSize);
-    }
-
-    @Test
-    public void onDensityChanged_enabled_updateDimensionsAndResetWindowMagnification() {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-            Mockito.reset(mWindowManager);
-            Mockito.reset(mMirrorWindowControl);
-        });
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_DENSITY);
-        });
-
-        verify(mResources, atLeastOnce()).getDimensionPixelSize(anyInt());
-        verify(mSurfaceControlViewHosts.get(0)).release();
-        verify(mMirrorWindowControl).destroyControl();
-        verify(mSurfaceControlViewHosts.get(1)).setView(any(), any());
-        verify(mMirrorWindowControl).showControl();
-    }
-
-    @Test
-    public void onDensityChanged_disabled_updateDimensions() {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_DENSITY);
-        });
-
-        verify(mResources, atLeastOnce()).getDimensionPixelSize(anyInt());
-    }
-
-    @Test
-    public void initializeA11yNode_enabled_expectedValues() {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(2.5f, Float.NaN,
-                    Float.NaN);
-        });
-        final View mirrorView = mSurfaceControlViewHost.getView();
-        assertThat(mirrorView).isNotNull();
-        final AccessibilityNodeInfo nodeInfo = new AccessibilityNodeInfo();
-
-        mirrorView.onInitializeAccessibilityNodeInfo(nodeInfo);
-
-        assertThat(nodeInfo.getContentDescription()).isNotNull();
-        assertThat(nodeInfo.getStateDescription().toString()).contains("250");
-        assertThat(nodeInfo.getActionList()).containsExactlyElementsIn(asList(
-                new AccessibilityAction(AccessibilityAction.ACTION_CLICK.getId(),
-                        mContext.getResources().getString(
-                        R.string.magnification_open_settings_click_label)),
-                new AccessibilityAction(R.id.accessibility_action_zoom_in,
-                        mContext.getString(R.string.accessibility_control_zoom_in)),
-                new AccessibilityAction(R.id.accessibility_action_zoom_out,
-                        mContext.getString(R.string.accessibility_control_zoom_out)),
-                new AccessibilityAction(R.id.accessibility_action_move_right,
-                        mContext.getString(R.string.accessibility_control_move_right)),
-                new AccessibilityAction(R.id.accessibility_action_move_left,
-                        mContext.getString(R.string.accessibility_control_move_left)),
-                new AccessibilityAction(R.id.accessibility_action_move_down,
-                        mContext.getString(R.string.accessibility_control_move_down)),
-                new AccessibilityAction(R.id.accessibility_action_move_up,
-                        mContext.getString(R.string.accessibility_control_move_up))));
-    }
-
-    @Test
-    public void performA11yActions_visible_expectedResults() {
-        final int displayId = mContext.getDisplayId();
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(1.5f, Float.NaN,
-                    Float.NaN);
-        });
-
-        final View mirrorView = mSurfaceControlViewHost.getView();
-        assertThat(mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_out, null))
-                .isTrue();
-        // Minimum scale is 1.0.
-        verify(mWindowMagnifierCallback).onPerformScaleAction(
-                eq(displayId), /* scale= */ eq(1.0f), /* updatePersistence= */ eq(true));
-
-        assertThat(mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_in, null))
-                .isTrue();
-        verify(mWindowMagnifierCallback).onPerformScaleAction(
-                eq(displayId), /* scale= */ eq(2.5f), /* updatePersistence= */ eq(true));
-
-        // TODO: Verify the final state when the mirror surface is visible.
-        assertThat(mirrorView.performAccessibilityAction(R.id.accessibility_action_move_up, null))
-                .isTrue();
-        assertThat(
-                mirrorView.performAccessibilityAction(R.id.accessibility_action_move_down, null))
-                .isTrue();
-        assertThat(
-                mirrorView.performAccessibilityAction(R.id.accessibility_action_move_right, null))
-                .isTrue();
-        assertThat(
-                mirrorView.performAccessibilityAction(R.id.accessibility_action_move_left, null))
-                .isTrue();
-        verify(mWindowMagnifierCallback, times(4)).onMove(eq(displayId));
-
-        assertThat(mirrorView.performAccessibilityAction(
-                AccessibilityAction.ACTION_CLICK.getId(), null)).isTrue();
-        verify(mWindowMagnifierCallback).onClickSettingsButton(eq(displayId));
-    }
-
-    @Test
-    public void performA11yActions_visible_notifyAccessibilityActionPerformed() {
-        final int displayId = mContext.getDisplayId();
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(2.5f, Float.NaN,
-                    Float.NaN);
-        });
-
-        final View mirrorView = mSurfaceControlViewHost.getView();
-        mirrorView.performAccessibilityAction(R.id.accessibility_action_move_up, null);
-
-        verify(mWindowMagnifierCallback).onAccessibilityActionPerformed(eq(displayId));
-    }
-
-    @Test
-    public void windowMagnifierEditMode_performA11yClickAction_exitEditMode() {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-            mWindowMagnificationController.setEditMagnifierSizeMode(true);
-        });
-
-        View closeButton = getInternalView(R.id.close_button);
-        View bottomRightCorner = getInternalView(R.id.bottom_right_corner);
-        View bottomLeftCorner = getInternalView(R.id.bottom_left_corner);
-        View topRightCorner = getInternalView(R.id.top_right_corner);
-        View topLeftCorner = getInternalView(R.id.top_left_corner);
-
-        assertThat(closeButton.getVisibility()).isEqualTo(View.VISIBLE);
-        assertThat(bottomRightCorner.getVisibility()).isEqualTo(View.VISIBLE);
-        assertThat(bottomLeftCorner.getVisibility()).isEqualTo(View.VISIBLE);
-        assertThat(topRightCorner.getVisibility()).isEqualTo(View.VISIBLE);
-        assertThat(topLeftCorner.getVisibility()).isEqualTo(View.VISIBLE);
-
-        final View mirrorView = mSurfaceControlViewHost.getView();
-        mInstrumentation.runOnMainSync(() ->
-                mirrorView.performAccessibilityAction(AccessibilityAction.ACTION_CLICK.getId(),
-                        null));
-
-        assertThat(closeButton.getVisibility()).isEqualTo(View.GONE);
-        assertThat(bottomRightCorner.getVisibility()).isEqualTo(View.GONE);
-        assertThat(bottomLeftCorner.getVisibility()).isEqualTo(View.GONE);
-        assertThat(topRightCorner.getVisibility()).isEqualTo(View.GONE);
-        assertThat(topLeftCorner.getVisibility()).isEqualTo(View.GONE);
-    }
-
-    @Test
-
-    public void windowWidthIsNotMax_performA11yActionIncreaseWidth_windowWidthIncreased() {
-        final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
-        final int startingWidth = (int) (windowBounds.width() * 0.8);
-        final int startingHeight = (int) (windowBounds.height() * 0.8);
-        final float changeWindowSizeAmount = mContext.getResources().getFraction(
-                R.fraction.magnification_resize_window_size_amount,
-                /* base= */ 1,
-                /* pbase= */ 1);
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-            mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
-            mWindowMagnificationController.setEditMagnifierSizeMode(true);
-        });
-
-        final View mirrorView = mSurfaceControlViewHost.getView();
-        final AtomicInteger actualWindowHeight = new AtomicInteger();
-        final AtomicInteger actualWindowWidth = new AtomicInteger();
-
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mirrorView.performAccessibilityAction(
-                            R.id.accessibility_action_increase_window_width, null);
-                    actualWindowHeight.set(
-                            mSurfaceControlViewHost.getView().getLayoutParams().height);
-                    actualWindowWidth.set(
-                            mSurfaceControlViewHost.getView().getLayoutParams().width);
-                });
-
-        final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
-                R.dimen.magnification_mirror_surface_margin);
-        // Window width includes the magnifier frame and the margin. Increasing the window size
-        // will be increasing the amount of the frame size only.
-        int newWindowWidth =
-                (int) ((startingWidth - 2 * mirrorSurfaceMargin) * (1 + changeWindowSizeAmount))
-                        + 2 * mirrorSurfaceMargin;
-        assertThat(actualWindowWidth.get()).isEqualTo(newWindowWidth);
-        assertThat(actualWindowHeight.get()).isEqualTo(startingHeight);
-    }
-
-    @Test
-    public void windowHeightIsNotMax_performA11yActionIncreaseHeight_windowHeightIncreased() {
-        final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
-        final int startingWidth = (int) (windowBounds.width() * 0.8);
-        final int startingHeight = (int) (windowBounds.height() * 0.8);
-        final float changeWindowSizeAmount = mContext.getResources().getFraction(
-                R.fraction.magnification_resize_window_size_amount,
-                /* base= */ 1,
-                /* pbase= */ 1);
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-            mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
-            mWindowMagnificationController.setEditMagnifierSizeMode(true);
-        });
-
-        final View mirrorView = mSurfaceControlViewHost.getView();
-        final AtomicInteger actualWindowHeight = new AtomicInteger();
-        final AtomicInteger actualWindowWidth = new AtomicInteger();
-
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mirrorView.performAccessibilityAction(
-                            R.id.accessibility_action_increase_window_height, null);
-                    actualWindowHeight.set(
-                            mSurfaceControlViewHost.getView().getLayoutParams().height);
-                    actualWindowWidth.set(
-                            mSurfaceControlViewHost.getView().getLayoutParams().width);
-                });
-
-        final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
-                R.dimen.magnification_mirror_surface_margin);
-        // Window height includes the magnifier frame and the margin. Increasing the window size
-        // will be increasing the amount of the frame size only.
-        int newWindowHeight =
-                (int) ((startingHeight - 2 * mirrorSurfaceMargin) * (1 + changeWindowSizeAmount))
-                        + 2 * mirrorSurfaceMargin;
-        assertThat(actualWindowWidth.get()).isEqualTo(startingWidth);
-        assertThat(actualWindowHeight.get()).isEqualTo(newWindowHeight);
-    }
-
-    @Test
-    public void windowWidthIsMax_noIncreaseWindowWidthA11yAction() {
-        final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
-        final int startingWidth = windowBounds.width();
-        final int startingHeight = windowBounds.height();
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-            mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
-            mWindowMagnificationController.setEditMagnifierSizeMode(true);
-        });
-
-        final View mirrorView = mSurfaceControlViewHost.getView();
-        final AccessibilityNodeInfo accessibilityNodeInfo =
-                mirrorView.createAccessibilityNodeInfo();
-        assertThat(accessibilityNodeInfo.getActionList()).doesNotContain(
-                new AccessibilityAction(
-                        R.id.accessibility_action_increase_window_width,
-                        mContext.getString(
-                                R.string.accessibility_control_increase_window_width)));
-    }
-
-    @Test
-    public void windowHeightIsMax_noIncreaseWindowHeightA11yAction() {
-        final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
-        final int startingWidth = windowBounds.width();
-        final int startingHeight = windowBounds.height();
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-            mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
-            mWindowMagnificationController.setEditMagnifierSizeMode(true);
-        });
-
-        final View mirrorView = mSurfaceControlViewHost.getView();
-        final AccessibilityNodeInfo accessibilityNodeInfo =
-                mirrorView.createAccessibilityNodeInfo();
-        assertThat(accessibilityNodeInfo.getActionList()).doesNotContain(
-                new AccessibilityAction(
-                        R.id.accessibility_action_increase_window_height, null));
-    }
-
-    @Test
-    public void windowWidthIsNotMin_performA11yActionDecreaseWidth_windowWidthDecreased() {
-        int mMinWindowSize = mResources.getDimensionPixelSize(
-                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
-        final int startingSize = (int) (mMinWindowSize * 1.1);
-        final float changeWindowSizeAmount = mContext.getResources().getFraction(
-                R.fraction.magnification_resize_window_size_amount,
-                /* base= */ 1,
-                /* pbase= */ 1);
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-            mWindowMagnificationController.setWindowSize(startingSize, startingSize);
-            mWindowMagnificationController.setEditMagnifierSizeMode(true);
-        });
-
-        final View mirrorView = mSurfaceControlViewHost.getView();
-        final AtomicInteger actualWindowHeight = new AtomicInteger();
-        final AtomicInteger actualWindowWidth = new AtomicInteger();
-
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mirrorView.performAccessibilityAction(
-                            R.id.accessibility_action_decrease_window_width, null);
-                    actualWindowHeight.set(
-                            mSurfaceControlViewHost.getView().getLayoutParams().height);
-                    actualWindowWidth.set(
-                            mSurfaceControlViewHost.getView().getLayoutParams().width);
-                });
-
-        final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
-                R.dimen.magnification_mirror_surface_margin);
-        // Window width includes the magnifier frame and the margin. Decreasing the window size
-        // will be decreasing the amount of the frame size only.
-        int newWindowWidth =
-                (int) ((startingSize - 2 * mirrorSurfaceMargin) * (1 - changeWindowSizeAmount))
-                        + 2 * mirrorSurfaceMargin;
-        assertThat(actualWindowWidth.get()).isEqualTo(newWindowWidth);
-        assertThat(actualWindowHeight.get()).isEqualTo(startingSize);
-    }
-
-    @Test
-    public void windowHeightIsNotMin_performA11yActionDecreaseHeight_windowHeightDecreased() {
-        int mMinWindowSize = mResources.getDimensionPixelSize(
-                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
-        final int startingSize = (int) (mMinWindowSize * 1.1);
-        final float changeWindowSizeAmount = mContext.getResources().getFraction(
-                R.fraction.magnification_resize_window_size_amount,
-                /* base= */ 1,
-                /* pbase= */ 1);
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-            mWindowMagnificationController.setWindowSize(startingSize, startingSize);
-            mWindowMagnificationController.setEditMagnifierSizeMode(true);
-        });
-
-        final View mirrorView = mSurfaceControlViewHost.getView();
-        final AtomicInteger actualWindowHeight = new AtomicInteger();
-        final AtomicInteger actualWindowWidth = new AtomicInteger();
-
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mirrorView.performAccessibilityAction(
-                            R.id.accessibility_action_decrease_window_height, null);
-                    actualWindowHeight.set(
-                            mSurfaceControlViewHost.getView().getLayoutParams().height);
-                    actualWindowWidth.set(
-                            mSurfaceControlViewHost.getView().getLayoutParams().width);
-                });
-
-        final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
-                R.dimen.magnification_mirror_surface_margin);
-        // Window height includes the magnifier frame and the margin. Decreasing the window size
-        // will be decreasing the amount of the frame size only.
-        int newWindowHeight =
-                (int) ((startingSize - 2 * mirrorSurfaceMargin) * (1 - changeWindowSizeAmount))
-                        + 2 * mirrorSurfaceMargin;
-        assertThat(actualWindowWidth.get()).isEqualTo(startingSize);
-        assertThat(actualWindowHeight.get()).isEqualTo(newWindowHeight);
-    }
-
-    @Test
-    public void windowWidthIsMin_noDecreaseWindowWidthA11yAction() {
-        int mMinWindowSize = mResources.getDimensionPixelSize(
-                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
-        final int startingSize = mMinWindowSize;
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-            mWindowMagnificationController.setWindowSize(startingSize, startingSize);
-            mWindowMagnificationController.setEditMagnifierSizeMode(true);
-        });
-
-        final View mirrorView = mSurfaceControlViewHost.getView();
-        final AccessibilityNodeInfo accessibilityNodeInfo =
-                mirrorView.createAccessibilityNodeInfo();
-        assertThat(accessibilityNodeInfo.getActionList()).doesNotContain(
-                new AccessibilityAction(
-                        R.id.accessibility_action_decrease_window_width, null));
-    }
-
-    @Test
-    public void windowHeightIsMin_noDecreaseWindowHeightA11yAction() {
-        int mMinWindowSize = mResources.getDimensionPixelSize(
-                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
-        final int startingSize = mMinWindowSize;
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-            mWindowMagnificationController.setWindowSize(startingSize, startingSize);
-            mWindowMagnificationController.setEditMagnifierSizeMode(true);
-        });
-
-        final View mirrorView = mSurfaceControlViewHost.getView();
-        final AccessibilityNodeInfo accessibilityNodeInfo =
-                mirrorView.createAccessibilityNodeInfo();
-        assertThat(accessibilityNodeInfo.getActionList()).doesNotContain(
-                new AccessibilityAction(
-                        R.id.accessibility_action_decrease_window_height, null));
-    }
-
-    @Test
-    public void enableWindowMagnification_hasA11yWindowTitle() {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-        });
-
-        assertThat(getAccessibilityWindowTitle()).isEqualTo(getContext().getResources().getString(
-                com.android.internal.R.string.android_system_label));
-    }
-
-    @Test
-    public void enableWindowMagnificationWithScaleLessThanOne_enabled_disabled() {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-        });
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(0.9f, Float.NaN,
-                    Float.NaN);
-        });
-
-        assertThat(mWindowMagnificationController.getScale()).isEqualTo(Float.NaN);
-    }
-
-    @Test
-    public void enableWindowMagnification_rotationIsChanged_updateRotationValue() {
-        // the config orientation should not be undefined, since it would cause config.diff
-        // returning 0 and thus the orientation changed would not be detected
-        assertThat(mResources.getConfiguration().orientation).isNotEqualTo(ORIENTATION_UNDEFINED);
-
-        final Configuration config = mResources.getConfiguration();
-        config.orientation = config.orientation == ORIENTATION_LANDSCAPE ? ORIENTATION_PORTRAIT
-                : ORIENTATION_LANDSCAPE;
-        final int newRotation = simulateRotateTheDevice();
-
-        mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
-                        Float.NaN, Float.NaN));
-
-        assertThat(mWindowMagnificationController.mRotation).isEqualTo(newRotation);
-    }
-
-    @Test
-    public void enableWindowMagnification_registerComponentCallback() {
-        mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
-                        Float.NaN,
-                        Float.NaN));
-
-        verify(mContext).registerComponentCallbacks(mWindowMagnificationController);
-    }
-
-    @Test
-    public void onLocaleChanged_enabled_updateA11yWindowTitle() {
-        final String newA11yWindowTitle = "new a11y window title";
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-        });
-        final TestableResources testableResources = getContext().getOrCreateTestableResources();
-        testableResources.addOverride(com.android.internal.R.string.android_system_label,
-                newA11yWindowTitle);
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_LOCALE);
-        });
-
-        assertThat(getAccessibilityWindowTitle()).isEqualTo(newA11yWindowTitle);
-    }
-
-    @Ignore("it's flaky in presubmit but works in abtd, filter for now. b/305654925")
-    @Test
-    public void onSingleTap_enabled_scaleAnimates() {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-        });
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.onSingleTap(mSpyView);
-        });
-
-        final View mirrorView = mSurfaceControlViewHost.getView();
-
-        final AtomicDouble maxScaleX = new AtomicDouble();
-        advanceTimeBy(mWaitBounceEffectDuration, /* runnableOnEachRefresh= */ () -> {
-            // For some reason the fancy way doesn't compile...
-            // maxScaleX.getAndAccumulate(mirrorView.getScaleX(), Math::max);
-            final double oldMax = maxScaleX.get();
-            final double newMax = Math.max(mirrorView.getScaleX(), oldMax);
-            assertThat(maxScaleX.compareAndSet(oldMax, newMax)).isTrue();
-        });
-
-        assertThat(maxScaleX.get()).isGreaterThan(1.0);
-    }
-
-    @Test
-    public void moveWindowMagnificationToTheBottom_enabledWithGestureInset_overlapFlagIsTrue() {
-        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
-        setSystemGestureInsets();
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-        });
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.moveWindowMagnifier(0, bounds.height());
-        });
-
-        ReferenceTestUtils.waitForCondition(() -> hasMagnificationOverlapFlag());
-    }
-
-    @Test
-    public void moveWindowMagnificationToRightEdge_dragHandleMovesToLeftAndUpdatesTapExcludeRegion()
-            throws RemoteException {
-        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
-        setSystemGestureInsets();
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mWindowMagnificationController.updateWindowMagnificationInternal(
-                            Float.NaN, Float.NaN, Float.NaN);
-                });
-        // Wait for Region updated.
-        waitForIdleSync();
-
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mWindowMagnificationController.moveWindowMagnifier(bounds.width(), 0);
-                });
-        // Wait for Region updated.
-        waitForIdleSync();
-
-        AttachedSurfaceControl viewRoot = mSurfaceControlViewHost.getRootSurfaceControl();
-        // Verifying two times in: (1) enable window magnification (2) reposition drag handle
-        verify(viewRoot, times(2)).setTouchableRegion(any());
-
-        View dragButton = getInternalView(R.id.drag_handle);
-        FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) dragButton.getLayoutParams();
-        assertThat(params.gravity).isEqualTo(Gravity.BOTTOM | Gravity.LEFT);
-    }
-
-    @Test
-    public void moveWindowMagnificationToLeftEdge_dragHandleMovesToRightAndUpdatesTapExcludeRegion()
-            throws RemoteException {
-        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
-        setSystemGestureInsets();
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mWindowMagnificationController.updateWindowMagnificationInternal(
-                            Float.NaN, Float.NaN, Float.NaN);
-                });
-        // Wait for Region updated.
-        waitForIdleSync();
-
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mWindowMagnificationController.moveWindowMagnifier(-bounds.width(), 0);
-                });
-        // Wait for Region updated.
-        waitForIdleSync();
-
-        AttachedSurfaceControl viewRoot = mSurfaceControlViewHost.getRootSurfaceControl();
-        // Verifying one times in: (1) enable window magnification
-        verify(viewRoot).setTouchableRegion(any());
-
-        View dragButton = getInternalView(R.id.drag_handle);
-        FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) dragButton.getLayoutParams();
-        assertThat(params.gravity).isEqualTo(Gravity.BOTTOM | Gravity.RIGHT);
-    }
-
-    @Test
-    public void setMinimumWindowSize_enabled_expectedWindowSize() {
-        final int minimumWindowSize = mResources.getDimensionPixelSize(
-                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
-        final int  expectedWindowHeight = minimumWindowSize;
-        final int  expectedWindowWidth = minimumWindowSize;
-        mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
-                        Float.NaN, Float.NaN));
-
-        final AtomicInteger actualWindowHeight = new AtomicInteger();
-        final AtomicInteger actualWindowWidth = new AtomicInteger();
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.setWindowSize(expectedWindowWidth, expectedWindowHeight);
-            actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height);
-            actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width);
-
-        });
-
-        assertThat(actualWindowHeight.get()).isEqualTo(expectedWindowHeight);
-        assertThat(actualWindowWidth.get()).isEqualTo(expectedWindowWidth);
-    }
-
-    @Test
-    public void setMinimumWindowSizeThenEnable_expectedWindowSize() {
-        final int minimumWindowSize = mResources.getDimensionPixelSize(
-                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
-        final int  expectedWindowHeight = minimumWindowSize;
-        final int  expectedWindowWidth = minimumWindowSize;
-
-        final AtomicInteger actualWindowHeight = new AtomicInteger();
-        final AtomicInteger actualWindowWidth = new AtomicInteger();
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.setWindowSize(expectedWindowWidth, expectedWindowHeight);
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
-                    Float.NaN, Float.NaN);
-            actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height);
-            actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width);
-        });
-
-        assertThat(actualWindowHeight.get()).isEqualTo(expectedWindowHeight);
-        assertThat(actualWindowWidth.get()).isEqualTo(expectedWindowWidth);
-    }
-
-    @Test
-    public void setWindowSizeLessThanMin_enabled_minimumWindowSize() {
-        final int minimumWindowSize = mResources.getDimensionPixelSize(
-                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
-        mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
-                        Float.NaN, Float.NaN));
-
-        final AtomicInteger actualWindowHeight = new AtomicInteger();
-        final AtomicInteger actualWindowWidth = new AtomicInteger();
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.setWindowSize(minimumWindowSize - 10,
-                    minimumWindowSize - 10);
-            actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height);
-            actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width);
-        });
-
-        assertThat(actualWindowHeight.get()).isEqualTo(minimumWindowSize);
-        assertThat(actualWindowWidth.get()).isEqualTo(minimumWindowSize);
-    }
-
-    @Test
-    public void setWindowSizeLargerThanScreenSize_enabled_windowSizeIsScreenSize() {
-        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
-        mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
-                        Float.NaN, Float.NaN));
-
-        final AtomicInteger actualWindowHeight = new AtomicInteger();
-        final AtomicInteger actualWindowWidth = new AtomicInteger();
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.setWindowSize(bounds.width() + 10, bounds.height() + 10);
-            actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height);
-            actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width);
-        });
-
-        assertThat(actualWindowHeight.get()).isEqualTo(bounds.height());
-        assertThat(actualWindowWidth.get()).isEqualTo(bounds.width());
-    }
-
-    @Test
-    public void changeMagnificationSize_expectedWindowSize() {
-        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
-
-        final float magnificationScaleLarge = 2.5f;
-        final int initSize = Math.min(bounds.width(), bounds.height()) / 3;
-        final int magnificationSize = (int) (initSize * magnificationScaleLarge)
-                - (int) (initSize * magnificationScaleLarge) % 2;
-
-        final int expectedWindowHeight = magnificationSize;
-        final int expectedWindowWidth = magnificationSize;
-
-        mInstrumentation.runOnMainSync(
-                () ->
-                        mWindowMagnificationController.updateWindowMagnificationInternal(
-                                Float.NaN, Float.NaN, Float.NaN));
-
-        final AtomicInteger actualWindowHeight = new AtomicInteger();
-        final AtomicInteger actualWindowWidth = new AtomicInteger();
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mWindowMagnificationController.changeMagnificationSize(
-                            WindowMagnificationSettings.MagnificationSize.LARGE);
-                    actualWindowHeight.set(
-                            mSurfaceControlViewHost.getView().getLayoutParams().height);
-                    actualWindowWidth.set(
-                            mSurfaceControlViewHost.getView().getLayoutParams().width);
-                });
-
-        assertThat(actualWindowHeight.get()).isEqualTo(expectedWindowHeight);
-        assertThat(actualWindowWidth.get()).isEqualTo(expectedWindowWidth);
-    }
-
-    @Test
-    public void editModeOnDragCorner_resizesWindow() {
-        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
-
-        final int startingSize = (int) (bounds.width() / 2);
-
-        mInstrumentation.runOnMainSync(
-                () ->
-                        mWindowMagnificationController.updateWindowMagnificationInternal(
-                                Float.NaN, Float.NaN, Float.NaN));
-
-        final AtomicInteger actualWindowHeight = new AtomicInteger();
-        final AtomicInteger actualWindowWidth = new AtomicInteger();
-
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mWindowMagnificationController.setWindowSize(startingSize, startingSize);
-                    mWindowMagnificationController.setEditMagnifierSizeMode(true);
-                });
-
-        waitForIdleSync();
-
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mWindowMagnificationController
-                            .onDrag(getInternalView(R.id.bottom_right_corner), 2f, 1f);
-                    actualWindowHeight.set(
-                            mSurfaceControlViewHost.getView().getLayoutParams().height);
-                    actualWindowWidth.set(
-                            mSurfaceControlViewHost.getView().getLayoutParams().width);
-                });
-
-        assertThat(actualWindowHeight.get()).isEqualTo(startingSize + 1);
-        assertThat(actualWindowWidth.get()).isEqualTo(startingSize + 2);
-    }
-
-    @Test
-    public void editModeOnDragEdge_resizesWindowInOnlyOneDirection() {
-        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
-
-        final int startingSize = (int) (bounds.width() / 2f);
-
-        mInstrumentation.runOnMainSync(
-                () ->
-                        mWindowMagnificationController.updateWindowMagnificationInternal(
-                                Float.NaN, Float.NaN, Float.NaN));
-
-        final AtomicInteger actualWindowHeight = new AtomicInteger();
-        final AtomicInteger actualWindowWidth = new AtomicInteger();
-
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mWindowMagnificationController.setWindowSize(startingSize, startingSize);
-                    mWindowMagnificationController.setEditMagnifierSizeMode(true);
-                    mWindowMagnificationController
-                            .onDrag(getInternalView(R.id.bottom_handle), 2f, 1f);
-                    actualWindowHeight.set(
-                            mSurfaceControlViewHost.getView().getLayoutParams().height);
-                    actualWindowWidth.set(
-                            mSurfaceControlViewHost.getView().getLayoutParams().width);
-                });
-        assertThat(actualWindowHeight.get()).isEqualTo(startingSize + 1);
-        assertThat(actualWindowWidth.get()).isEqualTo(startingSize);
-    }
-
-    @Test
-    public void setWindowCenterOutOfScreen_enabled_magnificationCenterIsInsideTheScreen() {
-
-        final int minimumWindowSize = mResources.getDimensionPixelSize(
-                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
-        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
-        mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
-                        Float.NaN, Float.NaN));
-
-        final AtomicInteger magnificationCenterX = new AtomicInteger();
-        final AtomicInteger magnificationCenterY = new AtomicInteger();
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.setWindowSizeAndCenter(minimumWindowSize,
-                    minimumWindowSize, bounds.right, bounds.bottom);
-            magnificationCenterX.set((int) mWindowMagnificationController.getCenterX());
-            magnificationCenterY.set((int) mWindowMagnificationController.getCenterY());
-        });
-
-        assertThat(magnificationCenterX.get()).isLessThan(bounds.right);
-        assertThat(magnificationCenterY.get()).isLessThan(bounds.bottom);
-    }
-
-    @Test
-    public void performSingleTap_DragHandle() {
-        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mWindowMagnificationController.updateWindowMagnificationInternal(
-                            1.5f, bounds.centerX(), bounds.centerY());
-                });
-        View dragButton = getInternalView(R.id.drag_handle);
-
-        // Perform a single-tap
-        final long downTime = SystemClock.uptimeMillis();
-        dragButton.dispatchTouchEvent(
-                obtainMotionEvent(downTime, 0, ACTION_DOWN, 100, 100));
-        dragButton.dispatchTouchEvent(
-                obtainMotionEvent(downTime, downTime, ACTION_UP, 100, 100));
-
-        verify(mSurfaceControlViewHost).setView(any(View.class), any());
-    }
-
-    private <T extends View> T getInternalView(@IdRes int idRes) {
-        View mirrorView = mSurfaceControlViewHost.getView();
-        T view = mirrorView.findViewById(idRes);
-        assertThat(view).isNotNull();
-        return view;
-    }
-
-    private MotionEvent obtainMotionEvent(long downTime, long eventTime, int action, float x,
-            float y) {
-        return mMotionEventHelper.obtainMotionEvent(downTime, eventTime, action, x, y);
-    }
-
-    private String getAccessibilityWindowTitle() {
-        final View mirrorView = mSurfaceControlViewHost.getView();
-        if (mirrorView == null) {
-            return null;
-        }
-        WindowManager.LayoutParams layoutParams =
-                (WindowManager.LayoutParams) mirrorView.getLayoutParams();
-        return layoutParams.accessibilityTitle.toString();
-    }
-
-    private boolean hasMagnificationOverlapFlag() {
-        return (mSysUiState.getFlags() & SYSUI_STATE_MAGNIFICATION_OVERLAP) != 0;
-    }
-
-    private void setSystemGestureInsets() {
-        final WindowInsets testInsets = new WindowInsets.Builder()
-                .setInsets(systemGestures(), Insets.of(0, 0, 0, 10))
-                .build();
-        mWindowManager.setWindowInsets(testInsets);
-    }
-
-    private int updateMirrorSurfaceMarginDimension() {
-        return mContext.getResources().getDimensionPixelSize(
-                R.dimen.magnification_mirror_surface_margin);
-    }
-
-    @Surface.Rotation
-    private int simulateRotateTheDevice() {
-        final Display display = Mockito.spy(mContext.getDisplay());
-        final int currentRotation = display.getRotation();
-        final int newRotation = (currentRotation + 1) % 4;
-        when(display.getRotation()).thenReturn(newRotation);
-        when(mContext.getDisplay()).thenReturn(display);
-        return newRotation;
-    }
-
-    // advance time based on the device frame refresh rate
-    private void advanceTimeBy(long timeDelta) {
-        advanceTimeBy(timeDelta, /* runnableOnEachRefresh= */ null);
-    }
-
-    // advance time based on the device frame refresh rate, and trigger runnable on each refresh
-    private void advanceTimeBy(long timeDelta, @Nullable Runnable runnableOnEachRefresh) {
-        final float frameRate = mContext.getDisplay().getRefreshRate();
-        final int timeSlot = (int) (1000 / frameRate);
-        int round = (int) Math.ceil((double) timeDelta / timeSlot);
-        for (; round >= 0; round--) {
-            mInstrumentation.runOnMainSync(() -> {
-                mAnimatorTestRule.advanceTimeBy(timeSlot);
-                if (runnableOnEachRefresh != null) {
-                    runnableOnEachRefresh.run();
-                }
-            });
-        }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
index 288ed4d..a1f59c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
@@ -20,18 +20,20 @@
 import android.animation.AnimatorTestRuleToolkit
 import android.animation.MotionControl
 import android.animation.recordMotion
+import android.graphics.Color
+import android.graphics.PointF
 import android.graphics.drawable.GradientDrawable
 import android.platform.test.annotations.MotionTest
 import android.view.ViewGroup
 import android.widget.FrameLayout
 import androidx.test.ext.junit.rules.ActivityScenarioRule
 import androidx.test.filters.SmallTest
-import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.activity.EmptyTestActivity
 import com.android.systemui.concurrency.fakeExecutor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.runOnMainThreadAndWaitForIdleSync
 import kotlin.test.assertTrue
 import org.junit.Rule
 import org.junit.Test
@@ -47,13 +49,25 @@
 @SmallTest
 @MotionTest
 @RunWith(ParameterizedAndroidJunit4::class)
-class TransitionAnimatorTest(val useSpring: Boolean) : SysuiTestCase() {
+class TransitionAnimatorTest(
+    private val fadeWindowBackgroundLayer: Boolean,
+    private val isLaunching: Boolean,
+    private val useSpring: Boolean,
+) : SysuiTestCase() {
     companion object {
         private const val GOLDENS_PATH = "frameworks/base/packages/SystemUI/tests/goldens"
 
-        @get:Parameters(name = "{0}")
+        @get:Parameters(name = "fadeBackground={0}, isLaunching={1}, useSpring={2}")
         @JvmStatic
-        val useSpringValues = booleanArrayOf(false, true).toList()
+        val parameterValues = buildList {
+            booleanArrayOf(true, false).forEach { fadeBackground ->
+                booleanArrayOf(true, false).forEach { isLaunching ->
+                    booleanArrayOf(true, false).forEach { useSpring ->
+                        add(arrayOf(fadeBackground, isLaunching, useSpring))
+                    }
+                }
+            }
+        }
     }
 
     private val kosmos = Kosmos()
@@ -66,11 +80,23 @@
             ActivityTransitionAnimator.SPRING_TIMINGS,
             ActivityTransitionAnimator.SPRING_INTERPOLATORS,
         )
-    private val withSpring =
-        if (useSpring) {
-            "_withSpring"
+    private val fade =
+        if (fadeWindowBackgroundLayer) {
+            "withFade"
         } else {
-            ""
+            "withoutFade"
+        }
+    private val direction =
+        if (isLaunching) {
+            "whenLaunching"
+        } else {
+            "whenReturning"
+        }
+    private val mode =
+        if (useSpring) {
+            "withSpring"
+        } else {
+            "withAnimator"
         }
 
     @get:Rule(order = 1) val activityRule = ActivityScenarioRule(EmptyTestActivity::class.java)
@@ -83,113 +109,75 @@
         )
 
     @Test
-    fun backgroundAnimation_whenLaunching() {
-        val backgroundLayer = GradientDrawable().apply { alpha = 0 }
-        val animator =
-            setUpTest(backgroundLayer, isLaunching = true).apply {
-                getInstrumentation().runOnMainSync { start() }
-            }
+    fun backgroundAnimationTimeSeries() {
+        val transitionContainer = createScene()
+        val backgroundLayer = createBackgroundLayer()
+        val animation = createAnimation(transitionContainer, backgroundLayer)
 
-        val recordedMotion = recordMotion(backgroundLayer, animator)
+        val recordedMotion = record(backgroundLayer, animation)
 
         motionRule
             .assertThat(recordedMotion)
-            .timeSeriesMatchesGolden("backgroundAnimation_whenLaunching$withSpring")
+            .timeSeriesMatchesGolden("backgroundAnimationTimeSeries_${fade}_${direction}_$mode")
     }
 
-    @Test
-    fun backgroundAnimation_whenReturning() {
-        val backgroundLayer = GradientDrawable().apply { alpha = 0 }
-        val animator =
-            setUpTest(backgroundLayer, isLaunching = false).apply {
-                getInstrumentation().runOnMainSync { start() }
-            }
-
-        val recordedMotion = recordMotion(backgroundLayer, animator)
-
-        motionRule
-            .assertThat(recordedMotion)
-            .timeSeriesMatchesGolden("backgroundAnimation_whenReturning$withSpring")
-    }
-
-    @Test
-    fun backgroundAnimationWithoutFade_whenLaunching() {
-        val backgroundLayer = GradientDrawable().apply { alpha = 0 }
-        val animator =
-            setUpTest(backgroundLayer, isLaunching = true, fadeWindowBackgroundLayer = false)
-                .apply { getInstrumentation().runOnMainSync { start() } }
-
-        val recordedMotion = recordMotion(backgroundLayer, animator)
-
-        motionRule
-            .assertThat(recordedMotion)
-            .timeSeriesMatchesGolden("backgroundAnimationWithoutFade_whenLaunching$withSpring")
-    }
-
-    @Test
-    fun backgroundAnimationWithoutFade_whenReturning() {
-        val backgroundLayer = GradientDrawable().apply { alpha = 0 }
-        val animator =
-            setUpTest(backgroundLayer, isLaunching = false, fadeWindowBackgroundLayer = false)
-                .apply { getInstrumentation().runOnMainSync { start() } }
-
-        val recordedMotion = recordMotion(backgroundLayer, animator)
-
-        motionRule
-            .assertThat(recordedMotion)
-            .timeSeriesMatchesGolden("backgroundAnimationWithoutFade_whenReturning$withSpring")
-    }
-
-    private fun setUpTest(
-        backgroundLayer: GradientDrawable,
-        isLaunching: Boolean,
-        fadeWindowBackgroundLayer: Boolean = true,
-    ): TransitionAnimator.Animation {
+    private fun createScene(): ViewGroup {
         lateinit var transitionContainer: ViewGroup
         activityRule.scenario.onActivity { activity ->
-            transitionContainer = FrameLayout(activity).apply { setBackgroundColor(0x00FF00) }
+            transitionContainer = FrameLayout(activity)
             activity.setContentView(transitionContainer)
         }
         waitForIdleSync()
+        return transitionContainer
+    }
 
+    private fun createBackgroundLayer() =
+        GradientDrawable().apply {
+            setColor(Color.BLACK)
+            alpha = 0
+        }
+
+    private fun createAnimation(
+        transitionContainer: ViewGroup,
+        backgroundLayer: GradientDrawable,
+    ): TransitionAnimator.Animation {
         val controller = TestController(transitionContainer, isLaunching)
-        return transitionAnimator.createAnimation(
-            controller,
-            controller.createAnimatorState(),
-            createEndState(transitionContainer),
-            backgroundLayer,
-            fadeWindowBackgroundLayer,
-            useSpring = useSpring,
-        )
-    }
 
-    private fun createEndState(container: ViewGroup): TransitionAnimator.State {
         val containerLocation = IntArray(2)
-        container.getLocationOnScreen(containerLocation)
-        return TransitionAnimator.State(
-            left = containerLocation[0],
-            top = containerLocation[1],
-            right = containerLocation[0] + 320,
-            bottom = containerLocation[1] + 690,
-            topCornerRadius = 0f,
-            bottomCornerRadius = 0f,
-        )
+        transitionContainer.getLocationOnScreen(containerLocation)
+        val endState =
+            TransitionAnimator.State(
+                left = containerLocation[0],
+                top = containerLocation[1],
+                right = containerLocation[0] + 320,
+                bottom = containerLocation[1] + 690,
+                topCornerRadius = 0f,
+                bottomCornerRadius = 0f,
+            )
+
+        val startVelocity =
+            if (useSpring) {
+                PointF(2500f, 30000f)
+            } else {
+                null
+            }
+
+        return transitionAnimator
+            .createAnimation(
+                controller,
+                controller.createAnimatorState(),
+                endState,
+                backgroundLayer,
+                fadeWindowBackgroundLayer,
+                startVelocity = startVelocity,
+            )
+            .apply { runOnMainThreadAndWaitForIdleSync { start() } }
     }
 
-    private fun recordMotion(
+    private fun record(
         backgroundLayer: GradientDrawable,
         animation: TransitionAnimator.Animation,
     ): RecordedMotion {
-        fun record(motionControl: MotionControl, sampleIntervalMs: Long): RecordedMotion {
-            return motionRule.recordMotion(
-                AnimatorRuleRecordingSpec(backgroundLayer, motionControl, sampleIntervalMs) {
-                    feature(DrawableFeatureCaptures.bounds, "bounds")
-                    feature(DrawableFeatureCaptures.cornerRadii, "corner_radii")
-                    feature(DrawableFeatureCaptures.alpha, "alpha")
-                }
-            )
-        }
-
         val motionControl: MotionControl
         val sampleIntervalMs: Long
         if (useSpring) {
@@ -204,9 +192,13 @@
             sampleIntervalMs = 20L
         }
 
-        var recording: RecordedMotion? = null
-        getInstrumentation().runOnMainSync { recording = record(motionControl, sampleIntervalMs) }
-        return recording!!
+        return motionRule.recordMotion(
+            AnimatorRuleRecordingSpec(backgroundLayer, motionControl, sampleIntervalMs) {
+                feature(DrawableFeatureCaptures.bounds, "bounds")
+                feature(DrawableFeatureCaptures.cornerRadii, "corner_radii")
+                feature(DrawableFeatureCaptures.alpha, "alpha")
+            }
+        )
     }
 }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index e1b8a1d..91f9cce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -212,6 +212,20 @@
     }
 
     @Test
+    fun testDimissOnLock() {
+        val container = initializeFingerprintContainer(addToView = true)
+        assertThat(container.parent).isNotNull()
+        val root = container.rootView
+
+        // Simulate sleep/lock invocation
+        container.onStartedGoingToSleep()
+        waitForIdleSync()
+
+        assertThat(container.parent).isNull()
+        assertThat(root.isAttachedToWindow).isFalse()
+    }
+
+    @Test
     fun testCredentialPasswordDismissesOnBack() {
         val container = initializeCredentialPasswordContainer(addToView = true)
         assertThat(container.parent).isNotNull()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt
index 9ace8e9..387cc08 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt
@@ -42,6 +42,7 @@
 import com.android.systemui.res.R
 import com.android.systemui.scene.domain.startable.sceneContainerStartable
 import com.android.systemui.testKosmos
+import kotlin.time.Duration.Companion.seconds
 import org.junit.After
 import org.junit.Before
 import org.junit.Rule
@@ -109,7 +110,7 @@
 
     @Test
     fun doubleClick_swapSide() =
-        motionTestRule.runTest {
+        motionTestRule.runTest(timeout = 30.seconds) {
             val motion =
                 recordMotion(
                     content = { BouncerContentUnderTest() },
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt
index 088bb02..768f1dc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.lifecycle.activateIn
 import com.android.systemui.motion.createSysUiComposeMotionTestRule
 import com.android.systemui.testKosmos
+import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.takeWhile
@@ -71,7 +72,7 @@
 
     @Test
     fun entryAnimation() =
-        motionTestRule.runTest {
+        motionTestRule.runTest(timeout = 30.seconds) {
             val motion =
                 recordMotion(
                     content = { play -> if (play) PatternBouncerUnderTest() },
@@ -89,7 +90,7 @@
 
     @Test
     fun animateFailure() =
-        motionTestRule.runTest {
+        motionTestRule.runTest(timeout = 30.seconds) {
             val failureAnimationMotionControl =
                 MotionControl(
                     delayReadyToPlay = {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
index 6061063..5624815 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
@@ -20,6 +20,7 @@
 
 import static com.android.systemui.Flags.FLAG_CLIPBOARD_SHARED_TRANSITIONS;
 import static com.android.systemui.Flags.FLAG_CLIPBOARD_USE_DESCRIPTION_MIMETYPE;
+import static com.android.systemui.Flags.FLAG_SHOW_CLIPBOARD_INDICATION;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_SHOWN;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISS_TAPPED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_EXPANDED_FROM_MINIMIZED;
@@ -121,6 +122,24 @@
 
     private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
 
+    private class FakeClipboardIndicationProvider implements ClipboardIndicationProvider {
+        private ClipboardIndicationCallback mIndicationCallback;
+
+        public void notifyIndicationTextChanged(CharSequence indicationText) {
+            if (mIndicationCallback != null) {
+                mIndicationCallback.onIndicationTextChanged(indicationText);
+            }
+        }
+
+        @Override
+        public void getIndicationText(ClipboardIndicationCallback callback) {
+            mIndicationCallback = callback;
+        }
+    }
+
+    private FakeClipboardIndicationProvider mClipboardIndicationProvider =
+            new FakeClipboardIndicationProvider();
+
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
@@ -156,6 +175,7 @@
                 mExecutor,
                 mClipboardImageLoader,
                 mClipboardTransitionExecutor,
+                mClipboardIndicationProvider,
                 mUiEventLogger);
         verify(mClipboardOverlayView).setCallbacks(mOverlayCallbacksCaptor.capture());
         mCallbacks = mOverlayCallbacksCaptor.getValue();
@@ -305,6 +325,17 @@
     }
 
     @Test
+    @EnableFlags(FLAG_SHOW_CLIPBOARD_INDICATION)
+    public void test_onIndicationTextChanged_setIndicationTextCorrectly() {
+        initController();
+        mOverlayController.setClipData(mSampleClipData, "");
+
+        mClipboardIndicationProvider.notifyIndicationTextChanged("copied");
+
+        verify(mClipboardOverlayView).setIndicationText("copied");
+    }
+
+    @Test
     @DisableFlags(FLAG_CLIPBOARD_SHARED_TRANSITIONS)
     public void test_viewCallbacks_onShareTapped_sharedTransitionsOff() {
         initController();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt
index 7709a65..0ab4cd0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt
@@ -21,7 +21,6 @@
 import android.graphics.Rect
 import android.os.UserHandle
 import android.platform.test.annotations.DisableFlags
-import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.SetFlagsRule
 import android.view.Display.DEFAULT_DISPLAY
 import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD
@@ -76,9 +75,8 @@
 
     /** Tests applying CaptureParameters with 'IsolatedTask' CaptureType */
     @Test
-    @EnableFlags(Flags.FLAG_SCREENSHOT_POLICY_SPLIT_AND_DESKTOP_MODE)
     fun testProcess_newPolicy_isolatedTask() = runTest {
-        val taskImage = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+        val taskImage = Bitmap.createBitmap(200, 100, Bitmap.Config.ARGB_8888)
 
         /* Create a policy request processor with no capture policies */
         val requestProcessor =
@@ -96,9 +94,15 @@
             requestProcessor.modify(
                 screenshotRequest,
                 CaptureParameters(
-                    IsolatedTask(taskId = TASK_ID, taskBounds = null),
-                    ComponentName.unflattenFromString(FILES),
-                    UserHandle.of(WORK),
+                    type = IsolatedTask(taskId = 100, taskBounds = Rect(0, 100, 200, 200)),
+                    contentTask =
+                        TaskReference(
+                            taskId = 1001,
+                            component = ComponentName.unflattenFromString(FILES)!!,
+                            owner = UserHandle.CURRENT,
+                            bounds = Rect(100, 100, 200, 200),
+                        ),
+                    owner = UserHandle.of(WORK),
                 ),
             )
 
@@ -112,14 +116,13 @@
             .that(result.topComponent)
             .isEqualTo(ComponentName.unflattenFromString(FILES))
 
-        assertWithMessage("Task ID").that(result.taskId).isEqualTo(TASK_ID)
+        assertWithMessage("Task ID").that(result.taskId).isEqualTo(1001)
     }
 
     /** Tests applying CaptureParameters with 'FullScreen' CaptureType */
     @Test
-    @EnableFlags(Flags.FLAG_SCREENSHOT_POLICY_SPLIT_AND_DESKTOP_MODE)
     fun testProcess_newPolicy_fullScreen() = runTest {
-        val screenImage = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+        val screenImage = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888)
 
         /* Create a policy request processor with no capture policies */
         val requestProcessor =
@@ -136,7 +139,17 @@
         val result =
             requestProcessor.modify(
                 screenshotRequest,
-                CaptureParameters(FullScreen(displayId = 0), defaultComponent, defaultOwner),
+                CaptureParameters(
+                    type = FullScreen(displayId = 0),
+                    contentTask =
+                        TaskReference(
+                            taskId = 1234,
+                            component = defaultComponent,
+                            owner = UserHandle.CURRENT,
+                            bounds = Rect(1, 2, 3, 4),
+                        ),
+                    owner = defaultOwner,
+                ),
             )
 
         assertWithMessage("The result bitmap").that(result.bitmap).isSameInstanceAs(screenImage)
@@ -149,7 +162,11 @@
             .that(result.topComponent)
             .isEqualTo(defaultComponent)
 
-        assertWithMessage("Task ID").that(result.taskId).isEqualTo(-1)
+        assertWithMessage("The bounds of the screenshot")
+            .that(result.originalScreenBounds)
+            .isEqualTo(Rect(0, 0, 100, 100))
+
+        assertWithMessage("Task ID").that(result.taskId).isEqualTo(1234)
     }
 
     /** Tests behavior when no policies are applied */
@@ -230,7 +247,7 @@
                 policy = "",
                 reason = "",
                 parameters =
-                    CaptureParameters(
+                    LegacyCaptureParameters(
                         IsolatedTask(taskId = 0, taskBounds = null),
                         null,
                         UserHandle.CURRENT,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
index a8929a6..f870200 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.statusbar.notification.logging;
 
-import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
-
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -59,8 +57,6 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository;
-import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
 import com.android.systemui.statusbar.notification.logging.nano.Notifications;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor;
@@ -118,11 +114,6 @@
     private final FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository();
     private final PowerInteractor mPowerInteractor =
             PowerInteractorFactory.create().getPowerInteractor();
-    private final ActiveNotificationListRepository mActiveNotificationListRepository =
-            new ActiveNotificationListRepository();
-    private final ActiveNotificationsInteractor mActiveNotificationsInteractor =
-            new ActiveNotificationsInteractor(mActiveNotificationListRepository,
-                    StandardTestDispatcher(null, null));
     private WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor;
     private final JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope());
 
@@ -137,7 +128,7 @@
                 mKeyguardRepository,
                 mHeadsUpManager,
                 mPowerInteractor,
-                mActiveNotificationsInteractor,
+                mKosmos.getActiveNotificationsInteractor(),
                 () -> mKosmos.getSceneInteractor());
         mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
deleted file mode 100644
index 625963f..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ /dev/null
@@ -1,584 +0,0 @@
-/*
- * Copyright (C) 2018 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.row;
-
-import static android.app.AppOpsManager.OP_CAMERA;
-import static android.app.AppOpsManager.OP_RECORD_AUDIO;
-import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
-import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
-import static android.app.NotificationManager.IMPORTANCE_HIGH;
-import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
-
-import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
-
-import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertTrue;
-
-import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.mock;
-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 static org.mockito.Mockito.when;
-
-import android.app.INotificationManager;
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.content.Intent;
-import android.content.pm.LauncherApps;
-import android.content.pm.PackageManager;
-import android.content.pm.ShortcutManager;
-import android.graphics.Color;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.service.notification.StatusBarNotification;
-import android.testing.TestableLooper;
-import android.util.ArraySet;
-import android.view.View;
-import android.view.accessibility.AccessibilityManager;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.logging.testing.UiEventLoggerFake;
-import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
-import com.android.systemui.kosmos.KosmosJavaAdapter;
-import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.power.domain.interactor.PowerInteractorFactory;
-import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository;
-import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
-import com.android.systemui.settings.UserContextProvider;
-import com.android.systemui.shade.ShadeController;
-import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.NotificationPresenter;
-import com.android.systemui.statusbar.notification.AssistantFeedbackController;
-import com.android.systemui.statusbar.notification.NotificationActivityStarter;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository;
-import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
-import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
-import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
-import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.kotlin.JavaAdapter;
-import com.android.systemui.wmshell.BubblesManager;
-
-import kotlinx.coroutines.test.TestScope;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import java.util.Optional;
-
-/**
- * Tests for {@link NotificationGutsManager}.
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-@TestableLooper.RunWithLooper
-public class NotificationGutsManagerTest extends SysuiTestCase {
-    private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId";
-
-    private NotificationChannel mTestNotificationChannel = new NotificationChannel(
-            TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT);
-
-    private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
-    private final TestScope mTestScope = mKosmos.getTestScope();
-    private final JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope());
-    private final FakeExecutor mExecutor = mKosmos.getFakeExecutor();
-    private final Handler mHandler = mKosmos.getFakeExecutorHandler();
-    private NotificationTestHelper mHelper;
-    private NotificationGutsManager mGutsManager;
-
-    @Rule public MockitoRule mockito = MockitoJUnit.rule();
-    @Mock private MetricsLogger mMetricsLogger;
-    @Mock private OnUserInteractionCallback mOnUserInteractionCallback;
-    @Mock private NotificationPresenter mPresenter;
-    @Mock private NotificationActivityStarter mNotificationActivityStarter;
-    @Mock private NotificationListContainer mNotificationListContainer;
-    @Mock private OnSettingsClickListener mOnSettingsClickListener;
-    @Mock private DeviceProvisionedController mDeviceProvisionedController;
-    @Mock private AccessibilityManager mAccessibilityManager;
-    @Mock private HighPriorityProvider mHighPriorityProvider;
-    @Mock private INotificationManager mINotificationManager;
-    @Mock private IStatusBarService mBarService;
-    @Mock private LauncherApps mLauncherApps;
-    @Mock private ShortcutManager mShortcutManager;
-    @Mock private ChannelEditorDialogController mChannelEditorDialogController;
-    @Mock private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
-    @Mock private UserContextProvider mContextTracker;
-    @Mock private BubblesManager mBubblesManager;
-    @Mock private ShadeController mShadeController;
-    @Mock private PeopleSpaceWidgetManager mPeopleSpaceWidgetManager;
-    @Mock private AssistantFeedbackController mAssistantFeedbackController;
-    @Mock private NotificationLockscreenUserManager mNotificationLockscreenUserManager;
-    @Mock private StatusBarStateController mStatusBarStateController;
-    @Mock private HeadsUpManager mHeadsUpManager;
-    @Mock private ActivityStarter mActivityStarter;
-
-    @Mock private UserManager mUserManager;
-
-    private final ActiveNotificationListRepository mActiveNotificationListRepository =
-            new ActiveNotificationListRepository();
-    private final ActiveNotificationsInteractor mActiveNotificationsInteractor =
-            new ActiveNotificationsInteractor(mActiveNotificationListRepository,
-                    StandardTestDispatcher(null, null));
-
-    private WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor;
-
-    @Before
-    public void setUp() {
-        allowTestableLooperAsMainThread();
-        mHelper = new NotificationTestHelper(mContext, mDependency);
-        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
-
-        mWindowRootViewVisibilityInteractor = new WindowRootViewVisibilityInteractor(
-                mTestScope.getBackgroundScope(),
-                new WindowRootViewVisibilityRepository(mBarService, mExecutor),
-                new FakeKeyguardRepository(),
-                mHeadsUpManager,
-                PowerInteractorFactory.create().getPowerInteractor(),
-                mActiveNotificationsInteractor,
-                () -> mKosmos.getSceneInteractor()
-        );
-
-        mGutsManager = new NotificationGutsManager(
-                mContext,
-                mHandler,
-                mHandler,
-                mJavaAdapter,
-                mAccessibilityManager,
-                mHighPriorityProvider,
-                mINotificationManager,
-                mUserManager,
-                mPeopleSpaceWidgetManager,
-                mLauncherApps,
-                mShortcutManager,
-                mChannelEditorDialogController,
-                mContextTracker,
-                mAssistantFeedbackController,
-                Optional.of(mBubblesManager),
-                new UiEventLoggerFake(),
-                mOnUserInteractionCallback,
-                mShadeController,
-                mWindowRootViewVisibilityInteractor,
-                mNotificationLockscreenUserManager,
-                mStatusBarStateController,
-                mBarService,
-                mDeviceProvisionedController,
-                mMetricsLogger,
-                mHeadsUpManager,
-                mActivityStarter);
-        mGutsManager.setUpWithPresenter(mPresenter, mNotificationListContainer,
-                mOnSettingsClickListener);
-        mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
-        mGutsManager.start();
-    }
-
-    ////////////////////////////////////////////////////////////////////////////////////////////////
-    // Test methods:
-
-    @Test
-    public void testOpenAndCloseGuts() {
-        NotificationGuts guts = spy(new NotificationGuts(mContext));
-        when(guts.post(any())).thenAnswer(invocation -> {
-            mHandler.post(((Runnable) invocation.getArguments()[0]));
-            return null;
-        });
-
-        // Test doesn't support animation since the guts view is not attached.
-        doNothing().when(guts).openControls(
-                anyInt(),
-                anyInt(),
-                anyBoolean(),
-                any(Runnable.class));
-
-        ExpandableNotificationRow realRow = createTestNotificationRow();
-        NotificationMenuRowPlugin.MenuItem menuItem = createTestMenuItem(realRow);
-
-        ExpandableNotificationRow row = spy(realRow);
-        when(row.getWindowToken()).thenReturn(new Binder());
-        when(row.getGuts()).thenReturn(guts);
-
-        assertTrue(mGutsManager.openGutsInternal(row, 0, 0, menuItem));
-        assertEquals(View.INVISIBLE, guts.getVisibility());
-        mExecutor.runAllReady();
-        verify(guts).openControls(
-                anyInt(),
-                anyInt(),
-                anyBoolean(),
-                any(Runnable.class));
-        verify(mHeadsUpManager).setGutsShown(realRow.getEntry(), true);
-
-        assertEquals(View.VISIBLE, guts.getVisibility());
-        mGutsManager.closeAndSaveGuts(false, false, true, 0, 0, false);
-
-        verify(guts).closeControls(anyBoolean(), anyBoolean(), anyInt(), anyInt(), anyBoolean());
-        verify(row, times(1)).setGutsView(any());
-        mExecutor.runAllReady();
-        verify(mHeadsUpManager).setGutsShown(realRow.getEntry(), false);
-    }
-
-    @Test
-    public void testLockscreenShadeVisible_visible_gutsNotClosed() {
-        // First, start out lockscreen or shade as not visible
-        mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(false);
-        mTestScope.getTestScheduler().runCurrent();
-
-        NotificationGuts guts = mock(NotificationGuts.class);
-        mGutsManager.setExposedGuts(guts);
-
-        // WHEN the lockscreen or shade becomes visible
-        mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true);
-        mTestScope.getTestScheduler().runCurrent();
-
-        // THEN the guts are not closed
-        verify(guts, never()).removeCallbacks(any());
-        verify(guts, never()).closeControls(
-                anyBoolean(), anyBoolean(), anyInt(), anyInt(), anyBoolean());
-    }
-
-    @Test
-    public void testLockscreenShadeVisible_notVisible_gutsClosed() {
-        // First, start out lockscreen or shade as visible
-        mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true);
-        mTestScope.getTestScheduler().runCurrent();
-
-        NotificationGuts guts = mock(NotificationGuts.class);
-        mGutsManager.setExposedGuts(guts);
-
-        // WHEN the lockscreen or shade is no longer visible
-        mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(false);
-        mTestScope.getTestScheduler().runCurrent();
-
-        // THEN the guts are closed
-        verify(guts).removeCallbacks(any());
-        verify(guts).closeControls(
-                /* leavebehinds= */ eq(true),
-                /* controls= */ eq(true),
-                /* x= */ anyInt(),
-                /* y= */ anyInt(),
-                /* force= */ eq(true));
-    }
-
-    @Test
-    public void testLockscreenShadeVisible_notVisible_listContainerReset() {
-        // First, start out lockscreen or shade as visible
-        mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true);
-        mTestScope.getTestScheduler().runCurrent();
-
-        // WHEN the lockscreen or shade is no longer visible
-        mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(false);
-        mTestScope.getTestScheduler().runCurrent();
-
-        // THEN the list container is reset
-        verify(mNotificationListContainer).resetExposedMenuView(anyBoolean(), anyBoolean());
-    }
-
-    @Test
-    public void testChangeDensityOrFontScale() {
-        NotificationGuts guts = spy(new NotificationGuts(mContext));
-        when(guts.post(any())).thenAnswer(invocation -> {
-            mHandler.post(((Runnable) invocation.getArguments()[0]));
-            return null;
-        });
-
-        // Test doesn't support animation since the guts view is not attached.
-        doNothing().when(guts).openControls(
-                anyInt(),
-                anyInt(),
-                anyBoolean(),
-                any(Runnable.class));
-
-        ExpandableNotificationRow realRow = createTestNotificationRow();
-        NotificationMenuRowPlugin.MenuItem menuItem = createTestMenuItem(realRow);
-
-        ExpandableNotificationRow row = spy(realRow);
-
-        when(row.getWindowToken()).thenReturn(new Binder());
-        when(row.getGuts()).thenReturn(guts);
-        doNothing().when(row).ensureGutsInflated();
-
-        NotificationEntry realEntry = realRow.getEntry();
-        NotificationEntry entry = spy(realEntry);
-
-        when(entry.getRow()).thenReturn(row);
-        when(entry.getGuts()).thenReturn(guts);
-
-        assertTrue(mGutsManager.openGutsInternal(row, 0, 0, menuItem));
-        mExecutor.runAllReady();
-        verify(guts).openControls(
-                anyInt(),
-                anyInt(),
-                anyBoolean(),
-                any(Runnable.class));
-
-        // called once by mGutsManager.bindGuts() in mGutsManager.openGuts()
-        verify(row).setGutsView(any());
-
-        row.onDensityOrFontScaleChanged();
-        mGutsManager.onDensityOrFontScaleChanged(entry);
-
-        mExecutor.runAllReady();
-
-        mGutsManager.closeAndSaveGuts(false, false, false, 0, 0, false);
-
-        verify(guts).closeControls(anyBoolean(), anyBoolean(), anyInt(), anyInt(), anyBoolean());
-
-        // called again by mGutsManager.bindGuts(), in mGutsManager.onDensityOrFontScaleChanged()
-        verify(row, times(2)).setGutsView(any());
-    }
-
-    @Test
-    public void testAppOpsSettingsIntent_camera() {
-        ArraySet<Integer> ops = new ArraySet<>();
-        ops.add(OP_CAMERA);
-        mGutsManager.startAppOpsSettingsActivity("", 0, ops, null);
-        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
-        verify(mNotificationActivityStarter, times(1))
-                .startNotificationGutsIntent(captor.capture(), anyInt(), any());
-        assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.getValue().getAction());
-    }
-
-    @Test
-    public void testAppOpsSettingsIntent_mic() {
-        ArraySet<Integer> ops = new ArraySet<>();
-        ops.add(OP_RECORD_AUDIO);
-        mGutsManager.startAppOpsSettingsActivity("", 0, ops, null);
-        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
-        verify(mNotificationActivityStarter, times(1))
-                .startNotificationGutsIntent(captor.capture(), anyInt(), any());
-        assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.getValue().getAction());
-    }
-
-    @Test
-    public void testAppOpsSettingsIntent_camera_mic() {
-        ArraySet<Integer> ops = new ArraySet<>();
-        ops.add(OP_CAMERA);
-        ops.add(OP_RECORD_AUDIO);
-        mGutsManager.startAppOpsSettingsActivity("", 0, ops, null);
-        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
-        verify(mNotificationActivityStarter, times(1))
-                .startNotificationGutsIntent(captor.capture(), anyInt(), any());
-        assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.getValue().getAction());
-    }
-
-    @Test
-    public void testAppOpsSettingsIntent_overlay() {
-        ArraySet<Integer> ops = new ArraySet<>();
-        ops.add(OP_SYSTEM_ALERT_WINDOW);
-        mGutsManager.startAppOpsSettingsActivity("", 0, ops, null);
-        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
-        verify(mNotificationActivityStarter, times(1))
-                .startNotificationGutsIntent(captor.capture(), anyInt(), any());
-        assertEquals(Settings.ACTION_MANAGE_APP_OVERLAY_PERMISSION, captor.getValue().getAction());
-    }
-
-    @Test
-    public void testAppOpsSettingsIntent_camera_mic_overlay() {
-        ArraySet<Integer> ops = new ArraySet<>();
-        ops.add(OP_CAMERA);
-        ops.add(OP_RECORD_AUDIO);
-        ops.add(OP_SYSTEM_ALERT_WINDOW);
-        mGutsManager.startAppOpsSettingsActivity("", 0, ops, null);
-        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
-        verify(mNotificationActivityStarter, times(1))
-                .startNotificationGutsIntent(captor.capture(), anyInt(), any());
-        assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.getValue().getAction());
-    }
-
-    @Test
-    public void testAppOpsSettingsIntent_camera_overlay() {
-        ArraySet<Integer> ops = new ArraySet<>();
-        ops.add(OP_CAMERA);
-        ops.add(OP_SYSTEM_ALERT_WINDOW);
-        mGutsManager.startAppOpsSettingsActivity("", 0, ops, null);
-        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
-        verify(mNotificationActivityStarter, times(1))
-                .startNotificationGutsIntent(captor.capture(), anyInt(), any());
-        assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.getValue().getAction());
-    }
-
-    @Test
-    public void testAppOpsSettingsIntent_mic_overlay() {
-        ArraySet<Integer> ops = new ArraySet<>();
-        ops.add(OP_RECORD_AUDIO);
-        ops.add(OP_SYSTEM_ALERT_WINDOW);
-        mGutsManager.startAppOpsSettingsActivity("", 0, ops, null);
-        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
-        verify(mNotificationActivityStarter, times(1))
-                .startNotificationGutsIntent(captor.capture(), anyInt(), any());
-        assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.getValue().getAction());
-    }
-
-    @Test
-    public void testInitializeNotificationInfoView_highPriority() throws Exception {
-        NotificationInfo notificationInfoView = mock(NotificationInfo.class);
-        ExpandableNotificationRow row = spy(mHelper.createRow());
-        final NotificationEntry entry = row.getEntry();
-        modifyRanking(entry)
-                .setUserSentiment(USER_SENTIMENT_NEGATIVE)
-                .setImportance(IMPORTANCE_HIGH)
-                .build();
-
-        when(row.getIsNonblockable()).thenReturn(false);
-        when(mHighPriorityProvider.isHighPriority(entry)).thenReturn(true);
-        StatusBarNotification statusBarNotification = entry.getSbn();
-        mGutsManager.initializeNotificationInfo(row, notificationInfoView);
-
-        verify(notificationInfoView).bindNotification(
-                any(PackageManager.class),
-                any(INotificationManager.class),
-                eq(mOnUserInteractionCallback),
-                eq(mChannelEditorDialogController),
-                eq(statusBarNotification.getPackageName()),
-                any(NotificationChannel.class),
-                eq(entry),
-                any(NotificationInfo.OnSettingsClickListener.class),
-                any(NotificationInfo.OnAppSettingsClickListener.class),
-                any(UiEventLogger.class),
-                eq(false),
-                eq(false),
-                eq(true), /* wasShownHighPriority */
-                eq(mAssistantFeedbackController),
-                any(MetricsLogger.class));
-    }
-
-    @Test
-    public void testInitializeNotificationInfoView_PassesAlongProvisionedState() throws Exception {
-        NotificationInfo notificationInfoView = mock(NotificationInfo.class);
-        ExpandableNotificationRow row = spy(mHelper.createRow());
-        modifyRanking(row.getEntry())
-                .setUserSentiment(USER_SENTIMENT_NEGATIVE)
-                .build();
-        when(row.getIsNonblockable()).thenReturn(false);
-        StatusBarNotification statusBarNotification = row.getEntry().getSbn();
-        NotificationEntry entry = row.getEntry();
-
-        when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
-
-        mGutsManager.initializeNotificationInfo(row, notificationInfoView);
-
-        verify(notificationInfoView).bindNotification(
-                any(PackageManager.class),
-                any(INotificationManager.class),
-                eq(mOnUserInteractionCallback),
-                eq(mChannelEditorDialogController),
-                eq(statusBarNotification.getPackageName()),
-                any(NotificationChannel.class),
-                eq(entry),
-                any(NotificationInfo.OnSettingsClickListener.class),
-                any(NotificationInfo.OnAppSettingsClickListener.class),
-                any(UiEventLogger.class),
-                eq(true),
-                eq(false),
-                eq(false), /* wasShownHighPriority */
-                eq(mAssistantFeedbackController),
-                any(MetricsLogger.class));
-    }
-
-    @Test
-    public void testInitializeNotificationInfoView_withInitialAction() throws Exception {
-        NotificationInfo notificationInfoView = mock(NotificationInfo.class);
-        ExpandableNotificationRow row = spy(mHelper.createRow());
-        modifyRanking(row.getEntry())
-                .setUserSentiment(USER_SENTIMENT_NEGATIVE)
-                .build();
-        when(row.getIsNonblockable()).thenReturn(false);
-        StatusBarNotification statusBarNotification = row.getEntry().getSbn();
-        NotificationEntry entry = row.getEntry();
-
-        mGutsManager.initializeNotificationInfo(row, notificationInfoView);
-
-        verify(notificationInfoView).bindNotification(
-                any(PackageManager.class),
-                any(INotificationManager.class),
-                eq(mOnUserInteractionCallback),
-                eq(mChannelEditorDialogController),
-                eq(statusBarNotification.getPackageName()),
-                any(NotificationChannel.class),
-                eq(entry),
-                any(NotificationInfo.OnSettingsClickListener.class),
-                any(NotificationInfo.OnAppSettingsClickListener.class),
-                any(UiEventLogger.class),
-                eq(false),
-                eq(false),
-                eq(false), /* wasShownHighPriority */
-                eq(mAssistantFeedbackController),
-                any(MetricsLogger.class));
-    }
-
-    ////////////////////////////////////////////////////////////////////////////////////////////////
-    // Utility methods:
-
-    private ExpandableNotificationRow createTestNotificationRow() {
-        Notification.Builder nb = new Notification.Builder(mContext,
-                mTestNotificationChannel.getId())
-                                        .setContentTitle("foo")
-                                        .setColorized(true).setColor(Color.RED)
-                                        .setFlag(Notification.FLAG_CAN_COLORIZE, true)
-                                        .setSmallIcon(android.R.drawable.sym_def_app_icon);
-
-        try {
-            ExpandableNotificationRow row = mHelper.createRow(nb.build());
-            modifyRanking(row.getEntry())
-                    .setChannel(mTestNotificationChannel)
-                    .build();
-            return row;
-        } catch (Exception e) {
-            fail();
-            return null;
-        }
-    }
-
-    private NotificationMenuRowPlugin.MenuItem createTestMenuItem(ExpandableNotificationRow row) {
-        NotificationMenuRowPlugin menuRow =
-                new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
-        menuRow.createMenu(row, row.getEntry().getSbn());
-
-        NotificationMenuRowPlugin.MenuItem menuItem = menuRow.getLongpressMenuItem(mContext);
-        assertNotNull(menuItem);
-        return menuItem;
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 6069b44..d2350bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -19,6 +19,7 @@
 import static android.view.View.GONE;
 import static android.view.WindowInsets.Type.ime;
 
+import static com.android.systemui.flags.SceneContainerFlagParameterizationKt.parameterizeSceneContainerFlag;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.RUBBER_BAND_FACTOR_NORMAL;
@@ -54,6 +55,7 @@
 import android.os.SystemClock;
 import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.FlagsParameterization;
 import android.testing.TestableLooper;
 import android.testing.TestableResources;
 import android.util.MathUtils;
@@ -64,13 +66,13 @@
 import android.view.WindowInsetsAnimation;
 import android.widget.TextView;
 
-import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.keyguard.BouncerPanelExpansionCalculator;
 import com.android.systemui.ExpandHelper;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.BrokenWithSceneContainer;
 import com.android.systemui.flags.DisableSceneContainer;
 import com.android.systemui.flags.EnableSceneContainer;
 import com.android.systemui.flags.FakeFeatureFlags;
@@ -118,16 +120,25 @@
 import org.mockito.junit.MockitoRule;
 
 import java.util.ArrayList;
+import java.util.List;
 import java.util.function.Consumer;
 
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
 /**
  * Tests for {@link NotificationStackScrollLayout}.
  */
 @SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(ParameterizedAndroidJunit4.class)
 @TestableLooper.RunWithLooper
 public class NotificationStackScrollLayoutTest extends SysuiTestCase {
 
+    @Parameters(name = "{0}")
+    public static List<FlagsParameterization> getParams() {
+        return parameterizeSceneContainerFlag();
+    }
+
     private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
     private NotificationStackScrollLayout mStackScroller;  // Normally test this
     private NotificationStackScrollLayout mStackScrollerInternal;  // See explanation below
@@ -154,6 +165,11 @@
     @Mock private LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
     @Mock private AvalancheController mAvalancheController;
 
+    public NotificationStackScrollLayoutTest(FlagsParameterization flags) {
+        super();
+        mSetFlagsRule.setFlagsParameterization(flags);
+    }
+
     @Before
     public void setUp() throws Exception {
         allowTestableLooperAsMainThread();
@@ -353,6 +369,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void updateStackEndHeightAndStackHeight_onlyUpdatesStackHeightDuringSwipeUp() {
         final float expansionFraction = 0.5f;
         mAmbientState.setStatusBarState(StatusBarState.KEYGUARD);
@@ -366,6 +383,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void setPanelFlinging_updatesStackEndHeightOnlyOnFinish() {
         final float expansionFraction = 0.5f;
         mAmbientState.setStatusBarState(StatusBarState.KEYGUARD);
@@ -729,7 +747,9 @@
     }
 
     @Test
-    @DisableFlags({FooterViewRefactor.FLAG_NAME, ModesEmptyShadeFix.FLAG_NAME})
+    @DisableFlags({FooterViewRefactor.FLAG_NAME,
+        ModesEmptyShadeFix.FLAG_NAME,
+        NotifRedesignFooter.FLAG_NAME})
     public void testReInflatesFooterViews() {
         when(mEmptyShadeView.getTextResource()).thenReturn(R.string.empty_shade_text);
         clearInvocations(mStackScroller);
@@ -1429,6 +1449,7 @@
 
     @Test
     @EnableFlags(NotificationThrottleHun.FLAG_NAME)
+    @BrokenWithSceneContainer(bugId = 332732878) // because NSSL#mAnimationsEnabled is always true
     public void testGenerateHeadsUpAnimation_isSeenInShade_noAnimation() {
         // GIVEN NSSL is ready for HUN animations
         Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
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 f472fd1..7d019bf 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
@@ -155,6 +155,7 @@
 import com.android.systemui.shade.ShadeControllerImpl;
 import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.shade.ShadeLogger;
+import com.android.systemui.shade.StatusBarLongPressGestureDetector;
 import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.KeyboardShortcutListSearch;
@@ -174,8 +175,8 @@
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
 import com.android.systemui.statusbar.core.StatusBarConnectedDisplays;
 import com.android.systemui.statusbar.core.StatusBarInitializerImpl;
-import com.android.systemui.statusbar.core.StatusBarOrchestrator;
 import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository;
+import com.android.systemui.statusbar.data.repository.StatusBarModePerDisplayRepository;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
@@ -371,7 +372,7 @@
     @Mock private EmergencyGestureIntentFactory mEmergencyGestureIntentFactory;
     @Mock private NotificationSettingsInteractor mNotificationSettingsInteractor;
     @Mock private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
-    @Mock private StatusBarOrchestrator mStatusBarOrchestrator;
+    @Mock private StatusBarLongPressGestureDetector mStatusBarLongPressGestureDetector;
     private ShadeController mShadeController;
     private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
     private final FakeGlobalSettings mFakeGlobalSettings = new FakeGlobalSettings();
@@ -387,6 +388,9 @@
 
     private final BrightnessMirrorShowingInteractor mBrightnessMirrorShowingInteractor =
             mKosmos.getBrightnessMirrorShowingInteractor();
+
+    private final StatusBarModePerDisplayRepository mStatusBarModePerDisplayRepository =
+            mKosmos.getStatusBarModePerDisplayRepository();
     private ScrimController mScrimController;
 
     @Before
@@ -537,6 +541,7 @@
                 mAutoHideController,
                 new StatusBarInitializerImpl(
                         mStatusBarWindowController,
+                        mStatusBarModePerDisplayRepository,
                         mCollapsedStatusBarFragmentProvider,
                         mock(StatusBarRootFactory.class),
                         mock(HomeStatusBarComponent.Factory.class),
@@ -602,6 +607,7 @@
                 mShadeController,
                 mWindowRootViewVisibilityInteractor,
                 mStatusBarKeyguardViewManager,
+                () -> mStatusBarLongPressGestureDetector,
                 mViewMediatorCallback,
                 mInitController,
                 new Handler(TestableLooper.get(this).getLooper()),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 638f195..69efa87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -40,14 +40,13 @@
 import com.android.systemui.plugins.fakeDarkIconDispatcher
 import com.android.systemui.res.R
 import com.android.systemui.scene.ui.view.WindowRootView
-import com.android.systemui.shade.LongPressGestureDetector
 import com.android.systemui.shade.ShadeControllerImpl
 import com.android.systemui.shade.ShadeLogger
 import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shade.StatusBarLongPressGestureDetector
 import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.data.repository.fakeStatusBarContentInsetsProviderStore
-import com.android.systemui.statusbar.data.repository.statusBarContentInsetsProviderStore
 import com.android.systemui.statusbar.policy.Clock
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.window.StatusBarWindowStateController
@@ -98,7 +97,7 @@
     @Mock private lateinit var windowRootView: Provider<WindowRootView>
     @Mock private lateinit var shadeLogger: ShadeLogger
     @Mock private lateinit var viewUtil: ViewUtil
-    @Mock private lateinit var longPressGestureDetector: LongPressGestureDetector
+    @Mock private lateinit var mStatusBarLongPressGestureDetector: StatusBarLongPressGestureDetector
     private lateinit var statusBarWindowStateController: StatusBarWindowStateController
 
     private lateinit var view: PhoneStatusBarView
@@ -395,7 +394,7 @@
                 shadeControllerImpl,
                 shadeViewController,
                 panelExpansionInteractor,
-                { longPressGestureDetector },
+                { mStatusBarLongPressGestureDetector },
                 windowRootView,
                 shadeLogger,
                 viewUtil,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
index 4d293b9..6326e73 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
@@ -102,6 +102,7 @@
                     logBuffer = FakeLogBuffer.Factory.create(),
                     verboseLogBuffer = FakeLogBuffer.Factory.create(),
                     systemClock,
+                    context.resources,
                 )
 
             val connectionState by collectLastValue(underTest.connectionState)
@@ -267,11 +268,7 @@
     fun satelliteProvisioned_notSupported_defaultFalse() =
         testScope.runTest {
             // GIVEN satellite is not supported
-            setUpRepo(
-                uptime = MIN_UPTIME,
-                satMan = satelliteManager,
-                satelliteSupported = false,
-            )
+            setUpRepo(uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = false)
 
             assertThat(underTest.isSatelliteProvisioned.value).isFalse()
         }
@@ -280,11 +277,7 @@
     fun satelliteProvisioned_supported_defaultFalse() =
         testScope.runTest {
             // GIVEN satellite is supported
-            setUpRepo(
-                uptime = MIN_UPTIME,
-                satMan = satelliteManager,
-                satelliteSupported = true,
-            )
+            setUpRepo(uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = true)
 
             // THEN default provisioned state is false
             assertThat(underTest.isSatelliteProvisioned.value).isFalse()
@@ -323,6 +316,7 @@
                     logBuffer = FakeLogBuffer.Factory.create(),
                     verboseLogBuffer = FakeLogBuffer.Factory.create(),
                     systemClock,
+                    context.resources,
                 )
 
             // WHEN we try to check for provisioned status
@@ -361,6 +355,7 @@
                     logBuffer = FakeLogBuffer.Factory.create(),
                     verboseLogBuffer = FakeLogBuffer.Factory.create(),
                     systemClock,
+                    context.resources,
                 )
 
             // WHEN we try to check for provisioned status
@@ -445,11 +440,7 @@
     fun satelliteProvisioned_supported_tracksCallback_reRegistersOnCrash() =
         testScope.runTest {
             // GIVEN satellite is supported
-            setUpRepo(
-                uptime = MIN_UPTIME,
-                satMan = satelliteManager,
-                satelliteSupported = true,
-            )
+            setUpRepo(uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = true)
 
             val provisioned by collectLastValue(underTest.isSatelliteProvisioned)
 
@@ -487,11 +478,7 @@
     fun satelliteNotSupported_listenersAreNotRegistered() =
         testScope.runTest {
             // GIVEN satellite is not supported
-            setUpRepo(
-                uptime = MIN_UPTIME,
-                satMan = satelliteManager,
-                satelliteSupported = false,
-            )
+            setUpRepo(uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = false)
 
             // WHEN data is requested from the repo
             val connectionState by collectLastValue(underTest.connectionState)
@@ -517,11 +504,7 @@
     fun satelliteNotSupported_registersCallbackForStateChanges() =
         testScope.runTest {
             // GIVEN satellite is not supported
-            setUpRepo(
-                uptime = MIN_UPTIME,
-                satMan = satelliteManager,
-                satelliteSupported = false,
-            )
+            setUpRepo(uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = false)
 
             runCurrent()
             // THEN the repo registers for state changes of satellite support
@@ -577,11 +560,7 @@
     fun satelliteNotSupported_supportShowsUp_registersListeners() =
         testScope.runTest {
             // GIVEN satellite is not supported
-            setUpRepo(
-                uptime = MIN_UPTIME,
-                satMan = satelliteManager,
-                satelliteSupported = false,
-            )
+            setUpRepo(uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = false)
             runCurrent()
 
             val callback =
@@ -610,11 +589,7 @@
     fun repoDoesNotCheckForSupportUntilMinUptime() =
         testScope.runTest {
             // GIVEN we init 100ms after sysui starts up
-            setUpRepo(
-                uptime = 100,
-                satMan = satelliteManager,
-                satelliteSupported = true,
-            )
+            setUpRepo(uptime = 100, satMan = satelliteManager, satelliteSupported = true)
 
             // WHEN data is requested
             val connectionState by collectLastValue(underTest.connectionState)
@@ -726,6 +701,7 @@
                 logBuffer = FakeLogBuffer.Factory.create(),
                 verboseLogBuffer = FakeLogBuffer.Factory.create(),
                 systemClock,
+                context.resources,
             )
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/OnTeardownRule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/OnTeardownRule.kt
new file mode 100644
index 0000000..778614b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/OnTeardownRule.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2024 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
+
+import org.junit.rules.TestWatcher
+import org.junit.runner.Description
+import org.junit.runners.model.MultipleFailureException
+
+/**
+ * Rule that allows teardown steps to be added right next to the places where it becomes clear they
+ * are needed. This can avoid the need for complicated or conditional logic in a single teardown
+ * method. Examples:
+ * ```
+ * @get:Rule teardownRule = OnTeardownRule()
+ *
+ * // setup and teardown right next to each other
+ * @Before
+ * fun setUp() {
+ *   val oldTimeout = getGlobalTimeout()
+ *   teardownRule.onTeardown { setGlobalTimeout(oldTimeout) }
+ *   overrideGlobalTimeout(5000)
+ * }
+ *
+ * // add teardown logic for fixtures that aren't used in every test
+ * fun addCustomer() {
+ *   val id = globalDatabase.addCustomer(TEST_NAME, TEST_ADDRESS, ...)
+ *   teardownRule.onTeardown { globalDatabase.deleteCustomer(id) }
+ * }
+ * ```
+ */
+class OnTeardownRule : TestWatcher() {
+    private var canAdd = true
+    private val teardowns = mutableListOf<() -> Unit>()
+
+    fun onTeardown(teardownRunnable: () -> Unit) {
+        if (!canAdd) {
+            throw IllegalStateException("Cannot add new teardown routines after test complete.")
+        }
+        teardowns.add(teardownRunnable)
+    }
+
+    fun onTeardown(teardownRunnable: Runnable) {
+        if (!canAdd) {
+            throw IllegalStateException("Cannot add new teardown routines after test complete.")
+        }
+        teardowns.add { teardownRunnable.run() }
+    }
+
+    override fun finished(description: Description?) {
+        canAdd = false
+        val errors = mutableListOf<Throwable>()
+        teardowns.reversed().forEach {
+            try {
+                it()
+            } catch (e: Throwable) {
+                errors.add(e)
+            }
+        }
+        MultipleFailureException.assertEmpty(errors)
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index 27a2cab..153a8be 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -56,6 +56,8 @@
 
 import java.io.FileInputStream;
 import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Future;
@@ -69,6 +71,17 @@
 // background on Ravenwood is available at go/ravenwood-docs
 @DisabledOnRavenwood
 public abstract class SysuiTestCase {
+    /**
+     * Especially when self-testing test utilities, we may have classes that look like test
+     * classes, but we don't expect to ever actually run as a top-level test.
+     * For example, {@link com.android.systemui.TryToDoABadThing}.
+     * Verifying properties on these as a part of structural tests like
+     * AAAPlusPlusVerifySysuiRequiredTestPropertiesTest is a waste of our time, and makes things
+     * look more confusing, so this lets us skip when appropriate.
+     */
+    @Retention(RetentionPolicy.RUNTIME)
+    public @interface SkipSysuiVerification {
+    }
 
     private static final String TAG = "SysuiTestCase";
 
@@ -172,6 +185,15 @@
     public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
             new DexmakerShareClassLoaderRule();
 
+    @Rule public final OnTeardownRule mTearDownRule = new OnTeardownRule();
+
+    /**
+     * Schedule a cleanup routine to happen when the test state is torn down.
+     */
+    protected void onTeardown(Runnable tearDownRunnable) {
+        mTearDownRule.onTeardown(tearDownRunnable);
+    }
+
     // set the highest order so it's the innermost rule
     @Rule(order = Integer.MAX_VALUE)
     public TestWithLooperRule mlooperRule = new TestWithLooperRule();
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorKosmos.kt
index 7e0e5f3..f876003 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorKosmos.kt
@@ -20,4 +20,4 @@
 import com.android.systemui.kosmos.Kosmos
 
 var Kosmos.configurationInteractor: ConfigurationInteractor by
-    Kosmos.Fixture { ConfigurationInteractor(configurationRepository) }
+    Kosmos.Fixture { ConfigurationInteractorImpl(configurationRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
index b27dadc..3b175853de7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
@@ -42,15 +42,6 @@
             val id = nextWidgetId++
             val providerInfo = createAppWidgetProviderInfo(provider, user.identifier)
 
-            fakeDatabase[id] =
-                CommunalWidgetContentModel.Available(
-                    appWidgetId = id,
-                    rank = rank ?: 0,
-                    providerInfo = providerInfo,
-                    spanY = 3,
-                )
-            updateListFromDatabase()
-
             val configured = configurator?.configureWidget(id) != false
             if (configured) {
                 onConfigured(id, providerInfo, rank ?: -1)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
index 8c4ec4c..4a6e273 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
@@ -19,7 +19,7 @@
 
 import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
-import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -77,7 +77,7 @@
                 repository = repository,
                 powerInteractor = powerInteractor,
                 bouncerRepository = bouncerRepository,
-                configurationInteractor = ConfigurationInteractor(configurationRepository),
+                configurationInteractor = ConfigurationInteractorImpl(configurationRepository),
                 shadeRepository = shadeRepository,
                 keyguardTransitionInteractor = keyguardTransitionInteractor,
                 sceneInteractorProvider = { sceneInteractor },
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
index ddae581..72cb1df 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
@@ -1,8 +1,10 @@
 package com.android.systemui.kosmos
 
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.Kosmos.Fixture
 import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
@@ -45,3 +47,5 @@
     testScope.runTest { this@runTest.testBody() }
 
 fun Kosmos.runCurrent() = testScope.runCurrent()
+
+fun <T> Kosmos.collectLastValue(flow: Flow<T>) = testScope.collectLastValue(flow)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index 5eaa198..63e6eb6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -51,6 +51,8 @@
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.pulseExpansionInteractor
+import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.lockscreenToGlanceableHubTransitionViewModel
 import com.android.systemui.model.sceneContainerPlugin
 import com.android.systemui.plugins.statusbar.statusBarStateController
 import com.android.systemui.power.data.repository.fakePowerRepository
@@ -68,6 +70,8 @@
 import com.android.systemui.shade.shadeController
 import com.android.systemui.shade.ui.viewmodel.notificationShadeWindowModel
 import com.android.systemui.statusbar.chips.ui.viewmodel.ongoingActivityChipsViewModel
+import com.android.systemui.statusbar.data.repository.fakeStatusBarModePerDisplayRepository
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
@@ -106,6 +110,7 @@
     val bouncerRepository by lazy { kosmos.bouncerRepository }
     val communalRepository by lazy { kosmos.fakeCommunalSceneRepository }
     val communalTransitionViewModel by lazy { kosmos.communalTransitionViewModel }
+    val activeNotificationsInteractor by lazy { kosmos.activeNotificationsInteractor }
     val headsUpNotificationInteractor by lazy { kosmos.headsUpNotificationInteractor }
     val seenNotificationsInteractor by lazy { kosmos.seenNotificationsInteractor }
     val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
@@ -119,6 +124,7 @@
     val mobileConnectionsRepository by lazy { kosmos.fakeMobileConnectionsRepository }
     val simBouncerInteractor by lazy { kosmos.simBouncerInteractor }
     val statusBarStateController by lazy { kosmos.statusBarStateController }
+    val statusBarModePerDisplayRepository by lazy { kosmos.fakeStatusBarModePerDisplayRepository }
     val interactionJankMonitor by lazy { kosmos.interactionJankMonitor }
     val fakeSceneContainerConfig by lazy { kosmos.sceneContainerConfig }
     val sceneInteractor by lazy { kosmos.sceneInteractor }
@@ -164,4 +170,11 @@
     val msdlPlayer by lazy { kosmos.fakeMSDLPlayer }
     val shadeModeInteractor by lazy { kosmos.shadeModeInteractor }
     val bouncerHapticHelper by lazy { kosmos.bouncerHapticPlayer }
+
+    val glanceableHubToLockscreenTransitionViewModel by lazy {
+        kosmos.glanceableHubToLockscreenTransitionViewModel
+    }
+    val lockscreenToGlanceableHubTransitionViewModel by lazy {
+        kosmos.lockscreenToGlanceableHubTransitionViewModel
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryKosmos.kt
similarity index 78%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryKosmos.kt
index 94b2bdf..7e7eea2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryKosmos.kt
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util.settings
+package com.android.systemui.media.controls.domain.pipeline
 
 import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
 
-val Kosmos.userAwareSecureSettingsRepository by
-    Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+var Kosmos.media3ActionFactory: Media3ActionFactory by Kosmos.Fixture { mock {} }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderKosmos.kt
index cb7750f5..af6a0c5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderKosmos.kt
@@ -34,6 +34,7 @@
             fakeMediaControllerFactory,
             mediaFlags,
             imageLoader,
-            statusBarManager
+            statusBarManager,
+            media3ActionFactory,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.kt
index 7f8348e..b833750 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.kt
@@ -18,21 +18,32 @@
 
 import android.content.Context
 import android.media.session.MediaController
-import android.media.session.MediaSession
 import android.media.session.MediaSession.Token
+import android.os.Looper
+import androidx.media3.session.MediaController as Media3Controller
+import androidx.media3.session.SessionToken
 
 class FakeMediaControllerFactory(context: Context) : MediaControllerFactory(context) {
 
     private val mediaControllersForToken = mutableMapOf<Token, MediaController>()
+    private var media3Controller: Media3Controller? = null
 
-    override fun create(token: MediaSession.Token): android.media.session.MediaController {
+    override fun create(token: Token): MediaController {
         if (token !in mediaControllersForToken) {
             super.create(token)
         }
         return mediaControllersForToken[token]!!
     }
 
+    override suspend fun create(token: SessionToken, looper: Looper): Media3Controller {
+        return media3Controller ?: super.create(token, looper)
+    }
+
     fun setControllerForToken(token: Token, mediaController: MediaController) {
         mediaControllersForToken[token] = mediaController
     }
+
+    fun setMedia3Controller(mediaController: Media3Controller) {
+        media3Controller = mediaController
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeSessionTokenFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeSessionTokenFactory.kt
new file mode 100644
index 0000000..94e0bca
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeSessionTokenFactory.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.util
+
+import android.content.Context
+import android.media.session.MediaSession.Token
+import androidx.media3.session.SessionToken
+
+class FakeSessionTokenFactory(context: Context) : SessionTokenFactory(context) {
+    private var sessionToken: SessionToken? = null
+
+    override suspend fun createTokenFromLegacy(token: Token): SessionToken {
+        return sessionToken ?: super.createTokenFromLegacy(token)
+    }
+
+    fun setMedia3SessionToken(token: SessionToken) {
+        sessionToken = token
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/SessionTokenFactoryKosmos.kt
similarity index 77%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/SessionTokenFactoryKosmos.kt
index 94b2bdf..8e473042 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/SessionTokenFactoryKosmos.kt
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util.settings
+package com.android.systemui.media.controls.util
 
+import android.content.applicationContext
 import com.android.systemui.kosmos.Kosmos
 
-val Kosmos.userAwareSecureSettingsRepository by
-    Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+val Kosmos.fakeSessionTokenFactory by Kosmos.Fixture { FakeSessionTokenFactory(applicationContext) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt
index f66125a..6787b8e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt
@@ -52,7 +52,7 @@
                     )
                 object : QSTileViewModel {
                     override val state: StateFlow<QSTileState?> =
-                        MutableStateFlow(QSTileState.build({ null }, tileSpec.spec) {})
+                        MutableStateFlow(QSTileState.build(null, tileSpec.spec) {})
                     override val config: QSTileConfig = config
                     override val isAvailable: StateFlow<Boolean> = MutableStateFlow(true)
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt
index 5b6fd8c..ab1c181 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt
@@ -44,7 +44,7 @@
             check("other").that(other).isNotNull()
             other ?: return
         }
-        check("icon").that(actual.icon()).isEqualTo(other.icon())
+        check("icon").that(actual.icon).isEqualTo(other.icon)
         check("iconRes").that(actual.iconRes).isEqualTo(other.iconRes)
         check("label").that(actual.label).isEqualTo(other.label)
         check("activationState").that(actual.activationState).isEqualTo(other.activationState)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
index 4228c3c..7e6a727 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
@@ -32,7 +32,6 @@
 import com.android.systemui.keyguard.dismissCallbackRegistry
 import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.windowManagerLockscreenVisibilityInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.kosmos.testScope
@@ -78,7 +77,6 @@
         uiEventLogger = uiEventLogger,
         sceneBackInteractor = sceneBackInteractor,
         shadeSessionStorage = shadeSessionStorage,
-        windowMgrLockscreenVisInteractor = windowManagerLockscreenVisibilityInteractor,
         keyguardEnabledInteractor = keyguardEnabledInteractor,
         dismissCallbackRegistry = dismissCallbackRegistry,
         statusBarStateController = sysuiStatusBarStateController,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerFactory.kt
index 8c218be..50a19a9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerFactory.kt
@@ -16,11 +16,13 @@
 
 package com.android.systemui.statusbar.core
 
+import com.android.systemui.statusbar.data.repository.StatusBarModePerDisplayRepository
 import com.android.systemui.statusbar.window.StatusBarWindowController
 
 class FakeStatusBarInitializerFactory() : StatusBarInitializer.Factory {
 
     override fun create(
-        statusBarWindowController: StatusBarWindowController
+        statusBarWindowController: StatusBarWindowController,
+        statusBarModePerDisplayRepository: StatusBarModePerDisplayRepository,
     ): StatusBarInitializer = FakeStatusBarInitializer()
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt
index 303529b..6e99027 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt
@@ -19,6 +19,7 @@
 import com.android.systemui.display.data.repository.displayRepository
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository
 import com.android.systemui.statusbar.window.fakeStatusBarWindowControllerStore
 
 val Kosmos.fakeStatusBarInitializer by Kosmos.Fixture { FakeStatusBarInitializer() }
@@ -37,6 +38,7 @@
             displayRepository,
             fakeStatusBarInitializerFactory,
             fakeStatusBarWindowControllerStore,
+            fakeStatusBarModeRepository,
         )
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
index 285cebb..8712b02 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
@@ -20,8 +20,10 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.data.model.StatusBarAppearance
 import com.android.systemui.statusbar.data.model.StatusBarMode
+import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent
 import dagger.Binds
 import dagger.Module
+import java.io.PrintWriter
 import javax.inject.Inject
 import kotlinx.coroutines.flow.MutableStateFlow
 
@@ -53,6 +55,14 @@
     override fun clearTransient() {
         isTransientShown.value = false
     }
+
+    override fun start() {}
+
+    override fun stop() {}
+
+    override fun onStatusBarViewInitialized(component: HomeStatusBarComponent) {}
+
+    override fun dump(pw: PrintWriter, args: Array<out String>) {}
 }
 
 @Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreKosmos.kt
new file mode 100644
index 0000000..5f33732
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 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.data.repository
+
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.display.data.repository.displayScopeRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import org.mockito.kotlin.mock
+
+val Kosmos.lightBarControllerStoreImpl by
+    Kosmos.Fixture {
+        LightBarControllerStoreImpl(
+            backgroundApplicationScope = applicationCoroutineScope,
+            displayRepository = displayRepository,
+            factory = { _, _, _ -> mock() },
+            displayScopeRepository = displayScopeRepository,
+            statusBarModeRepositoryStore = statusBarModeRepository,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryKosmos.kt
index 12db2f741..a585602 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryKosmos.kt
@@ -16,7 +16,10 @@
 
 package com.android.systemui.statusbar.data.repository
 
+import com.android.systemui.display.data.repository.displayRepository
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import org.mockito.kotlin.mock
 
 val Kosmos.fakeStatusBarModePerDisplayRepository by
     Kosmos.Fixture { FakeStatusBarModePerDisplayRepository() }
@@ -24,3 +27,21 @@
 val Kosmos.statusBarModeRepository: StatusBarModeRepositoryStore by
     Kosmos.Fixture { fakeStatusBarModeRepository }
 val Kosmos.fakeStatusBarModeRepository by Kosmos.Fixture { FakeStatusBarModeRepository() }
+val Kosmos.fakeStatusBarModePerDisplayRepositoryFactory by
+    Kosmos.Fixture { FakeStatusBarModePerDisplayRepositoryFactory() }
+
+val Kosmos.multiDisplayStatusBarModeRepositoryStore by
+    Kosmos.Fixture {
+        MultiDisplayStatusBarModeRepositoryStore(
+            applicationCoroutineScope,
+            fakeStatusBarModePerDisplayRepositoryFactory,
+            displayRepository,
+        )
+    }
+
+class FakeStatusBarModePerDisplayRepositoryFactory : StatusBarModePerDisplayRepositoryFactory {
+
+    override fun create(displayId: Int): StatusBarModePerDisplayRepositoryImpl {
+        return mock<StatusBarModePerDisplayRepositoryImpl>()
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
index 76bdc0d..32c582f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
@@ -28,6 +28,7 @@
     key: String,
     groupKey: String? = null,
     whenTime: Long = 0L,
+    isPromoted: Boolean = false,
     isAmbient: Boolean = false,
     isRowDismissed: Boolean = false,
     isSilent: Boolean = false,
@@ -50,6 +51,7 @@
         key = key,
         groupKey = groupKey,
         whenTime = whenTime,
+        isPromoted = isPromoted,
         isAmbient = isAmbient,
         isRowDismissed = isRowDismissed,
         isSilent = isSilent,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt
index f7acae9..067193f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt
@@ -19,8 +19,13 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.statusbar.notification.collection.provider.sectionStyleProvider
 import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
+import com.android.systemui.statusbar.notification.promoted.promotedNotificationsProvider
 
 val Kosmos.renderNotificationListInteractor by
     Kosmos.Fixture {
-        RenderNotificationListInteractor(activeNotificationListRepository, sectionStyleProvider)
+        RenderNotificationListInteractor(
+            activeNotificationListRepository,
+            sectionStyleProvider,
+            promotedNotificationsProvider,
+        )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProviderKosmos.kt
similarity index 80%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProviderKosmos.kt
index 94b2bdf..a7aa0b4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProviderKosmos.kt
@@ -14,9 +14,8 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util.settings
+package com.android.systemui.statusbar.notification.promoted
 
 import com.android.systemui.kosmos.Kosmos
 
-val Kosmos.userAwareSecureSettingsRepository by
-    Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+val Kosmos.promotedNotificationsProvider by Kosmos.Fixture { PromotedNotificationsProviderImpl() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/viewmodel/StatusBarUserChipViewModelKosmos.kt
similarity index 65%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/viewmodel/StatusBarUserChipViewModelKosmos.kt
index 94b2bdf..01175a5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/viewmodel/StatusBarUserChipViewModelKosmos.kt
@@ -14,9 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util.settings
+package com.android.systemui.statusbar.ui.viewmodel
 
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.user.domain.interactor.userSwitcherInteractor
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel
 
-val Kosmos.userAwareSecureSettingsRepository by
-    Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+val Kosmos.statusBarUserChipViewModel: StatusBarUserChipViewModel by
+    Kosmos.Fixture { StatusBarUserChipViewModel(userSwitcherInteractor) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/ExecutionKosmos.kt
similarity index 73%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/ExecutionKosmos.kt
index 94b2bdf..bf66cb6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/ExecutionKosmos.kt
@@ -14,9 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util.settings
+package com.android.systemui.util.concurrency
 
 import com.android.systemui.kosmos.Kosmos
 
-val Kosmos.userAwareSecureSettingsRepository by
-    Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+val Kosmos.fakeExecution: FakeExecution by
+    Kosmos.Fixture { FakeExecution().apply { simulateMainThread = false } }
+var Kosmos.execution: Execution by Kosmos.Fixture { fakeExecution }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeUserAwareSecureSettingsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeUserAwareSecureSettingsRepository.kt
deleted file mode 100644
index 5054e29..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeUserAwareSecureSettingsRepository.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2024 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.util.settings
-
-import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.map
-
-class FakeUserAwareSecureSettingsRepository : UserAwareSecureSettingsRepository {
-
-    private val settings = MutableStateFlow<Map<String, Boolean>>(mutableMapOf())
-
-    override fun boolSettingForActiveUser(name: String, defaultValue: Boolean): Flow<Boolean> {
-        return settings.map { it.getOrDefault(name, defaultValue) }
-    }
-
-    fun setBoolSettingForActiveUser(name: String, value: Boolean) {
-        settings.value = settings.value.toMutableMap().apply { this[name] = value }
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/data/repository/UserAwareSecureSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/data/repository/UserAwareSecureSettingsRepositoryKosmos.kt
new file mode 100644
index 0000000..dc10ca9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/data/repository/UserAwareSecureSettingsRepositoryKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 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.util.settings.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.backgroundCoroutineContext
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.user.data.repository.userRepository
+import com.android.systemui.util.settings.fakeSettings
+import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository
+
+val Kosmos.userAwareSecureSettingsRepository by
+    Kosmos.Fixture {
+        UserAwareSecureSettingsRepository(
+            fakeSettings,
+            userRepository,
+            testDispatcher,
+            backgroundCoroutineContext,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/data/repository/UserAwareSystemSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/data/repository/UserAwareSystemSettingsRepositoryKosmos.kt
new file mode 100644
index 0000000..ff77908
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/data/repository/UserAwareSystemSettingsRepositoryKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 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.util.settings.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.backgroundCoroutineContext
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.user.data.repository.userRepository
+import com.android.systemui.util.settings.fakeSettings
+import com.android.systemui.util.settings.repository.UserAwareSystemSettingsRepository
+
+val Kosmos.userAwareSystemSettingsRepository by
+    Kosmos.Fixture {
+        UserAwareSystemSettingsRepository(
+            fakeSettings,
+            userRepository,
+            testDispatcher,
+            backgroundCoroutineContext,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
index 1b58582..ed5322e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.media.mediaOutputDialogManager
+import com.android.systemui.util.concurrency.execution
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
@@ -53,6 +54,7 @@
             testScope.testScheduler,
             mediaControllerRepository,
             mediaControllerInteractor,
+            execution,
         )
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractorKosmos.kt
index 652b3ea..fdeb8ce 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractorKosmos.kt
@@ -19,6 +19,7 @@
 import android.os.Handler
 import android.os.looper
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
 
 var Kosmos.mediaControllerInteractor: MediaControllerInteractor by
-    Kosmos.Fixture { MediaControllerInteractorImpl(Handler(looper)) }
+    Kosmos.Fixture { MediaControllerInteractorImpl(Handler(looper), testScope.testScheduler) }
diff --git a/packages/overlays/Android.bp b/packages/overlays/Android.bp
index 5075f63..44abb9f 100644
--- a/packages/overlays/Android.bp
+++ b/packages/overlays/Android.bp
@@ -21,6 +21,7 @@
 
 phony {
     name: "frameworks-base-overlays",
+    product_specific: true,
     required: [
         "DisplayCutoutEmulationCornerOverlay",
         "DisplayCutoutEmulationDoubleOverlay",
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index b3f78ab..4731cfb 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -279,6 +279,15 @@
     shared_libs: [
         "liblog",
     ],
+    visibility: ["//visibility:private"],
+}
+
+cc_library_host_shared {
+    name: "libravenwood_initializer",
+    defaults: ["ravenwood_jni_defaults"],
+    srcs: [
+        "runtime-jni/ravenwood_initializer.cpp",
+    ],
 }
 
 // We need this as a separate library because we need to overload the
@@ -301,7 +310,6 @@
         "libutils",
         "libcutils",
     ],
-    visibility: ["//frameworks/base"],
 }
 
 // For collecting the *stats.csv files in a known directory under out/host/linux-x86/testcases/.
@@ -659,6 +667,7 @@
     ],
     jni_libs: [
         // Libraries has to be loaded in the following order
+        "libravenwood_initializer",
         "libravenwood_sysprop",
         "libravenwood_runtime",
         "libandroid_runtime",
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
index 66a6890..869d854 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
@@ -26,12 +26,10 @@
 import android.os.Bundle;
 import android.platform.test.annotations.RavenwoodTestRunnerInitializing;
 import android.platform.test.annotations.internal.InnerRunner;
-import android.platform.test.ravenwood.RavenwoodTestStats.Result;
 import android.util.Log;
 
 import androidx.test.platform.app.InstrumentationRegistry;
 
-import org.junit.AssumptionViolatedException;
 import org.junit.rules.TestRule;
 import org.junit.runner.Description;
 import org.junit.runner.Runner;
@@ -171,10 +169,11 @@
         final var notifier = new RavenwoodRunNotifier(realNotifier);
         final var description = getDescription();
 
+        RavenwoodTestStats.getInstance().attachToRunNotifier(notifier);
+
         if (mRealRunner instanceof ClassSkippingTestRunner) {
-            mRealRunner.run(notifier);
             Log.i(TAG, "onClassSkipped: description=" + description);
-            RavenwoodTestStats.getInstance().onClassSkipped(description);
+            mRealRunner.run(notifier);
             return;
         }
 
@@ -205,7 +204,6 @@
 
             if (!skipRunnerHook) {
                 try {
-                    RavenwoodTestStats.getInstance().onClassFinished(description);
                     mState.exitTestClass();
                 } catch (Throwable th) {
                     notifier.reportAfterTestFailure(th);
@@ -295,8 +293,6 @@
         // method-level annotations here.
         if (scope == Scope.Instance && order == Order.Outer) {
             if (!RavenwoodEnablementChecker.shouldEnableOnRavenwood(description, true)) {
-                RavenwoodTestStats.getInstance().onTestFinished(
-                        classDescription, description, Result.Skipped);
                 return false;
             }
         }
@@ -317,16 +313,6 @@
             // End of a test method.
             mState.exitTestMethod();
 
-            final Result result;
-            if (th == null) {
-                result = Result.Passed;
-            } else if (th instanceof AssumptionViolatedException) {
-                result = Result.Skipped;
-            } else {
-                result = Result.Failed;
-            }
-
-            RavenwoodTestStats.getInstance().onTestFinished(classDescription, description, result);
         }
 
         // If RUN_DISABLED_TESTS is set, and the method did _not_ throw, make it an error.
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodNativeLoader.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodNativeLoader.java
index d29b93c..a208d6d 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodNativeLoader.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodNativeLoader.java
@@ -40,7 +40,7 @@
      * See frameworks/base/core/jni/platform/host/HostRuntime.cpp
      */
     private static final Class<?>[] sLibandroidClasses = {
-            android.util.Log.class,
+//            android.util.Log.class, // Not using native log: b/377377826
             android.os.Parcel.class,
             android.os.Binder.class,
             android.os.SystemProperties.class,
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
index 0f16352..9177857 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
@@ -40,6 +40,7 @@
 import android.os.Bundle;
 import android.os.HandlerThread;
 import android.os.Looper;
+import android.os.Process_ravenwood;
 import android.os.ServiceManager;
 import android.os.SystemProperties;
 import android.provider.DeviceConfig_host;
@@ -50,8 +51,10 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.hoststubgen.hosthelper.HostTestUtils;
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.RuntimeInit;
 import com.android.ravenwood.RavenwoodRuntimeNative;
+import com.android.ravenwood.RavenwoodRuntimeState;
 import com.android.ravenwood.common.RavenwoodCommonUtils;
 import com.android.ravenwood.common.RavenwoodRuntimeException;
 import com.android.ravenwood.common.SneakyThrow;
@@ -84,6 +87,7 @@
     }
 
     private static final String MAIN_THREAD_NAME = "RavenwoodMain";
+    private static final String LIBRAVENWOOD_INITIALIZER_NAME = "ravenwood_initializer";
     private static final String RAVENWOOD_NATIVE_SYSPROP_NAME = "ravenwood_sysprop";
     private static final String RAVENWOOD_NATIVE_RUNTIME_NAME = "ravenwood_runtime";
     private static final String RAVENWOOD_BUILD_PROP =
@@ -137,23 +141,61 @@
         return res;
     }
 
+    private static final Object sInitializationLock = new Object();
+
+    @GuardedBy("sInitializationLock")
+    private static boolean sInitialized = false;
+
+    @GuardedBy("sInitializationLock")
+    private static Throwable sExceptionFromGlobalInit;
+
     private static RavenwoodAwareTestRunner sRunner;
     private static RavenwoodSystemProperties sProps;
-    private static boolean sInitialized = false;
 
     /**
      * Initialize the global environment.
      */
     public static void globalInitOnce() {
-        if (sInitialized) {
-            return;
+        synchronized (sInitializationLock) {
+            if (!sInitialized) {
+                // globalInitOnce() is called from class initializer, which cause
+                // this method to be called recursively,
+                sInitialized = true;
+
+                // This is the first call.
+                try {
+                    globalInitInner();
+                } catch (Throwable th) {
+                    Log.e(TAG, "globalInit() failed", th);
+
+                    sExceptionFromGlobalInit = th;
+                    throw th;
+                }
+            } else {
+                // Subsequent calls. If the first call threw, just throw the same error, to prevent
+                // the test from running.
+                if (sExceptionFromGlobalInit != null) {
+                    Log.e(TAG, "globalInit() failed re-throwing the same exception",
+                            sExceptionFromGlobalInit);
+
+                    SneakyThrow.sneakyThrow(sExceptionFromGlobalInit);
+                }
+            }
         }
-        sInitialized = true;
+    }
+
+    private static void globalInitInner() {
+        if (RAVENWOOD_VERBOSE_LOGGING) {
+            Log.v(TAG, "globalInit() called here...", new RuntimeException("NOT A CRASH"));
+        }
+
+        // Some process-wide initialization. (maybe redirect stdout/stderr)
+        RavenwoodCommonUtils.loadJniLibrary(LIBRAVENWOOD_INITIALIZER_NAME);
 
         // We haven't initialized liblog yet, so directly write to System.out here.
-        RavenwoodCommonUtils.log(TAG, "globalInit()");
+        RavenwoodCommonUtils.log(TAG, "globalInitInner()");
 
-        // Load libravenwood_sysprop first
+        // Load libravenwood_sysprop before other libraries that may use SystemProperties.
         var libProp = RavenwoodCommonUtils.getJniLibraryPath(RAVENWOOD_NATIVE_SYSPROP_NAME);
         System.load(libProp);
         RavenwoodRuntimeNative.reloadNativeLibrary(libProp);
@@ -199,7 +241,7 @@
      */
     public static void init(RavenwoodAwareTestRunner runner) {
         if (RAVENWOOD_VERBOSE_LOGGING) {
-            Log.i(TAG, "init() called here: " + runner, new RuntimeException("STACKTRACE"));
+            Log.v(TAG, "init() called here: " + runner, new RuntimeException("STACKTRACE"));
         }
         if (sRunner == runner) {
             return;
@@ -223,7 +265,9 @@
             Thread.setDefaultUncaughtExceptionHandler(sUncaughtExceptionHandler);
         }
 
-        android.os.Process.init$ravenwood(config.mUid, config.mPid);
+        RavenwoodRuntimeState.sUid = config.mUid;
+        RavenwoodRuntimeState.sPid = config.mPid;
+        RavenwoodRuntimeState.sTargetSdkLevel = config.mTargetSdkLevel;
         sOriginalIdentityToken = Binder.clearCallingIdentity();
         reinit();
         setSystemProperties(config.mSystemProperties);
@@ -310,7 +354,7 @@
      */
     public static void reset() {
         if (RAVENWOOD_VERBOSE_LOGGING) {
-            Log.i(TAG, "reset() called here", new RuntimeException("STACKTRACE"));
+            Log.v(TAG, "reset() called here", new RuntimeException("STACKTRACE"));
         }
         if (sRunner == null) {
             throw new RavenwoodRuntimeException("Internal error: reset() already called");
@@ -350,8 +394,8 @@
         if (sOriginalIdentityToken != -1) {
             Binder.restoreCallingIdentity(sOriginalIdentityToken);
         }
-        android.os.Process.reset$ravenwood();
-
+        RavenwoodRuntimeState.reset();
+        Process_ravenwood.reset();
         DeviceConfig_host.reset();
 
         try {
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java
index 016de8e..7870585 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java
@@ -18,6 +18,9 @@
 import android.util.Log;
 
 import org.junit.runner.Description;
+import org.junit.runner.notification.Failure;
+import org.junit.runner.notification.RunListener;
+import org.junit.runner.notification.RunNotifier;
 
 import java.io.File;
 import java.io.IOException;
@@ -27,7 +30,7 @@
 import java.nio.file.Paths;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
-import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.Map;
 
 /**
@@ -39,7 +42,7 @@
  */
 public class RavenwoodTestStats {
     private static final String TAG = "RavenwoodTestStats";
-    private static final String HEADER = "Module,Class,ClassDesc,Passed,Failed,Skipped";
+    private static final String HEADER = "Module,Class,OuterClass,Passed,Failed,Skipped";
 
     private static RavenwoodTestStats sInstance;
 
@@ -66,7 +69,7 @@
     private final PrintWriter mOutputWriter;
     private final String mTestModuleName;
 
-    public final Map<Description, Map<Description, Result>> mStats = new HashMap<>();
+    public final Map<String, Map<String, Result>> mStats = new LinkedHashMap<>();
 
     /** Ctor */
     public RavenwoodTestStats() {
@@ -115,75 +118,129 @@
         return cwd.getName();
     }
 
-    private void addResult(Description classDescription, Description methodDescription,
+    private void addResult(String className, String methodName,
             Result result) {
-        mStats.compute(classDescription, (classDesc, value) -> {
+        mStats.compute(className, (className_, value) -> {
             if (value == null) {
-                value = new HashMap<>();
+                value = new LinkedHashMap<>();
             }
-            value.put(methodDescription, result);
+            // If the result is already set, don't overwrite it.
+            if (!value.containsKey(methodName)) {
+                value.put(methodName, result);
+            }
             return value;
         });
     }
 
     /**
-     * Call it when a test class is skipped.
-     */
-    public void onClassSkipped(Description classDescription) {
-        addResult(classDescription, Description.EMPTY, Result.Skipped);
-        onClassFinished(classDescription);
-    }
-
-    /**
      * Call it when a test method is finished.
      */
-    public void onTestFinished(Description classDescription, Description testDescription,
-            Result result) {
-        addResult(classDescription, testDescription, result);
+    private void onTestFinished(String className, String testName, Result result) {
+        addResult(className, testName, result);
     }
 
     /**
-     * Call it when a test class is finished.
+     * Dump all the results and clear it.
      */
-    public void onClassFinished(Description classDescription) {
-        int passed = 0;
-        int skipped = 0;
-        int failed = 0;
-        var stats = mStats.get(classDescription);
-        if (stats == null) {
-            return;
-        }
-        for (var e : stats.values()) {
-            switch (e) {
-                case Passed: passed++; break;
-                case Skipped: skipped++; break;
-                case Failed: failed++; break;
+    private void dumpAllAndClear() {
+        for (var entry : mStats.entrySet()) {
+            int passed = 0;
+            int skipped = 0;
+            int failed = 0;
+            var className = entry.getKey();
+
+            for (var e : entry.getValue().values()) {
+                switch (e) {
+                    case Passed:
+                        passed++;
+                        break;
+                    case Skipped:
+                        skipped++;
+                        break;
+                    case Failed:
+                        failed++;
+                        break;
+                }
             }
+
+            mOutputWriter.printf("%s,%s,%s,%d,%d,%d\n",
+                    mTestModuleName, className, getOuterClassName(className),
+                    passed, failed, skipped);
         }
-
-        var testClass = extractTestClass(classDescription);
-
-        mOutputWriter.printf("%s,%s,%s,%d,%d,%d\n",
-                mTestModuleName, (testClass == null ? "?" : testClass.getCanonicalName()),
-                classDescription, passed, failed, skipped);
         mOutputWriter.flush();
+        mStats.clear();
     }
 
-    /**
-     * Try to extract the class from a description, which is needed because
-     * ParameterizedAndroidJunit4's description doesn't contain a class.
-     */
-    private Class<?> extractTestClass(Description desc) {
-        if (desc.getTestClass() != null) {
-            return desc.getTestClass();
+    private static String getOuterClassName(String className) {
+        // Just delete the '$', because I'm not sure if the className we get here is actaully a
+        // valid class name that does exist. (it might have a parameter name, etc?)
+        int p = className.indexOf('$');
+        if (p < 0) {
+            return className;
         }
-        // Look into the children.
-        for (var child : desc.getChildren()) {
-            var fromChild = extractTestClass(child);
-            if (fromChild != null) {
-                return fromChild;
-            }
-        }
-        return null;
+        return className.substring(0, p);
     }
+
+    public void attachToRunNotifier(RunNotifier notifier) {
+        notifier.addListener(mRunListener);
+    }
+
+    private final RunListener mRunListener = new RunListener() {
+        @Override
+        public void testSuiteStarted(Description description) {
+            Log.d(TAG, "testSuiteStarted: " + description);
+        }
+
+        @Override
+        public void testSuiteFinished(Description description) {
+            Log.d(TAG, "testSuiteFinished: " + description);
+        }
+
+        @Override
+        public void testRunStarted(Description description) {
+            Log.d(TAG, "testRunStarted: " + description);
+        }
+
+        @Override
+        public void testRunFinished(org.junit.runner.Result result) {
+            Log.d(TAG, "testRunFinished: " + result);
+
+            dumpAllAndClear();
+        }
+
+        @Override
+        public void testStarted(Description description) {
+            Log.d(TAG, "  testStarted: " + description);
+        }
+
+        @Override
+        public void testFinished(Description description) {
+            Log.d(TAG, "  testFinished: " + description);
+
+            // Send "Passed", but if there's already another result sent for this, this won't
+            // override it.
+            onTestFinished(description.getClassName(), description.getMethodName(), Result.Passed);
+        }
+
+        @Override
+        public void testFailure(Failure failure) {
+            Log.d(TAG, "    testFailure: " + failure);
+
+            var description = failure.getDescription();
+            onTestFinished(description.getClassName(), description.getMethodName(), Result.Failed);
+        }
+
+        @Override
+        public void testAssumptionFailure(Failure failure) {
+            Log.d(TAG, "    testAssumptionFailure: " + failure);
+            var description = failure.getDescription();
+            onTestFinished(description.getClassName(), description.getMethodName(), Result.Skipped);
+        }
+
+        @Override
+        public void testIgnored(Description description) {
+            Log.d(TAG, "    testIgnored: " + description);
+            onTestFinished(description.getClassName(), description.getMethodName(), Result.Skipped);
+        }
+    };
 }
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
index 37b0abc..d8f2b70 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
@@ -23,6 +23,7 @@
 import android.annotation.Nullable;
 import android.app.Instrumentation;
 import android.content.Context;
+import android.os.Build;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
@@ -67,7 +68,7 @@
     String mTargetPackageName;
 
     int mMinSdkLevel;
-    int mTargetSdkLevel;
+    int mTargetSdkLevel = Build.VERSION_CODES.CUR_DEVELOPMENT;
 
     boolean mProvideMainThread = false;
 
diff --git a/ravenwood/runtime-helper-src/framework/android/os/Process_ravenwood.java b/ravenwood/runtime-helper-src/framework/android/os/Process_ravenwood.java
new file mode 100644
index 0000000..3c6a4d7
--- /dev/null
+++ b/ravenwood/runtime-helper-src/framework/android/os/Process_ravenwood.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 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.os;
+
+import android.util.Pair;
+
+public class Process_ravenwood {
+
+    private static volatile ThreadLocal<Pair<Integer, Boolean>> sThreadPriority;
+
+    static {
+        reset();
+    }
+
+    public static void reset() {
+        // Reset the thread local variable
+        sThreadPriority = ThreadLocal.withInitial(
+                () -> Pair.create(Process.THREAD_PRIORITY_DEFAULT, true));
+    }
+
+    /**
+     * Called by {@link Process#setThreadPriority(int, int)}
+     */
+    public static void setThreadPriority(int tid, int priority) {
+        if (Process.myTid() == tid) {
+            boolean backgroundOk = sThreadPriority.get().second;
+            if (priority >= Process.THREAD_PRIORITY_BACKGROUND && !backgroundOk) {
+                throw new IllegalArgumentException(
+                        "Priority " + priority + " blocked by setCanSelfBackground()");
+            }
+            sThreadPriority.set(Pair.create(priority, backgroundOk));
+        } else {
+            throw new UnsupportedOperationException(
+                    "Cross-thread priority management not yet available in Ravenwood");
+        }
+    }
+
+    /**
+     * Called by {@link Process#setCanSelfBackground(boolean)}
+     */
+    public static void setCanSelfBackground(boolean backgroundOk) {
+        int priority = sThreadPriority.get().first;
+        sThreadPriority.set(Pair.create(priority, backgroundOk));
+    }
+
+    /**
+     * Called by {@link Process#getThreadPriority(int)}
+     */
+    public static int getThreadPriority(int tid) {
+        if (Process.myTid() == tid) {
+            return sThreadPriority.get().first;
+        } else {
+            throw new UnsupportedOperationException(
+                    "Cross-thread priority management not yet available in Ravenwood");
+        }
+    }
+}
diff --git a/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayContainer_host.java b/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayContainer_host.java
deleted file mode 100644
index c18c307..0000000
--- a/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayContainer_host.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2024 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.internal.os;
-
-import java.util.Arrays;
-import java.util.HashMap;
-
-public class LongArrayContainer_host {
-    private static final HashMap<Long, long[]> sInstances = new HashMap<>();
-    private static long sNextId = 1;
-
-    public static long native_init(int arrayLength) {
-        long[] array = new long[arrayLength];
-        long instanceId = sNextId++;
-        sInstances.put(instanceId, array);
-        return instanceId;
-    }
-
-    static long[] getInstance(long instanceId) {
-        return sInstances.get(instanceId);
-    }
-
-    public static void native_setValues(long instanceId, long[] values) {
-        System.arraycopy(values, 0, getInstance(instanceId), 0, values.length);
-    }
-
-    public static void native_getValues(long instanceId, long[] values) {
-        System.arraycopy(getInstance(instanceId), 0, values, 0, values.length);
-    }
-
-    public static boolean native_combineValues(long instanceId, long[] array, int[] indexMap) {
-        long[] values = getInstance(instanceId);
-
-        boolean nonZero = false;
-        Arrays.fill(array, 0);
-
-        for (int i = 0; i < values.length; i++) {
-            int index = indexMap[i];
-            if (index < 0 || index >= array.length) {
-                throw new IndexOutOfBoundsException("Index " + index + " is out of bounds: [0, "
-                        + (array.length - 1) + "]");
-            }
-            if (values[i] != 0) {
-                array[index] += values[i];
-                nonZero = true;
-            }
-        }
-        return nonZero;
-    }
-}
diff --git a/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayMultiStateCounter_host.java b/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayMultiStateCounter_host.java
index 9ce8ea8..90608f6 100644
--- a/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayMultiStateCounter_host.java
+++ b/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayMultiStateCounter_host.java
@@ -286,15 +286,12 @@
         return getInstance(instanceId).mArrayLength;
     }
 
-    public static void native_setValues(long instanceId, int state, long containerInstanceId) {
-        getInstance(instanceId).setValue(state,
-                LongArrayContainer_host.getInstance(containerInstanceId));
+    public static void native_setValues(long instanceId, int state, long[] values) {
+        getInstance(instanceId).setValue(state, values);
     }
 
-    public static void native_updateValues(long instanceId, long containerInstanceId,
-            long timestampMs) {
-        getInstance(instanceId).updateValue(
-                LongArrayContainer_host.getInstance(containerInstanceId), timestampMs);
+    public static void native_updateValues(long instanceId, long[] values, long timestampMs) {
+        getInstance(instanceId).updateValue(values, timestampMs);
     }
 
     public static void native_setState(long instanceId, int state, long timestampMs) {
@@ -305,19 +302,16 @@
         getInstance(targetInstanceId).copyStatesFrom(getInstance(sourceInstanceId));
     }
 
-    public static void native_incrementValues(long instanceId, long containerInstanceId,
-            long timestampMs) {
-        getInstance(instanceId).incrementValues(
-                LongArrayContainer_host.getInstance(containerInstanceId), timestampMs);
+    public static void native_incrementValues(long instanceId, long[] delta, long timestampMs) {
+        getInstance(instanceId).incrementValues(delta, timestampMs);
     }
 
-    public static void native_addCounts(long instanceId, long containerInstanceId) {
-        getInstance(instanceId).addCounts(LongArrayContainer_host.getInstance(containerInstanceId));
+    public static void native_addCounts(long instanceId, long[] counts) {
+        getInstance(instanceId).addCounts(counts);
     }
 
-    public static void native_getCounts(long instanceId, long containerInstanceId, int state) {
-        getInstance(instanceId).getValues(LongArrayContainer_host.getInstance(containerInstanceId),
-                state);
+    public static void native_getCounts(long instanceId, long[] counts, int state) {
+        getInstance(instanceId).getValues(counts, state);
     }
 
     public static void native_reset(long instanceId) {
diff --git a/ravenwood/runtime-helper-src/framework/com/android/internal/ravenwood/RavenwoodEnvironment_host.java b/ravenwood/runtime-helper-src/framework/com/android/internal/ravenwood/RavenwoodEnvironment_host.java
index e12ff24..b65668b 100644
--- a/ravenwood/runtime-helper-src/framework/com/android/internal/ravenwood/RavenwoodEnvironment_host.java
+++ b/ravenwood/runtime-helper-src/framework/com/android/internal/ravenwood/RavenwoodEnvironment_host.java
@@ -23,14 +23,6 @@
     }
 
     /**
-     * Called from {@link RavenwoodEnvironment#ensureRavenwoodInitialized()}.
-     */
-    public static void ensureRavenwoodInitialized() {
-        // Initialization is now done by RavenwoodAwareTestRunner.
-        // Should we remove it?
-    }
-
-    /**
      * Called from {@link RavenwoodEnvironment#getRavenwoodRuntimePath()}.
      */
     public static String getRavenwoodRuntimePath(RavenwoodEnvironment env) {
diff --git a/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java b/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
index c94ef31..0298171 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
@@ -16,6 +16,7 @@
 package android.system;
 
 import com.android.ravenwood.RavenwoodRuntimeNative;
+import com.android.ravenwood.RavenwoodRuntimeState;
 import com.android.ravenwood.common.JvmWorkaround;
 
 import java.io.FileDescriptor;
@@ -97,4 +98,16 @@
     public static void setenv(String name, String value, boolean overwrite) throws ErrnoException {
         RavenwoodRuntimeNative.setenv(name, value, overwrite);
     }
+
+    public static int getpid() {
+        return RavenwoodRuntimeState.sPid;
+    }
+
+    public static int getuid() {
+        return RavenwoodRuntimeState.sUid;
+    }
+
+    public static int gettid() {
+        return RavenwoodRuntimeNative.gettid();
+    }
 }
diff --git a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java
index f13189f..7b940b4 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java
@@ -58,6 +58,8 @@
 
     public static native void clearSystemProperties();
 
+    public static native int gettid();
+
     public static long lseek(FileDescriptor fd, long offset, int whence) throws ErrnoException {
         return nLseek(JvmWorkaround.getInstance().getFdInt(fd), offset, whence);
     }
diff --git a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeState.java b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeState.java
new file mode 100644
index 0000000..175e020
--- /dev/null
+++ b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeState.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 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.ravenwood;
+
+public class RavenwoodRuntimeState {
+    // This must match VMRuntime.SDK_VERSION_CUR_DEVELOPMENT.
+    public static final int CUR_DEVELOPMENT = 10000;
+
+    public static volatile int sUid;
+    public static volatile int sPid;
+    public static volatile int sTargetSdkLevel;
+
+    static {
+        reset();
+    }
+
+    public static void reset() {
+        sUid = -1;
+        sPid = -1;
+        sTargetSdkLevel = CUR_DEVELOPMENT;
+    }
+}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java b/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java
index ba89f71..eaadac6 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java
@@ -19,6 +19,7 @@
 // The original is here:
 // $ANDROID_BUILD_TOP/libcore/libart/src/main/java/dalvik/system/VMRuntime.java
 
+import com.android.ravenwood.RavenwoodRuntimeState;
 import com.android.ravenwood.common.JvmWorkaround;
 
 import java.lang.reflect.Array;
@@ -52,4 +53,8 @@
     public long addressOf(Object obj) {
         return JvmWorkaround.getInstance().addressOf(obj);
     }
+
+    public int getTargetSdkVersion() {
+        return RavenwoodRuntimeState.sTargetSdkLevel;
+    }
 }
diff --git a/ravenwood/runtime-jni/ravenwood_initializer.cpp b/ravenwood/runtime-jni/ravenwood_initializer.cpp
new file mode 100644
index 0000000..89fb7c3
--- /dev/null
+++ b/ravenwood/runtime-jni/ravenwood_initializer.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2024 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 file is compiled into a single SO file, which we load at the very first.
+  * We can do process-wide initialization here.
+  */
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "jni_helper.h"
+
+static void maybeRedirectLog() {
+    auto ravenwoodLogOut = getenv("RAVENWOOD_LOG_OUT");
+    if (ravenwoodLogOut == NULL) {
+        return;
+    }
+    ALOGI("RAVENWOOD_LOG_OUT set. Redirecting output to %s", ravenwoodLogOut);
+
+    // Redirect stdin / stdout to /dev/tty.
+    int ttyFd = open(ravenwoodLogOut, O_WRONLY | O_APPEND);
+    if (ttyFd == -1) {
+        ALOGW("$RAVENWOOD_LOG_OUT is set to %s, but failed to open: %s ", ravenwoodLogOut,
+                strerror(errno));
+        return;
+    }
+    dup2(ttyFd, 1);
+    dup2(ttyFd, 2);
+}
+
+extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
+    ALOGI("%s: JNI_OnLoad", __FILE__);
+
+    maybeRedirectLog();
+    return JNI_VERSION_1_4;
+}
diff --git a/ravenwood/runtime-jni/ravenwood_runtime.cpp b/ravenwood/runtime-jni/ravenwood_runtime.cpp
index 2a3c26e..c1993f6 100644
--- a/ravenwood/runtime-jni/ravenwood_runtime.cpp
+++ b/ravenwood/runtime-jni/ravenwood_runtime.cpp
@@ -17,6 +17,7 @@
 #include <fcntl.h>
 #include <string.h>
 #include <sys/stat.h>
+#include <sys/syscall.h>
 #include <unistd.h>
 #include <utils/misc.h>
 
@@ -173,22 +174,10 @@
     throwIfMinusOne(env, "setenv", setenv(name.c_str(), value.c_str(), overwrite ? 1 : 0));
 }
 
-static void maybeRedirectLog() {
-    auto ravenwoodLogOut = getenv("RAVENWOOD_LOG_OUT");
-    if (ravenwoodLogOut == NULL) {
-        return;
-    }
-    ALOGI("RAVENWOOD_LOG_OUT set. Redirecting output to %s", ravenwoodLogOut);
 
-    // Redirect stdin / stdout to /dev/tty.
-    int ttyFd = open(ravenwoodLogOut, O_WRONLY);
-    if (ttyFd == -1) {
-        ALOGW("$RAVENWOOD_LOG_OUT is set to %s, but failed to open: %s ", ravenwoodLogOut,
-                strerror(errno));
-        return;
-    }
-    dup2(ttyFd, 1);
-    dup2(ttyFd, 2);
+static jint Linux_gettid(JNIEnv* env, jobject) {
+    // gettid(2() was added in glibc 2.30 but Android uses an older version in prebuilt.
+    return syscall(__NR_gettid);
 }
 
 // ---- Registration ----
@@ -207,11 +196,10 @@
     { "stat", "(Ljava/lang/String;)Landroid/system/StructStat;", (void*)Linux_stat },
     { "nOpen", "(Ljava/lang/String;II)I", (void*)Linux_open },
     { "setenv", "(Ljava/lang/String;Ljava/lang/String;Z)V", (void*)Linux_setenv },
+    { "gettid", "()I", (void*)Linux_gettid },
 };
 
 extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
-    maybeRedirectLog();
-
     ALOGI("%s: JNI_OnLoad", __FILE__);
 
     JNIEnv* env = GetJNIEnvOrDie(vm);
diff --git a/ravenwood/scripts/run-ravenwood-tests.sh b/ravenwood/scripts/run-ravenwood-tests.sh
index 5d623e0..1910100 100755
--- a/ravenwood/scripts/run-ravenwood-tests.sh
+++ b/ravenwood/scripts/run-ravenwood-tests.sh
@@ -18,12 +18,38 @@
 # Options:
 #
 #   -s: "Smoke" test -- skip slow tests (SysUI, ICU)
+#
+#   -x PCRE: Specify exclusion filter in PCRE
+#            Example: -x '^(Cts|hoststub)' # Exclude CTS and hoststubgen tests.
+#
+#   -f PCRE: Specify inclusion filter in PCRE
+
+
+# Regex to identify slow tests, in PCRE
+SLOW_TEST_RE='^(SystemUiRavenTests|CtsIcuTestCasesRavenwood|CarSystemUIRavenTests)$'
 
 smoke=0
-while getopts "s" opt; do
+include_re=""
+exclude_re=""
+smoke_exclude_re=""
+dry_run=""
+while getopts "sx:f:d" opt; do
 case "$opt" in
     s)
-        smoke=1
+        # Remove slow tests.
+        smoke_exclude_re="$SLOW_TEST_RE"
+        ;;
+    x)
+        # Take a PCRE from the arg, and use it as an exclusion filter.
+        exclude_re="$OPTARG"
+        ;;
+    f)
+        # Take a PCRE from the arg, and use it as an inclusion filter.
+        include_re="$OPTARG"
+        ;;
+    d)
+        # Dry run
+        dry_run="echo"
         ;;
     '?')
         exit 1
@@ -35,21 +61,46 @@
 all_tests=(hoststubgentest tiny-framework-dump-test hoststubgen-invoke-test ravenwood-stats-checker)
 all_tests+=( $(${0%/*}/list-ravenwood-tests.sh) )
 
-# Regex to identify slow tests, in PCRE
-slow_tests_re='^(SystemUiRavenTests|CtsIcuTestCasesRavenwood)$'
-
-if (( $smoke )) ; then
-    # Remove the slow tests.
-    all_tests=( $(
-        for t in "${all_tests[@]}"; do
-            echo $t | grep -vP "$slow_tests_re"
-        done
-    ) )
-fi
-
-run() {
-    echo "Running: $*"
-    "${@}"
+filter() {
+    local re="$1"
+    local grep_arg="$2"
+    if [[ "$re" == "" ]] ; then
+        cat # No filtering
+    else
+        grep $grep_arg -iP "$re"
+    fi
 }
 
-run ${ATEST:-atest} "${all_tests[@]}"
+filter_in() {
+    filter "$1"
+}
+
+filter_out() {
+    filter "$1" -v
+}
+
+
+# Remove the slow tests.
+targets=( $(
+    for t in "${all_tests[@]}"; do
+        echo $t | filter_in "$include_re" | filter_out "$smoke_exclude_re" | filter_out "$exclude_re"
+    done
+) )
+
+# Show the target tests
+
+echo "Target tests:"
+for t in "${targets[@]}"; do
+    echo "  $t"
+done
+
+# Calculate the removed tests.
+
+diff="$(diff  <(echo "${all_tests[@]}" | tr ' ' '\n') <(echo "${targets[@]}" | tr ' ' '\n') )"
+
+if [[ "$diff" != "" ]]; then
+    echo "Excluded tests:"
+    echo "$diff"
+fi
+
+$dry_run ${ATEST:-atest} "${targets[@]}"
diff --git a/ravenwood/tests/runtime-test/Android.bp b/ravenwood/tests/runtime-test/Android.bp
index 4102920..0c0df1f 100644
--- a/ravenwood/tests/runtime-test/Android.bp
+++ b/ravenwood/tests/runtime-test/Android.bp
@@ -10,6 +10,9 @@
 android_ravenwood_test {
     name: "RavenwoodRuntimeTest",
 
+    libs: [
+        "ravenwood-helper-runtime",
+    ],
     static_libs: [
         "androidx.annotation_annotation",
         "androidx.test.ext.junit",
diff --git a/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/IdentityTest.java b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/IdentityTest.java
new file mode 100644
index 0000000..8e04b69
--- /dev/null
+++ b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/IdentityTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2024 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.ravenwoodtest.runtimetest;
+
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
+import static android.os.Process.FIRST_APPLICATION_UID;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.Binder;
+import android.os.Build;
+import android.os.Process;
+import android.platform.test.ravenwood.RavenwoodConfig;
+import android.system.Os;
+
+import com.android.ravenwood.RavenwoodRuntimeState;
+
+import dalvik.system.VMRuntime;
+
+import org.junit.Test;
+
+public class IdentityTest {
+
+    @RavenwoodConfig.Config
+    public static final RavenwoodConfig sConfig =
+            new RavenwoodConfig.Builder()
+                    .setTargetSdkLevel(UPSIDE_DOWN_CAKE)
+                    .setProcessApp()
+                    .build();
+
+    @Test
+    public void testUid() {
+        assertEquals(FIRST_APPLICATION_UID, RavenwoodRuntimeState.sUid);
+        assertEquals(FIRST_APPLICATION_UID, Os.getuid());
+        assertEquals(FIRST_APPLICATION_UID, Process.myUid());
+        assertEquals(FIRST_APPLICATION_UID, Binder.getCallingUid());
+    }
+
+    @Test
+    public void testPid() {
+        int pid = RavenwoodRuntimeState.sPid;
+        assertEquals(pid, Os.getpid());
+        assertEquals(pid, Process.myPid());
+        assertEquals(pid, Binder.getCallingPid());
+    }
+
+    @Test
+    public void testTargetSdkLevel() {
+        assertEquals(Build.VERSION_CODES.CUR_DEVELOPMENT, RavenwoodRuntimeState.CUR_DEVELOPMENT);
+        assertEquals(UPSIDE_DOWN_CAKE, RavenwoodRuntimeState.sTargetSdkLevel);
+        assertEquals(UPSIDE_DOWN_CAKE, VMRuntime.getRuntime().getTargetSdkVersion());
+    }
+}
diff --git a/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/OsTest.java b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/OsTest.java
index c2230c7..c55506a 100644
--- a/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/OsTest.java
+++ b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/OsTest.java
@@ -24,6 +24,8 @@
 import static android.system.OsConstants.S_ISSOCK;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
 
 import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
 
@@ -51,10 +53,12 @@
 import java.nio.file.attribute.PosixFilePermission;
 import java.util.HashSet;
 import java.util.Set;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
 @RunWith(AndroidJUnit4.class)
 public class OsTest {
+
     public interface ConsumerWithThrow<T> {
         void accept(T var1) throws Exception;
     }
@@ -165,6 +169,35 @@
         });
     }
 
+    private static class TestThread extends Thread {
+
+        final CountDownLatch mLatch = new CountDownLatch(1);
+        int mTid;
+
+        TestThread() {
+            setDaemon(true);
+        }
+
+        @Override
+        public void run() {
+            mTid = Os.gettid();
+            mLatch.countDown();
+        }
+    }
+
+    @Test
+    public void testGetTid() throws InterruptedException {
+        var t1 = new TestThread();
+        var t2 = new TestThread();
+        t1.start();
+        t2.start();
+        // Wait for thread execution
+        assertTrue(t1.mLatch.await(1, TimeUnit.SECONDS));
+        assertTrue(t2.mLatch.await(1, TimeUnit.SECONDS));
+        // Make sure the tid is unique per-thread
+        assertNotEquals(t1.mTid, t2.mTid);
+    }
+
     // Verify StructStat values from libcore against native JVM PosixFileAttributes
     private static void assertAttributesEqual(PosixFileAttributes attr, StructStat stat) {
         assertEquals(attr.lastModifiedTime(), convertTimespecToFileTime(stat.st_mtim));
diff --git a/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/ProcessTest.java b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/ProcessTest.java
new file mode 100644
index 0000000..d25b5c1
--- /dev/null
+++ b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/ProcessTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 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.ravenwoodtest.runtimetest;
+
+import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
+import static android.os.Process.THREAD_PRIORITY_DEFAULT;
+import static android.os.Process.THREAD_PRIORITY_FOREGROUND;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import android.os.Process;
+import android.system.Os;
+
+import org.junit.Test;
+
+public class ProcessTest {
+
+    @Test
+    public void testGetUidPidTid() {
+        assertEquals(Os.getuid(), Process.myUid());
+        assertEquals(Os.getpid(), Process.myPid());
+        assertEquals(Os.gettid(), Process.myTid());
+    }
+
+    @Test
+    public void testThreadPriority() {
+        assertThrows(UnsupportedOperationException.class,
+                () -> Process.getThreadPriority(Process.myTid() + 1));
+        assertThrows(UnsupportedOperationException.class,
+                () -> Process.setThreadPriority(Process.myTid() + 1, THREAD_PRIORITY_DEFAULT));
+        assertEquals(THREAD_PRIORITY_DEFAULT, Process.getThreadPriority(Process.myTid()));
+        Process.setThreadPriority(THREAD_PRIORITY_FOREGROUND);
+        assertEquals(THREAD_PRIORITY_FOREGROUND, Process.getThreadPriority(Process.myTid()));
+        Process.setCanSelfBackground(false);
+        Process.setThreadPriority(THREAD_PRIORITY_DEFAULT);
+        assertEquals(THREAD_PRIORITY_DEFAULT, Process.getThreadPriority(Process.myTid()));
+        assertThrows(IllegalArgumentException.class,
+                () -> Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND));
+    }
+}
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 68ff972..8567ccb 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -17,6 +17,7 @@
 package com.android.server.appwidget;
 
 import static android.appwidget.flags.Flags.remoteAdapterConversion;
+import static android.appwidget.flags.Flags.remoteViewsProto;
 import static android.appwidget.flags.Flags.removeAppWidgetServiceIoFromCriticalPath;
 import static android.appwidget.flags.Flags.securityPolicyInteractAcrossUsers;
 import static android.appwidget.flags.Flags.supportResumeRestoreAfterReboot;
@@ -31,6 +32,7 @@
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
 
 import android.Manifest;
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.PermissionName;
@@ -104,6 +106,7 @@
 import android.os.UserManager;
 import android.provider.DeviceConfig;
 import android.service.appwidget.AppWidgetServiceDumpProto;
+import android.service.appwidget.GeneratedPreviewsProto;
 import android.service.appwidget.WidgetProto;
 import android.text.TextUtils;
 import android.util.ArrayMap;
@@ -122,7 +125,9 @@
 import android.util.SparseLongArray;
 import android.util.TypedValue;
 import android.util.Xml;
+import android.util.proto.ProtoInputStream;
 import android.util.proto.ProtoOutputStream;
+import android.util.proto.ProtoUtils;
 import android.view.Display;
 import android.view.View;
 import android.widget.RemoteViews;
@@ -134,6 +139,7 @@
 import com.android.internal.appwidget.IAppWidgetHost;
 import com.android.internal.appwidget.IAppWidgetService;
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
+import com.android.internal.infra.AndroidFuture;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.ArrayUtils;
@@ -221,6 +227,10 @@
     // XML attribute for widget ids that are pending deletion.
     // See {@link Provider#pendingDeletedWidgetIds}.
     private static final String PENDING_DELETED_IDS_ATTR = "pending_deleted_ids";
+    // Name of service directory in /data/system_ce/<user>/
+    private static final String APPWIDGET_CE_DATA_DIRNAME = "appwidget";
+    // Name of previews directory in /data/system_ce/<user>/appwidget/
+    private static final String WIDGET_PREVIEWS_DIRNAME = "previews";
 
     // Hard limit of number of hosts an app can create, note that the app that hosts the widgets
     // can have multiple instances of {@link AppWidgetHost}, typically in respect to different
@@ -320,6 +330,9 @@
 
     // Handler to the background thread that saves states to disk.
     private Handler mSaveStateHandler;
+    // Handler to the background thread that saves generated previews to disk. All operations that
+    // modify saved previews must be run on this Handler.
+    private Handler mSavePreviewsHandler;
     // Handler to the foreground thread that handles broadcasts related to user
     // and package events, as well as various internal events within
     // AppWidgetService.
@@ -363,6 +376,7 @@
         } else {
             mSaveStateHandler = BackgroundThread.getHandler();
         }
+        mSavePreviewsHandler = new Handler(BackgroundThread.get().getLooper());
         final ServiceThread serviceThread = new ServiceThread(TAG,
                 android.os.Process.THREAD_PRIORITY_FOREGROUND, false /* allowIo */);
         serviceThread.start();
@@ -382,7 +396,9 @@
                 SystemUiDeviceConfigFlags.GENERATED_PREVIEW_API_MAX_PROVIDERS,
                 DEFAULT_GENERATED_PREVIEW_MAX_PROVIDERS);
         mGeneratedPreviewsApiCounter = new ApiCounter(generatedPreviewResetInterval,
-                generatedPreviewMaxCallsPerInterval, generatedPreviewsMaxProviders);
+                generatedPreviewMaxCallsPerInterval,
+                // Set a limit on the number of providers if storing them in memory.
+                remoteViewsProto() ? Integer.MAX_VALUE : generatedPreviewsMaxProviders);
         DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_SYSTEMUI,
                 new HandlerExecutor(mCallbackHandler), this::handleSystemUiDeviceConfigChange);
 
@@ -648,7 +664,14 @@
         for (int i = 0; i < providerCount; i++) {
             Provider provider = mProviders.get(i);
             if (provider.id.uid == clearedUid) {
-                changed |= provider.clearGeneratedPreviewsLocked();
+                if (remoteViewsProto()) {
+                    changed |= clearGeneratedPreviewsAsync(provider);
+                } else {
+                    changed |= provider.clearGeneratedPreviewsLocked();
+                }
+                if (DEBUG) {
+                    Slog.e(TAG, "clearPreviewsForUidLocked " + provider + " changed " + changed);
+                }
             }
         }
         return changed;
@@ -898,18 +921,30 @@
             for (int j = 0; j < widgetCount; j++) {
                 Widget widget = provider.widgets.get(j);
                 if (targetWidget != null && targetWidget != widget) continue;
+                // Identify the user in the host process since the intent will be invoked by
+                // the host app.
+                final Host host = widget.host;
+                final UserHandle hostUser;
+                if (host != null && host.id != null) {
+                    hostUser = UserHandle.getUserHandleForUid(host.id.uid);
+                } else {
+                    // Fallback to the parent profile if the host is null.
+                    Slog.w(TAG, "Host is null when masking widget: " + widget.appWidgetId);
+                    hostUser = mUserManager.getProfileParent(appUserId).getUserHandle();
+                }
                 if (provider.maskedByStoppedPackage) {
                     Intent intent = createUpdateIntentLocked(provider,
                             new int[] { widget.appWidgetId });
                     views.setOnClickPendingIntent(android.R.id.background,
-                            PendingIntent.getBroadcast(mContext, widget.appWidgetId,
+                            PendingIntent.getBroadcastAsUser(mContext, widget.appWidgetId,
                                     intent, PendingIntent.FLAG_UPDATE_CURRENT
-                                            | PendingIntent.FLAG_IMMUTABLE));
+                                            | PendingIntent.FLAG_IMMUTABLE, hostUser));
                 } else if (onClickIntent != null) {
                     views.setOnClickPendingIntent(android.R.id.background,
-                            PendingIntent.getActivity(mContext, widget.appWidgetId, onClickIntent,
-                                    PendingIntent.FLAG_UPDATE_CURRENT
-                                       | PendingIntent.FLAG_IMMUTABLE));
+                            PendingIntent.getActivityAsUser(mContext, widget.appWidgetId,
+                            onClickIntent, PendingIntent.FLAG_UPDATE_CURRENT
+                                    | PendingIntent.FLAG_IMMUTABLE, null /* options */,
+                            hostUser));
                 }
                 if (widget.replaceWithMaskedViewsLocked(views)) {
                     scheduleNotifyUpdateAppWidgetLocked(widget, widget.getEffectiveViewsLocked());
@@ -3246,6 +3281,9 @@
         deleteWidgetsLocked(provider, UserHandle.USER_ALL);
         mProviders.remove(provider);
         mGeneratedPreviewsApiCounter.remove(provider.id);
+        if (remoteViewsProto()) {
+            clearGeneratedPreviewsAsync(provider);
+        }
 
         // no need to send the DISABLE broadcast, since the receiver is gone anyway
         cancelBroadcastsLocked(provider);
@@ -3824,6 +3862,14 @@
             } catch (IOException e) {
                 Slog.w(TAG, "Failed to read state: " + e);
             }
+
+            if (remoteViewsProto()) {
+                try {
+                    loadGeneratedPreviewCategoriesLocked(profileId);
+                } catch (IOException e) {
+                    Slog.w(TAG, "Failed to read preview categories: " + e);
+                }
+            }
         }
 
         if (version >= 0) {
@@ -4593,6 +4639,12 @@
                         keep.add(providerId);
                         // Use the new AppWidgetProviderInfo.
                         provider.setPartialInfoLocked(info);
+                        // Clear old previews
+                        if (remoteViewsProto()) {
+                            clearGeneratedPreviewsAsync(provider);
+                        } else {
+                            provider.clearGeneratedPreviewsLocked();
+                        }
                         // If it's enabled
                         final int M = provider.widgets.size();
                         if (M > 0) {
@@ -4884,6 +4936,7 @@
         mSecurityPolicy.enforceCallFromPackage(callingPackage);
         ensureWidgetCategoryCombinationIsValid(widgetCategory);
 
+        AndroidFuture<RemoteViews> result = null;
         synchronized (mLock) {
             ensureGroupStateLoadedLocked(profileId);
             final int providerCount = mProviders.size();
@@ -4917,10 +4970,23 @@
                                 callingPackage);
                 if (providerIsInCallerProfile && !shouldFilterAppAccess
                         && (providerIsInCallerPackage || hasBindAppWidgetPermission)) {
-                    return provider.getGeneratedPreviewLocked(widgetCategory);
+                    if (remoteViewsProto()) {
+                        result = getGeneratedPreviewsAsync(provider, widgetCategory);
+                    } else {
+                        return provider.getGeneratedPreviewLocked(widgetCategory);
+                    }
                 }
             }
         }
+
+        if (result != null) {
+            try {
+                return result.get();
+            } catch (Exception e) {
+                Slog.e(TAG, "Failed to get generated previews Future result", e);
+                return null;
+            }
+        }
         // Either the provider does not exist or the caller does not have permission to access its
         // previews.
         return null;
@@ -4950,8 +5016,12 @@
                         providerComponent + " is not a valid AppWidget provider");
             }
             if (mGeneratedPreviewsApiCounter.tryApiCall(providerId)) {
-                provider.setGeneratedPreviewLocked(widgetCategories, preview);
-                scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
+                if (remoteViewsProto()) {
+                    setGeneratedPreviewsAsync(provider, widgetCategories, preview);
+                } else {
+                    provider.setGeneratedPreviewLocked(widgetCategories, preview);
+                    scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
+                }
                 return true;
             }
             return false;
@@ -4979,11 +5049,361 @@
                 throw new IllegalArgumentException(
                         providerComponent + " is not a valid AppWidget provider");
             }
-            final boolean changed = provider.removeGeneratedPreviewLocked(widgetCategories);
-            if (changed) scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
+
+            if (remoteViewsProto()) {
+                removeGeneratedPreviewsAsync(provider, widgetCategories);
+            } else {
+                final boolean changed = provider.removeGeneratedPreviewLocked(widgetCategories);
+                if (changed) scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
+            }
         }
     }
 
+    /**
+     * Return previews for the specified provider from a background thread. The result of the future
+     * is nullable.
+     */
+    @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+    @NonNull
+    private AndroidFuture<RemoteViews> getGeneratedPreviewsAsync(
+            @NonNull Provider provider, @AppWidgetProviderInfo.CategoryFlags int widgetCategory) {
+        AndroidFuture<RemoteViews> result = new AndroidFuture<>();
+        mSavePreviewsHandler.post(() -> {
+            SparseArray<RemoteViews> previews = loadGeneratedPreviews(provider);
+            for (int i = 0; i < previews.size(); i++) {
+                if ((widgetCategory & previews.keyAt(i)) != 0) {
+                    result.complete(previews.valueAt(i));
+                    return;
+                }
+            }
+            result.complete(null);
+        });
+        return result;
+    }
+
+    /**
+     * Set previews for the specified provider on a background thread.
+     */
+    @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+    private void setGeneratedPreviewsAsync(@NonNull Provider provider, int widgetCategories,
+            @NonNull RemoteViews preview) {
+        mSavePreviewsHandler.post(() -> {
+            SparseArray<RemoteViews> previews = loadGeneratedPreviews(provider);
+            for (int flag : Provider.WIDGET_CATEGORY_FLAGS) {
+                if ((widgetCategories & flag) != 0) {
+                    previews.put(flag, preview);
+                }
+            }
+            saveGeneratedPreviews(provider, previews, /* notify= */ true);
+        });
+    }
+
+    /**
+     * Remove previews for the specified provider on a background thread.
+     */
+    @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+    private void removeGeneratedPreviewsAsync(@NonNull Provider provider, int widgetCategories) {
+        mSavePreviewsHandler.post(() -> {
+            SparseArray<RemoteViews> previews = loadGeneratedPreviews(provider);
+            boolean changed = false;
+            for (int flag : Provider.WIDGET_CATEGORY_FLAGS) {
+                if ((widgetCategories & flag) != 0) {
+                    changed |= previews.removeReturnOld(flag) != null;
+                }
+            }
+            if (changed) {
+                saveGeneratedPreviews(provider, previews, /* notify= */ true);
+            }
+        });
+    }
+
+    /**
+     * Clear previews for the specified provider on a background thread. Returns true if changed
+     * (i.e. there are previews to clear). If returns true, the caller should schedule a providers
+     * changed notification.
+     */
+    @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+    private boolean clearGeneratedPreviewsAsync(@NonNull Provider provider) {
+        mSavePreviewsHandler.post(() -> {
+            saveGeneratedPreviews(provider, /* previews= */ null, /* notify= */ false);
+        });
+        return provider.info.generatedPreviewCategories != 0;
+    }
+
+    private void checkSavePreviewsThread() {
+        if (DEBUG && !mSavePreviewsHandler.getLooper().isCurrentThread()) {
+            throw new IllegalStateException("Only modify previews on the background thread");
+        }
+    }
+
+    /**
+     * Load previews from file for the given provider. If there are no previews, returns an empty
+     * SparseArray. Else, returns a SparseArray of the previews mapped by widget category.
+     */
+    @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+    @NonNull
+    private SparseArray<RemoteViews> loadGeneratedPreviews(@NonNull Provider provider) {
+        checkSavePreviewsThread();
+        try {
+            AtomicFile previewsFile = getWidgetPreviewsFile(provider);
+            if (!previewsFile.exists()) {
+                return new SparseArray<>();
+            }
+            ProtoInputStream input = new ProtoInputStream(previewsFile.readFully());
+            SparseArray<RemoteViews> entries = readGeneratedPreviewsFromProto(input);
+            SparseArray<RemoteViews> singleCategoryKeyedEntries = new SparseArray<>();
+            for (int i = 0; i < entries.size(); i++) {
+                int widgetCategories = entries.keyAt(i);
+                RemoteViews preview = entries.valueAt(i);
+                for (int flag : Provider.WIDGET_CATEGORY_FLAGS) {
+                    if ((widgetCategories & flag) != 0) {
+                        singleCategoryKeyedEntries.put(flag, preview);
+                    }
+                }
+            }
+            return singleCategoryKeyedEntries;
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed to load generated previews for " + provider, e);
+            return new SparseArray<>();
+        }
+    }
+
+    /**
+     * This is called when loading profile/group state to populate
+     * AppWidgetProviderInfo.generatedPreviewCategories based on what previews are saved.
+     *
+     * This is the only time previews are read while not on mSavePreviewsHandler. It happens once
+     * per profile during initialization, before any calls to get/set/removeWidgetPreviewAsync
+     * happen for that profile.
+     */
+    @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+    @GuardedBy("mLock")
+    private void loadGeneratedPreviewCategoriesLocked(int profileId) throws IOException {
+        for (Provider provider : mProviders) {
+            if (provider.id.getProfile().getIdentifier() != profileId) {
+                continue;
+            }
+            AtomicFile previewsFile = getWidgetPreviewsFile(provider);
+            if (!previewsFile.exists()) {
+                continue;
+            }
+            ProtoInputStream input = new ProtoInputStream(previewsFile.readFully());
+            provider.info.generatedPreviewCategories = readGeneratedPreviewCategoriesFromProto(
+                    input);
+            if (DEBUG) {
+                Slog.i(TAG, TextUtils.formatSimple(
+                        "loadGeneratedPreviewCategoriesLocked %d %s categories %d", profileId,
+                        provider, provider.info.generatedPreviewCategories));
+            }
+        }
+    }
+
+    /**
+     * Save the given previews into storage.
+     *
+     * @param provider Provider for which to save previews
+     * @param previews Previews to save. If null or empty, clears any saved previews for this
+     *                 provider.
+     * @param notify If true, then this function will notify hosts of updated provider info.
+     */
+    @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+    private void saveGeneratedPreviews(@NonNull Provider provider,
+            @Nullable SparseArray<RemoteViews> previews, boolean notify) {
+        checkSavePreviewsThread();
+        AtomicFile file = null;
+        FileOutputStream stream = null;
+        try {
+            file = getWidgetPreviewsFile(provider);
+            if (previews == null || previews.size() == 0) {
+                if (file.exists()) {
+                    if (DEBUG) {
+                        Slog.i(TAG, "Deleting widget preview file " + file);
+                    }
+                    file.delete();
+                }
+            } else {
+                if (DEBUG) {
+                    Slog.i(TAG, "Writing widget preview file " + file);
+                }
+                ProtoOutputStream out = new ProtoOutputStream();
+                writePreviewsToProto(out, previews);
+                stream = file.startWrite();
+                stream.write(out.getBytes());
+                file.finishWrite(stream);
+            }
+
+            synchronized (mLock) {
+                provider.updateGeneratedPreviewCategoriesLocked(previews);
+                if (notify) {
+                    scheduleNotifyGroupHostsForProvidersChangedLocked(provider.getUserId());
+                }
+            }
+        } catch (IOException e) {
+            if (file != null && stream != null) {
+                file.failWrite(stream);
+            }
+            Slog.w(TAG, "Failed to save widget previews for provider " + provider.id.componentName);
+        }
+    }
+
+
+    /**
+     * Write the given previews as a GeneratedPreviewsProto to the output stream.
+     */
+    @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+    private void writePreviewsToProto(@NonNull ProtoOutputStream out,
+            @NonNull SparseArray<RemoteViews> generatedPreviews) {
+        // Collect RemoteViews mapped by hashCode in order to avoid writing duplicates.
+        SparseArray<Pair<Integer, RemoteViews>> previewsToWrite = new SparseArray<>();
+        for (int i = 0; i < generatedPreviews.size(); i++) {
+            int widgetCategory = generatedPreviews.keyAt(i);
+            RemoteViews views = generatedPreviews.valueAt(i);
+            if (!previewsToWrite.contains(views.hashCode())) {
+                previewsToWrite.put(views.hashCode(), new Pair<>(widgetCategory, views));
+            } else {
+                Pair<Integer, RemoteViews> entry = previewsToWrite.get(views.hashCode());
+                previewsToWrite.put(views.hashCode(),
+                        Pair.create(entry.first | widgetCategory, views));
+            }
+        }
+
+        for (int i = 0; i < previewsToWrite.size(); i++) {
+            final long token = out.start(GeneratedPreviewsProto.PREVIEWS);
+            Pair<Integer, RemoteViews> entry = previewsToWrite.valueAt(i);
+            out.write(GeneratedPreviewsProto.Preview.WIDGET_CATEGORIES, entry.first);
+            final long viewsToken = out.start(GeneratedPreviewsProto.Preview.VIEWS);
+            entry.second.writePreviewToProto(mContext, out);
+            out.end(viewsToken);
+            out.end(token);
+        }
+    }
+
+    /**
+     * Read a GeneratedPreviewsProto message from the input stream.
+     */
+    @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+    @NonNull
+    private SparseArray<RemoteViews> readGeneratedPreviewsFromProto(@NonNull ProtoInputStream input)
+            throws IOException {
+        SparseArray<RemoteViews> entries = new SparseArray<>();
+        while (input.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (input.getFieldNumber()) {
+                case (int) GeneratedPreviewsProto.PREVIEWS:
+                    final long token = input.start(GeneratedPreviewsProto.PREVIEWS);
+                    Pair<Integer, RemoteViews> entry = readSinglePreviewFromProto(input,
+                            /* skipViews= */ false);
+                    entries.put(entry.first, entry.second);
+                    input.end(token);
+                    break;
+                default:
+                    Slog.w(TAG, "Unknown field while reading GeneratedPreviewsProto! "
+                            + ProtoUtils.currentFieldToString(input));
+            }
+        }
+        return entries;
+    }
+
+    /**
+     * Read the widget categories from GeneratedPreviewsProto and return an int representing the
+     * combined widget categories of all the previews.
+     */
+    @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+    @AppWidgetProviderInfo.CategoryFlags
+    private int readGeneratedPreviewCategoriesFromProto(@NonNull ProtoInputStream input)
+            throws IOException {
+        int widgetCategories = 0;
+        while (input.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (input.getFieldNumber()) {
+                case (int) GeneratedPreviewsProto.PREVIEWS:
+                    final long token = input.start(GeneratedPreviewsProto.PREVIEWS);
+                    Pair<Integer, RemoteViews> entry = readSinglePreviewFromProto(input,
+                            /* skipViews= */ true);
+                    widgetCategories |= entry.first;
+                    input.end(token);
+                    break;
+                default:
+                    Slog.w(TAG, "Unknown field while reading GeneratedPreviewsProto! "
+                            + ProtoUtils.currentFieldToString(input));
+            }
+        }
+        return widgetCategories;
+    }
+
+    /**
+     * Read a single GeneratedPreviewsProto.Preview message from the input stream, and returns a
+     * pair of widget category and corresponding RemoteViews. If skipViews is true, this function
+     * will only read widget categories and the returned RemoteViews will be null.
+     */
+    @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+    @NonNull
+    private Pair<Integer, RemoteViews> readSinglePreviewFromProto(@NonNull ProtoInputStream input,
+            boolean skipViews) throws IOException {
+        int widgetCategories = 0;
+        RemoteViews views = null;
+        while (input.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (input.getFieldNumber()) {
+                case (int) GeneratedPreviewsProto.Preview.VIEWS:
+                    if (skipViews)  {
+                        // ProtoInputStream will skip over the nested message when nextField() is
+                        // called.
+                        continue;
+                    }
+                    final long token = input.start(GeneratedPreviewsProto.Preview.VIEWS);
+                    try {
+                        views = RemoteViews.createPreviewFromProto(mContext, input);
+                    } catch (Exception e) {
+                        Slog.e(TAG, "Unable to deserialize RemoteViews", e);
+                    }
+                    input.end(token);
+                    break;
+                case (int) GeneratedPreviewsProto.Preview.WIDGET_CATEGORIES:
+                    widgetCategories = input.readInt(
+                            GeneratedPreviewsProto.Preview.WIDGET_CATEGORIES);
+                    break;
+                default:
+                    Slog.w(TAG, "Unknown field while reading GeneratedPreviewsProto! "
+                            + ProtoUtils.currentFieldToString(input));
+            }
+        }
+        return Pair.create(widgetCategories, views);
+    }
+
+    /**
+     * Returns the file in which all generated previews for this provider are stored. This will be
+     * a path of the form:
+     *  {@literal /data/system_ce/<userId>/appwidget/previews/<package>-<class>-<uid>.binpb}
+     *
+     * This function will not create the file if it does not already exist.
+     */
+    @NonNull
+    private static AtomicFile getWidgetPreviewsFile(@NonNull Provider provider) throws IOException {
+        int userId = provider.getUserId();
+        File previewsDirectory = getWidgetPreviewsDirectory(userId);
+        File providerPreviews = Environment.buildPath(previewsDirectory,
+                TextUtils.formatSimple("%s-%s-%d.binpb", provider.id.componentName.getPackageName(),
+                        provider.id.componentName.getClassName(), provider.id.uid));
+        return new AtomicFile(providerPreviews);
+    }
+
+    /**
+     * Returns the widget previews directory for the given user, creating it if it does not exist.
+     * This will be a path of the form:
+     *  {@literal /data/system_ce/<userId>/appwidget/previews}
+     */
+    @NonNull
+    private static File getWidgetPreviewsDirectory(int userId) throws IOException {
+        File dataSystemCeDirectory = Environment.getDataSystemCeDirectory(userId);
+        File previewsDirectory = Environment.buildPath(dataSystemCeDirectory,
+                APPWIDGET_CE_DATA_DIRNAME, WIDGET_PREVIEWS_DIRNAME);
+        if (!previewsDirectory.exists()) {
+            if (!previewsDirectory.mkdirs()) {
+                throw new IOException("Unable to create widget preview directory "
+                        + previewsDirectory.getPath());
+            }
+        }
+        return previewsDirectory;
+    }
+
     private static void ensureWidgetCategoryCombinationIsValid(int widgetCategories) {
         int validCategories = AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN
                 | AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD
@@ -5414,11 +5834,11 @@
                                 AppWidgetManager.META_DATA_APPWIDGET_PROVIDER);
                     }
                     if (newInfo != null) {
+                        newInfo.generatedPreviewCategories = info.generatedPreviewCategories;
                         info = newInfo;
                         if (DEBUG) {
                             Objects.requireNonNull(info);
                         }
-                        updateGeneratedPreviewCategoriesLocked();
                     }
                 }
                 mInfoParsed = true;
@@ -5475,7 +5895,7 @@
                     generatedPreviews.put(flag, preview);
                 }
             }
-            updateGeneratedPreviewCategoriesLocked();
+            updateGeneratedPreviewCategoriesLocked(generatedPreviews);
         }
 
         @GuardedBy("this.mLock")
@@ -5487,7 +5907,7 @@
                 }
             }
             if (changed) {
-                updateGeneratedPreviewCategoriesLocked();
+                updateGeneratedPreviewCategoriesLocked(generatedPreviews);
             }
             return changed;
         }
@@ -5496,17 +5916,19 @@
         public boolean clearGeneratedPreviewsLocked() {
             if (generatedPreviews.size() > 0) {
                 generatedPreviews.clear();
-                updateGeneratedPreviewCategoriesLocked();
+                updateGeneratedPreviewCategoriesLocked(generatedPreviews);
                 return true;
             }
             return false;
         }
-
         @GuardedBy("this.mLock")
-        private void updateGeneratedPreviewCategoriesLocked() {
+        private void updateGeneratedPreviewCategoriesLocked(
+                @Nullable SparseArray<RemoteViews> previews) {
             info.generatedPreviewCategories = 0;
-            for (int i = 0; i < generatedPreviews.size(); i++) {
-                info.generatedPreviewCategories |= generatedPreviews.keyAt(i);
+            if (previews != null) {
+                for (int i = 0; i < previews.size(); i++) {
+                    info.generatedPreviewCategories |= previews.keyAt(i);
+                }
             }
         }
 
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 51034d2..7cba9e0 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -31,16 +31,13 @@
 
 import static com.android.internal.util.CollectionUtils.any;
 import static com.android.internal.util.Preconditions.checkState;
-import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 import static com.android.server.companion.utils.PackageUtils.enforceUsesCompanionDeviceFeature;
-import static com.android.server.companion.utils.PackageUtils.getPackageInfo;
 import static com.android.server.companion.utils.PackageUtils.isRestrictedSettingsAllowed;
 import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanManageAssociationsForPackage;
 import static com.android.server.companion.utils.PermissionsUtils.enforceCallerIsSystemOr;
 import static com.android.server.companion.utils.PermissionsUtils.enforceCallerIsSystemOrCanInteractWithUserId;
 
 import static java.util.Objects.requireNonNull;
-import static java.util.concurrent.TimeUnit.MINUTES;
 
 import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
@@ -69,31 +66,22 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.net.MacAddress;
-import android.net.NetworkPolicyManager;
 import android.os.Binder;
-import android.os.Environment;
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
 import android.os.PowerExemptionManager;
 import android.os.PowerManagerInternal;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.permission.flags.Flags;
-import android.util.ArraySet;
 import android.util.ExceptionUtils;
 import android.util.Slog;
 
-import com.android.internal.app.IAppOpsService;
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.notification.NotificationAccessConfirmationActivityContract;
-import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.server.FgThread;
@@ -114,35 +102,25 @@
 import com.android.server.companion.devicepresence.ObservableUuid;
 import com.android.server.companion.devicepresence.ObservableUuidStore;
 import com.android.server.companion.transport.CompanionTransportManager;
-import com.android.server.pm.UserManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
-import java.io.File;
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.Collection;
 import java.util.List;
-import java.util.Set;
 
 @SuppressLint("LongLogTag")
 public class CompanionDeviceManagerService extends SystemService {
     private static final String TAG = "CDM_CompanionDeviceManagerService";
 
     private static final long PAIR_WITHOUT_PROMPT_WINDOW_MS = 10 * 60 * 1000; // 10 min
-
-    private static final String PREF_FILE_NAME = "companion_device_preferences.xml";
-    private static final String PREF_KEY_AUTO_REVOKE_GRANTS_DONE = "auto_revoke_grants_done";
     private static final int MAX_CN_LENGTH = 500;
 
-    private final ActivityTaskManagerInternal mAtmInternal;
-    private final ActivityManagerInternal mAmInternal;
-    private final IAppOpsService mAppOpsManager;
-    private final PowerExemptionManager mPowerExemptionManager;
-    private final PackageManagerInternal mPackageManagerInternal;
-
     private final AssociationStore mAssociationStore;
     private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
     private final ObservableUuidStore mObservableUuidStore;
+
+    private final CompanionExemptionProcessor mCompanionExemptionProcessor;
     private final AssociationRequestsProcessor mAssociationRequestsProcessor;
     private final SystemDataTransferProcessor mSystemDataTransferProcessor;
     private final BackupRestoreProcessor mBackupRestoreProcessor;
@@ -156,12 +134,15 @@
         super(context);
 
         final ActivityManager activityManager = context.getSystemService(ActivityManager.class);
-        mPowerExemptionManager = context.getSystemService(PowerExemptionManager.class);
-        mAppOpsManager = IAppOpsService.Stub.asInterface(
-                ServiceManager.getService(Context.APP_OPS_SERVICE));
-        mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
-        mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
-        mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
+        final PowerExemptionManager powerExemptionManager = context.getSystemService(
+                PowerExemptionManager.class);
+        final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
+        final ActivityTaskManagerInternal atmInternal = LocalServices.getService(
+                ActivityTaskManagerInternal.class);
+        final ActivityManagerInternal amInternal = LocalServices.getService(
+                ActivityManagerInternal.class);
+        final PackageManagerInternal packageManagerInternal = LocalServices.getService(
+                PackageManagerInternal.class);
         final UserManager userManager = context.getSystemService(UserManager.class);
         final PowerManagerInternal powerManagerInternal = LocalServices.getService(
                 PowerManagerInternal.class);
@@ -173,25 +154,29 @@
 
         // Init processors
         mAssociationRequestsProcessor = new AssociationRequestsProcessor(context,
-                mPackageManagerInternal, mAssociationStore);
-        mBackupRestoreProcessor = new BackupRestoreProcessor(context, mPackageManagerInternal,
+                packageManagerInternal, mAssociationStore);
+        mBackupRestoreProcessor = new BackupRestoreProcessor(context, packageManagerInternal,
                 mAssociationStore, associationDiskStore, mSystemDataTransferRequestStore,
                 mAssociationRequestsProcessor);
 
         mCompanionAppBinder = new CompanionAppBinder(context);
 
+        mCompanionExemptionProcessor = new CompanionExemptionProcessor(context,
+                powerExemptionManager, appOpsManager, packageManagerInternal, atmInternal,
+                amInternal, mAssociationStore);
+
         mDevicePresenceProcessor = new DevicePresenceProcessor(context,
                 mCompanionAppBinder, userManager, mAssociationStore, mObservableUuidStore,
-                powerManagerInternal);
+                powerManagerInternal, mCompanionExemptionProcessor);
 
         mTransportManager = new CompanionTransportManager(context, mAssociationStore);
 
         mDisassociationProcessor = new DisassociationProcessor(context, activityManager,
-                mAssociationStore, mPackageManagerInternal, mDevicePresenceProcessor,
+                mAssociationStore, packageManagerInternal, mDevicePresenceProcessor,
                 mCompanionAppBinder, mSystemDataTransferRequestStore, mTransportManager);
 
         mSystemDataTransferProcessor = new SystemDataTransferProcessor(this,
-                mPackageManagerInternal, mAssociationStore,
+                packageManagerInternal, mAssociationStore,
                 mSystemDataTransferRequestStore, mTransportManager);
 
         // TODO(b/279663946): move context sync to a dedicated system service
@@ -202,7 +187,6 @@
     public void onStart() {
         // Init association stores
         mAssociationStore.refreshCache();
-        mAssociationStore.registerLocalListener(mAssociationStoreChangeListener);
 
         // Init UUID store
         mObservableUuidStore.getObservableUuidsForUser(getContext().getUserId());
@@ -240,11 +224,8 @@
 
         if (associations.isEmpty()) return;
 
-        updateAtm(userId, associations);
-
-        BackgroundThread.getHandler().sendMessageDelayed(
-                obtainMessage(CompanionDeviceManagerService::maybeGrantAutoRevokeExemptions, this),
-                MINUTES.toMillis(10));
+        mCompanionExemptionProcessor.updateAtm(userId, associations);
+        mCompanionExemptionProcessor.updateAutoRevokeExemptions();
     }
 
     @Override
@@ -262,9 +243,12 @@
         if (!associationsForPackage.isEmpty()) {
             Slog.i(TAG, "Package removed or data cleared for user=[" + userId + "], package=["
                     + packageName + "]. Cleaning up CDM data...");
-        }
-        for (AssociationInfo association : associationsForPackage) {
-            mDisassociationProcessor.disassociate(association.getId());
+
+            for (AssociationInfo association : associationsForPackage) {
+                mDisassociationProcessor.disassociate(association.getId());
+            }
+
+            mCompanionAppBinder.onPackageChanged(userId);
         }
 
         // Clear observable UUIDs for the package.
@@ -273,19 +257,16 @@
         for (ObservableUuid uuid : uuidsTobeObserved) {
             mObservableUuidStore.removeObservableUuid(userId, uuid.getUuid(), packageName);
         }
-
-        mCompanionAppBinder.onPackagesChanged(userId);
     }
 
     private void onPackageModifiedInternal(@UserIdInt int userId, @NonNull String packageName) {
-        final List<AssociationInfo> associationsForPackage =
+        final List<AssociationInfo> associations =
                 mAssociationStore.getAssociationsByPackage(userId, packageName);
-        for (AssociationInfo association : associationsForPackage) {
-            updateSpecialAccessPermissionForAssociatedPackage(association.getUserId(),
-                    association.getPackageName());
-        }
+        if (!associations.isEmpty()) {
+            mCompanionExemptionProcessor.exemptPackage(userId, packageName, false);
 
-        mCompanionAppBinder.onPackagesChanged(userId);
+            mCompanionAppBinder.onPackageChanged(userId);
+        }
     }
 
     private void onPackageAddedInternal(@UserIdInt int userId, @NonNull String packageName) {
@@ -765,130 +746,6 @@
         }
     }
 
-    /**
-     * Update special access for the association's package
-     */
-    public void updateSpecialAccessPermissionForAssociatedPackage(int userId, String packageName) {
-        final PackageInfo packageInfo =
-                getPackageInfo(getContext(), userId, packageName);
-
-        Binder.withCleanCallingIdentity(() -> updateSpecialAccessPermissionAsSystem(packageInfo));
-    }
-
-    private void updateSpecialAccessPermissionAsSystem(PackageInfo packageInfo) {
-        if (packageInfo == null) {
-            return;
-        }
-
-        if (containsEither(packageInfo.requestedPermissions,
-                android.Manifest.permission.RUN_IN_BACKGROUND,
-                android.Manifest.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND)) {
-            mPowerExemptionManager.addToPermanentAllowList(packageInfo.packageName);
-        } else {
-            try {
-                mPowerExemptionManager.removeFromPermanentAllowList(packageInfo.packageName);
-            } catch (UnsupportedOperationException e) {
-                Slog.w(TAG, packageInfo.packageName + " can't be removed from power save"
-                        + " whitelist. It might due to the package is whitelisted by the system.");
-            }
-        }
-
-        NetworkPolicyManager networkPolicyManager = NetworkPolicyManager.from(getContext());
-        try {
-            if (containsEither(packageInfo.requestedPermissions,
-                    android.Manifest.permission.USE_DATA_IN_BACKGROUND,
-                    android.Manifest.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND)) {
-                networkPolicyManager.addUidPolicy(
-                        packageInfo.applicationInfo.uid,
-                        NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND);
-            } else {
-                networkPolicyManager.removeUidPolicy(
-                        packageInfo.applicationInfo.uid,
-                        NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND);
-            }
-        } catch (IllegalArgumentException e) {
-            Slog.e(TAG, e.getMessage());
-        }
-
-        exemptFromAutoRevoke(packageInfo.packageName, packageInfo.applicationInfo.uid);
-    }
-
-    private void exemptFromAutoRevoke(String packageName, int uid) {
-        try {
-            mAppOpsManager.setMode(
-                    AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED,
-                    uid,
-                    packageName,
-                    AppOpsManager.MODE_IGNORED);
-        } catch (RemoteException e) {
-            Slog.w(TAG, "Error while granting auto revoke exemption for " + packageName, e);
-        }
-    }
-
-    private void updateAtm(int userId, List<AssociationInfo> associations) {
-        final Set<Integer> companionAppUids = new ArraySet<>();
-        for (AssociationInfo association : associations) {
-            final int uid = mPackageManagerInternal.getPackageUid(association.getPackageName(),
-                    0, userId);
-            if (uid >= 0) {
-                companionAppUids.add(uid);
-            }
-        }
-        if (mAtmInternal != null) {
-            mAtmInternal.setCompanionAppUids(userId, companionAppUids);
-        }
-        if (mAmInternal != null) {
-            // Make a copy of the set and send it to ActivityManager.
-            mAmInternal.setCompanionAppUids(userId, new ArraySet<>(companionAppUids));
-        }
-    }
-
-    private void maybeGrantAutoRevokeExemptions() {
-        Slog.d(TAG, "maybeGrantAutoRevokeExemptions()");
-
-        PackageManager pm = getContext().getPackageManager();
-        for (int userId : LocalServices.getService(UserManagerInternal.class).getUserIds()) {
-            SharedPreferences pref = getContext().getSharedPreferences(
-                    new File(Environment.getUserSystemDirectory(userId), PREF_FILE_NAME),
-                    Context.MODE_PRIVATE);
-            if (pref.getBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, false)) {
-                continue;
-            }
-
-            try {
-                final List<AssociationInfo> associations =
-                        mAssociationStore.getActiveAssociationsByUser(userId);
-                for (AssociationInfo a : associations) {
-                    try {
-                        int uid = pm.getPackageUidAsUser(a.getPackageName(), userId);
-                        exemptFromAutoRevoke(a.getPackageName(), uid);
-                    } catch (PackageManager.NameNotFoundException e) {
-                        Slog.w(TAG, "Unknown companion package: " + a.getPackageName(), e);
-                    }
-                }
-            } finally {
-                pref.edit().putBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, true).apply();
-            }
-        }
-    }
-
-    private final AssociationStore.OnChangeListener mAssociationStoreChangeListener =
-            new AssociationStore.OnChangeListener() {
-                @Override
-                public void onAssociationChanged(int changeType, AssociationInfo association) {
-                    Slog.d(TAG, "onAssociationChanged changeType=[" + changeType
-                            + "], association=[" + association);
-
-                    final int userId = association.getUserId();
-                    final List<AssociationInfo> updatedAssociations =
-                            mAssociationStore.getActiveAssociationsByUser(userId);
-
-                    updateAtm(userId, updatedAssociations);
-                    updateSpecialAccessPermissionForAssociatedPackage(association.getUserId(),
-                            association.getPackageName());
-                }
-            };
-
     private final PackageMonitor mPackageMonitor = new PackageMonitor() {
         @Override
         public void onPackageRemoved(String packageName, int uid) {
@@ -911,10 +768,6 @@
         }
     };
 
-    private static <T> boolean containsEither(T[] array, T a, T b) {
-        return ArrayUtils.contains(array, a) || ArrayUtils.contains(array, b);
-    }
-
     private class LocalService implements CompanionDeviceManagerServiceInternal {
 
         @Override
diff --git a/services/companion/java/com/android/server/companion/CompanionExemptionProcessor.java b/services/companion/java/com/android/server/companion/CompanionExemptionProcessor.java
new file mode 100644
index 0000000..4969ffbf
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/CompanionExemptionProcessor.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion;
+
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_IGNORED;
+
+import static com.android.server.companion.utils.PackageUtils.getPackageInfo;
+
+import android.annotation.SuppressLint;
+import android.app.ActivityManagerInternal;
+import android.app.AppOpsManager;
+import android.companion.AssociationInfo;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.net.NetworkPolicyManager;
+import android.os.Binder;
+import android.os.Environment;
+import android.os.PowerExemptionManager;
+import android.util.ArraySet;
+import android.util.Slog;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.server.LocalServices;
+import com.android.server.companion.association.AssociationStore;
+import com.android.server.pm.UserManagerInternal;
+import com.android.server.wm.ActivityTaskManagerInternal;
+
+import java.io.File;
+import java.util.List;
+import java.util.Set;
+
+@SuppressLint("LongLogTag")
+public class CompanionExemptionProcessor {
+
+    private static final String TAG = "CDM_CompanionExemptionProcessor";
+
+    private static final String PREF_FILE_NAME = "companion_device_preferences.xml";
+    private static final String PREF_KEY_AUTO_REVOKE_GRANTS_DONE = "auto_revoke_grants_done";
+
+    private final Context mContext;
+    private final PowerExemptionManager mPowerExemptionManager;
+    private final AppOpsManager mAppOpsManager;
+    private final PackageManagerInternal mPackageManager;
+    private final ActivityTaskManagerInternal mAtmInternal;
+    private final ActivityManagerInternal mAmInternal;
+    private final AssociationStore mAssociationStore;
+
+    public CompanionExemptionProcessor(Context context, PowerExemptionManager powerExemptionManager,
+            AppOpsManager appOpsManager, PackageManagerInternal packageManager,
+            ActivityTaskManagerInternal atmInternal, ActivityManagerInternal amInternal,
+            AssociationStore associationStore) {
+        mContext = context;
+        mPowerExemptionManager = powerExemptionManager;
+        mAppOpsManager = appOpsManager;
+        mPackageManager = packageManager;
+        mAtmInternal = atmInternal;
+        mAmInternal = amInternal;
+        mAssociationStore = associationStore;
+
+        mAssociationStore.registerLocalListener(new AssociationStore.OnChangeListener() {
+            @Override
+            public void onAssociationChanged(int changeType, AssociationInfo association) {
+                final int userId = association.getUserId();
+                final List<AssociationInfo> updatedAssociations =
+                        mAssociationStore.getActiveAssociationsByUser(userId);
+
+                updateAtm(userId, updatedAssociations);
+            }
+        });
+    }
+
+    /**
+     * Update ActivityManager and ActivityTaskManager exemptions
+     */
+    public void updateAtm(int userId, List<AssociationInfo> associations) {
+        final Set<Integer> companionAppUids = new ArraySet<>();
+        for (AssociationInfo association : associations) {
+            int uid = mPackageManager.getPackageUid(association.getPackageName(), 0, userId);
+            if (uid >= 0) {
+                companionAppUids.add(uid);
+            }
+        }
+        if (mAtmInternal != null) {
+            mAtmInternal.setCompanionAppUids(userId, companionAppUids);
+        }
+        if (mAmInternal != null) {
+            // Make a copy of the set and send it to ActivityManager.
+            mAmInternal.setCompanionAppUids(userId, new ArraySet<>(companionAppUids));
+        }
+    }
+
+    /**
+     * Update special access for the association's package
+     */
+    public void exemptPackage(int userId, String packageName, boolean hasPresentDevices) {
+        Slog.i(TAG, "Exempting package [" + packageName + "]...");
+
+        final PackageInfo packageInfo = getPackageInfo(mContext, userId, packageName);
+
+        Binder.withCleanCallingIdentity(
+                () -> exemptPackageAsSystem(userId, packageInfo, hasPresentDevices));
+    }
+
+    @SuppressLint("MissingPermission")
+    private void exemptPackageAsSystem(int userId, PackageInfo packageInfo,
+            boolean hasPresentDevices) {
+        if (packageInfo == null) {
+            return;
+        }
+
+        // If the app has run-in-bg permission and present devices, add it to power saver allowlist.
+        if (containsEither(packageInfo.requestedPermissions,
+                android.Manifest.permission.RUN_IN_BACKGROUND,
+                android.Manifest.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND)
+                && hasPresentDevices) {
+            mPowerExemptionManager.addToPermanentAllowList(packageInfo.packageName);
+        } else {
+            try {
+                mPowerExemptionManager.removeFromPermanentAllowList(packageInfo.packageName);
+            } catch (UnsupportedOperationException e) {
+                Slog.w(TAG, packageInfo.packageName + " can't be removed from power save"
+                        + " allowlist. It might be due to the package being allowlisted by the"
+                        + " system.");
+            }
+        }
+
+        // If the app has run-in-bg permission and present device, allow metered network use.
+        NetworkPolicyManager networkPolicyManager = NetworkPolicyManager.from(mContext);
+        try {
+            if (containsEither(packageInfo.requestedPermissions,
+                    android.Manifest.permission.USE_DATA_IN_BACKGROUND,
+                    android.Manifest.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND)
+                    && hasPresentDevices) {
+                networkPolicyManager.addUidPolicy(
+                        packageInfo.applicationInfo.uid,
+                        NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND);
+            } else {
+                networkPolicyManager.removeUidPolicy(
+                        packageInfo.applicationInfo.uid,
+                        NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND);
+            }
+        } catch (IllegalArgumentException e) {
+            Slog.e(TAG, e.getMessage());
+        }
+
+        updateAutoRevokeExemption(packageInfo.packageName, packageInfo.applicationInfo.uid,
+                !mAssociationStore.getActiveAssociationsByPackage(userId,
+                        packageInfo.packageName).isEmpty());
+    }
+
+    /**
+     * Update auto revoke exemptions.
+     * If the app has any association, exempt it from permission auto revoke.
+     */
+    public void updateAutoRevokeExemptions() {
+        Slog.d(TAG, "maybeGrantAutoRevokeExemptions()");
+
+        PackageManager pm = mContext.getPackageManager();
+        for (int userId : LocalServices.getService(UserManagerInternal.class).getUserIds()) {
+            SharedPreferences pref = mContext.getSharedPreferences(
+                    new File(Environment.getUserSystemDirectory(userId), PREF_FILE_NAME),
+                    Context.MODE_PRIVATE);
+            if (pref.getBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, false)) {
+                continue;
+            }
+
+            try {
+                final List<AssociationInfo> associations =
+                        mAssociationStore.getActiveAssociationsByUser(userId);
+                for (AssociationInfo a : associations) {
+                    try {
+                        int uid = pm.getPackageUidAsUser(a.getPackageName(), userId);
+                        updateAutoRevokeExemption(a.getPackageName(), uid, true);
+                    } catch (PackageManager.NameNotFoundException e) {
+                        Slog.w(TAG, "Unknown companion package: " + a.getPackageName(), e);
+                    }
+                }
+            } finally {
+                pref.edit().putBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, true).apply();
+            }
+        }
+    }
+
+    @SuppressLint("MissingPermission")
+    private void updateAutoRevokeExemption(String packageName, int uid, boolean hasAssociations) {
+        try {
+            mAppOpsManager.setMode(
+                    AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED,
+                    uid,
+                    packageName,
+                    hasAssociations ? MODE_IGNORED : MODE_ALLOWED);
+        } catch (Exception e) {
+            Slog.e(TAG, "Error while granting auto revoke exemption for " + packageName, e);
+        }
+    }
+
+    private <T> boolean containsEither(T[] array, T a, T b) {
+        return ArrayUtils.contains(array, a) || ArrayUtils.contains(array, b);
+    }
+
+}
diff --git a/services/companion/java/com/android/server/companion/devicepresence/CompanionAppBinder.java b/services/companion/java/com/android/server/companion/devicepresence/CompanionAppBinder.java
index 60f4688..8307da5 100644
--- a/services/companion/java/com/android/server/companion/devicepresence/CompanionAppBinder.java
+++ b/services/companion/java/com/android/server/companion/devicepresence/CompanionAppBinder.java
@@ -95,7 +95,9 @@
     /**
      * On package changed.
      */
-    public void onPackagesChanged(@UserIdInt int userId) {
+    public void onPackageChanged(@UserIdInt int userId) {
+        // Note: To invalidate the user space for simplicity. We could alternatively manage each
+        //       package, but that would easily cause errors if one case is mis-handled.
         mCompanionServicesRegister.invalidate(userId);
     }
 
@@ -299,12 +301,14 @@
 
     private class CompanionServicesRegister extends PerUser<Map<String, List<ComponentName>>> {
         @Override
-        public synchronized @NonNull Map<String, List<ComponentName>> forUser(
+        @NonNull
+        public synchronized Map<String, List<ComponentName>> forUser(
                 @UserIdInt int userId) {
             return super.forUser(userId);
         }
 
-        synchronized @NonNull List<ComponentName> forPackage(
+        @NonNull
+        synchronized List<ComponentName> forPackage(
                 @UserIdInt int userId, @NonNull String packageName) {
             return forUser(userId).getOrDefault(packageName, Collections.emptyList());
         }
@@ -314,7 +318,8 @@
         }
 
         @Override
-        protected final @NonNull Map<String, List<ComponentName>> create(@UserIdInt int userId) {
+        @NonNull
+        protected final Map<String, List<ComponentName>> create(@UserIdInt int userId) {
             return PackageUtils.getCompanionServicesForUser(mContext, userId);
         }
     }
diff --git a/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java b/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java
index a374d27..7b4dd7d 100644
--- a/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java
+++ b/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java
@@ -57,6 +57,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.CollectionUtils;
+import com.android.server.companion.CompanionExemptionProcessor;
 import com.android.server.companion.association.AssociationStore;
 
 import java.io.PrintWriter;
@@ -101,6 +102,8 @@
     private final PowerManagerInternal mPowerManagerInternal;
     @NonNull
     private final UserManager mUserManager;
+    @NonNull
+    private final CompanionExemptionProcessor mCompanionExemptionProcessor;
 
     // NOTE: Same association may appear in more than one of the following sets at the same time.
     // (E.g. self-managed devices that have MAC addresses, could be reported as present by their
@@ -111,7 +114,7 @@
     @NonNull
     private final Set<Integer> mNearbyBleDevices = new HashSet<>();
     @NonNull
-    private final Set<Integer> mReportedSelfManagedDevices = new HashSet<>();
+    private final Set<Integer> mConnectedSelfManagedDevices = new HashSet<>();
     @NonNull
     private final Set<ParcelUuid> mConnectedUuidDevices = new HashSet<>();
     @NonNull
@@ -146,7 +149,8 @@
             @NonNull UserManager userManager,
             @NonNull AssociationStore associationStore,
             @NonNull ObservableUuidStore observableUuidStore,
-            @NonNull PowerManagerInternal powerManagerInternal) {
+            @NonNull PowerManagerInternal powerManagerInternal,
+            @NonNull CompanionExemptionProcessor companionExemptionProcessor) {
         mContext = context;
         mCompanionAppBinder = companionAppBinder;
         mAssociationStore = associationStore;
@@ -156,6 +160,7 @@
                 mObservableUuidStore, this);
         mBleDeviceProcessor = new BleDeviceProcessor(associationStore, this);
         mPowerManagerInternal = powerManagerInternal;
+        mCompanionExemptionProcessor = companionExemptionProcessor;
     }
 
     /** Initialize {@link DevicePresenceProcessor} */
@@ -404,7 +409,7 @@
      * nearby (for "self-managed" associations).
      */
     public boolean isDevicePresent(int associationId) {
-        return mReportedSelfManagedDevices.contains(associationId)
+        return mConnectedSelfManagedDevices.contains(associationId)
                 || mConnectedBtDevices.contains(associationId)
                 || mNearbyBleDevices.contains(associationId)
                 || mSimulated.contains(associationId);
@@ -451,7 +456,7 @@
      * notifyDeviceAppeared()}
      */
     public void onSelfManagedDeviceConnected(int associationId) {
-        onDevicePresenceEvent(mReportedSelfManagedDevices,
+        onDevicePresenceEvent(mConnectedSelfManagedDevices,
                 associationId, EVENT_SELF_MANAGED_APPEARED);
     }
 
@@ -467,7 +472,7 @@
      * notifyDeviceDisappeared()}
      */
     public void onSelfManagedDeviceDisconnected(int associationId) {
-        onDevicePresenceEvent(mReportedSelfManagedDevices,
+        onDevicePresenceEvent(mConnectedSelfManagedDevices,
                 associationId, EVENT_SELF_MANAGED_DISAPPEARED);
     }
 
@@ -475,7 +480,7 @@
      * Marks a "self-managed" device as disconnected when binderDied.
      */
     public void onSelfManagedDeviceReporterBinderDied(int associationId) {
-        onDevicePresenceEvent(mReportedSelfManagedDevices,
+        onDevicePresenceEvent(mConnectedSelfManagedDevices,
                 associationId, EVENT_SELF_MANAGED_DISAPPEARED);
     }
 
@@ -683,6 +688,7 @@
 
                 if (association.shouldBindWhenPresent()) {
                     bindApplicationIfNeeded(userId, packageName, association.isSelfManaged());
+                    mCompanionExemptionProcessor.exemptPackage(userId, packageName, true);
                 } else {
                     return;
                 }
@@ -715,6 +721,7 @@
                 // Check if there are other devices associated to the app that are present.
                 if (!shouldBindPackage(userId, packageName)) {
                     mCompanionAppBinder.unbindCompanionApp(userId, packageName);
+                    mCompanionExemptionProcessor.exemptPackage(userId, packageName, false);
                 }
                 break;
             default:
@@ -940,7 +947,7 @@
 
         mConnectedBtDevices.remove(id);
         mNearbyBleDevices.remove(id);
-        mReportedSelfManagedDevices.remove(id);
+        mConnectedSelfManagedDevices.remove(id);
         mSimulated.remove(id);
         synchronized (mBtDisconnectedDevices) {
             mBtDisconnectedDevices.remove(id);
@@ -1100,7 +1107,7 @@
         out.append("Companion Device Present: ");
         if (mConnectedBtDevices.isEmpty()
                 && mNearbyBleDevices.isEmpty()
-                && mReportedSelfManagedDevices.isEmpty()) {
+                && mConnectedSelfManagedDevices.isEmpty()) {
             out.append("<empty>\n");
             return;
         } else {
@@ -1130,11 +1137,11 @@
         }
 
         out.append("  Self-Reported Devices: ");
-        if (mReportedSelfManagedDevices.isEmpty()) {
+        if (mConnectedSelfManagedDevices.isEmpty()) {
             out.append("<empty>\n");
         } else {
             out.append("\n");
-            for (int associationId : mReportedSelfManagedDevices) {
+            for (int associationId : mConnectedSelfManagedDevices) {
                 AssociationInfo a = mAssociationStore.getAssociationById(associationId);
                 out.append("    ").append(a.toShortString()).append('\n');
             }
diff --git a/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java b/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
index ef39846..8a4b1fa 100644
--- a/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
+++ b/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
@@ -59,14 +59,14 @@
     private int mObserverCount = 0;
 
     @GuardedBy("mLock")
-    private ArrayMap<String, InjectionSessionData> mPackageToSessionData = new ArrayMap<>();
+    private final ArrayMap<String, InjectionSessionData> mPackageToSessionData = new ArrayMap<>();
 
     /**
      * Mapping from camera ID to open camera app associations. Key is the camera id, value is the
      * information of the app's uid and package name.
      */
     @GuardedBy("mLock")
-    private ArrayMap<String, OpenCameraInfo> mAppsToBlockOnVirtualDevice = new ArrayMap<>();
+    private final ArrayMap<String, OpenCameraInfo> mAppsToBlockOnVirtualDevice = new ArrayMap<>();
 
     static class InjectionSessionData {
         public int appUid;
@@ -179,6 +179,15 @@
                 Slog.w(TAG, "Unexpected close with observers remaining: " + mObserverCount);
             }
         }
+        // Clean up camera injection sessions (if any).
+        synchronized (mLock) {
+            for (InjectionSessionData sessionData : mPackageToSessionData.values()) {
+                for (CameraInjectionSession session : sessionData.cameraIdToSession.values()) {
+                    session.close();
+                }
+            }
+            mPackageToSessionData.clear();
+        }
         mCameraManager.unregisterAvailabilityCallback(this);
     }
 
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 281a2ce..8b5b93e 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -1465,28 +1465,28 @@
     public int createVirtualDisplay(@NonNull VirtualDisplayConfig virtualDisplayConfig,
             @NonNull IVirtualDisplayCallback callback) {
         checkCallerIsDeviceOwner();
+
+        int displayId;
+        boolean showPointer;
+        boolean isTrustedDisplay;
         GenericWindowPolicyController gwpc;
         synchronized (mVirtualDeviceLock) {
             gwpc = createWindowPolicyControllerLocked(virtualDisplayConfig.getDisplayCategories());
-        }
-        int displayId;
-        displayId = mDisplayManagerInternal.createVirtualDisplay(virtualDisplayConfig, callback,
-                this, gwpc, mOwnerPackageName);
-        boolean isMirrorDisplay =
-                mDisplayManagerInternal.getDisplayIdToMirror(displayId) != Display.INVALID_DISPLAY;
-        gwpc.setDisplayId(displayId, isMirrorDisplay);
-        boolean isTrustedDisplay =
-                (mDisplayManagerInternal.getDisplayInfo(displayId).flags & Display.FLAG_TRUSTED)
-                        == Display.FLAG_TRUSTED;
-        if (!isTrustedDisplay) {
-            if (getDevicePolicy(POLICY_TYPE_CLIPBOARD) != DEVICE_POLICY_DEFAULT) {
-                throw new SecurityException("All displays must be trusted for devices with custom"
-                        + "clipboard policy.");
+            displayId = mDisplayManagerInternal.createVirtualDisplay(virtualDisplayConfig,
+                    callback, this, gwpc, mOwnerPackageName);
+            boolean isMirrorDisplay =
+                    mDisplayManagerInternal.getDisplayIdToMirror(displayId)
+                            != Display.INVALID_DISPLAY;
+            gwpc.setDisplayId(displayId, isMirrorDisplay);
+            isTrustedDisplay =
+                    (mDisplayManagerInternal.getDisplayInfo(displayId).flags & Display.FLAG_TRUSTED)
+                            == Display.FLAG_TRUSTED;
+            if (!isTrustedDisplay
+                    && getDevicePolicy(POLICY_TYPE_CLIPBOARD) != DEVICE_POLICY_DEFAULT) {
+                throw new SecurityException("All displays must be trusted for devices with "
+                        + "custom clipboard policy.");
             }
-        }
 
-        boolean showPointer;
-        synchronized (mVirtualDeviceLock) {
             if (mVirtualDisplays.contains(displayId)) {
                 gwpc.unregisterRunningAppsChangedListener(this);
                 throw new IllegalStateException(
@@ -1523,6 +1523,9 @@
     }
 
     private PowerManager.WakeLock createAndAcquireWakeLockForDisplay(int displayId) {
+        if (android.companion.virtualdevice.flags.Flags.deviceAwareDisplayPower()) {
+            return null;
+        }
         final long token = Binder.clearCallingIdentity();
         try {
             PowerManager powerManager = mContext.getSystemService(PowerManager.class);
@@ -1681,6 +1684,14 @@
         return mOwnerUid;
     }
 
+    long getDimDurationMillis() {
+        return mParams.getDimDuration().toMillis();
+    }
+
+    long getScreenOffTimeoutMillis() {
+        return mParams.getScreenOffTimeout().toMillis();
+    }
+
     @Override  // Binder call
     public int[] getDisplayIds() {
         synchronized (mVirtualDeviceLock) {
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index f87e3c3..6729231d 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -17,6 +17,7 @@
 package com.android.server.companion.virtual;
 
 import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_DEFAULT_DEVICE_CAMERA_ACCESS;
 import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;
 
 import static com.android.server.wm.ActivityInterceptorCallback.VIRTUAL_DEVICE_SERVICE_ORDERED_ID;
@@ -27,6 +28,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SuppressLint;
 import android.app.ActivityOptions;
+import android.app.compat.CompatChanges;
 import android.companion.AssociationInfo;
 import android.companion.AssociationRequest;
 import android.companion.CompanionDeviceManager;
@@ -41,11 +43,14 @@
 import android.companion.virtual.flags.Flags;
 import android.companion.virtual.sensor.VirtualSensor;
 import android.companion.virtualnative.IVirtualDeviceManagerNative;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
 import android.content.AttributionSource;
 import android.content.Context;
 import android.content.Intent;
 import android.hardware.display.DisplayManagerInternal;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.LocaleList;
@@ -88,7 +93,6 @@
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
-
 @SuppressLint("LongLogTag")
 public class VirtualDeviceManagerService extends SystemService {
 
@@ -101,6 +105,11 @@
             AssociationRequest.DEVICE_PROFILE_APP_STREAMING,
             AssociationRequest.DEVICE_PROFILE_NEARBY_DEVICE_STREAMING);
 
+    /** Enable default device camera access for apps running on virtual devices. */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    public static final long ENABLE_DEFAULT_DEVICE_CAMERA_ACCESS = 371173368L;
+
     /**
      * A virtual device association id corresponding to no CDM association.
      */
@@ -110,7 +119,7 @@
     private final VirtualDeviceManagerImpl mImpl;
     private final VirtualDeviceManagerNativeImpl mNativeImpl;
     private final VirtualDeviceManagerInternal mLocalService;
-    private VirtualDeviceLog mVirtualDeviceLog = new VirtualDeviceLog(getContext());
+    private final VirtualDeviceLog mVirtualDeviceLog = new VirtualDeviceLog(getContext());
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     private final PendingTrampolineMap mPendingTrampolines = new PendingTrampolineMap(mHandler);
 
@@ -236,7 +245,7 @@
         }
     }
 
-    void onCameraAccessBlocked(int appUid) {
+    private void onCameraAccessBlocked(int appUid) {
         ArrayList<VirtualDeviceImpl> virtualDevicesSnapshot = getVirtualDevicesSnapshot();
         for (int i = 0; i < virtualDevicesSnapshot.size(); i++) {
             VirtualDeviceImpl virtualDevice = virtualDevicesSnapshot.get(i);
@@ -248,8 +257,13 @@
         }
     }
 
-    CameraAccessController getCameraAccessController(UserHandle userHandle) {
-        if (Flags.streamCamera()) {
+    private CameraAccessController getCameraAccessController(UserHandle userHandle,
+            VirtualDeviceParams params, String callingPackage) {
+        if (CompatChanges.isChangeEnabled(ENABLE_DEFAULT_DEVICE_CAMERA_ACCESS, callingPackage,
+                userHandle)
+                && android.companion.virtualdevice.flags.Flags.defaultDeviceCameraAccessPolicy()
+                && (params.getDevicePolicy(POLICY_TYPE_DEFAULT_DEVICE_CAMERA_ACCESS)
+                    == DEVICE_POLICY_DEFAULT)) {
             return null;
         }
         int userId = userHandle.getIdentifier();
@@ -496,7 +510,8 @@
 
             final UserHandle userHandle = getCallingUserHandle();
             final CameraAccessController cameraAccessController =
-                    getCameraAccessController(userHandle);
+                    getCameraAccessController(userHandle, params,
+                            attributionSource.getPackageName());
             final int deviceId = sNextUniqueIndex.getAndIncrement();
             final Consumer<ArraySet<Integer>> runningAppsChangedCallback =
                     runningUids -> notifyRunningAppsChanged(deviceId, runningUids);
@@ -576,7 +591,6 @@
             }
         }
 
-
         @Override // Binder call
         public int getDeviceIdForDisplayId(int displayId) {
             if (displayId == Display.INVALID_DISPLAY || displayId == Display.DEFAULT_DISPLAY) {
@@ -911,6 +925,22 @@
         }
 
         @Override
+        public long getDimDurationMillisForDeviceId(int deviceId) {
+            synchronized (mVirtualDeviceManagerLock) {
+                VirtualDeviceImpl virtualDevice = mVirtualDevices.get(deviceId);
+                return virtualDevice == null ? -1 : virtualDevice.getDimDurationMillis();
+            }
+        }
+
+        @Override
+        public long getScreenOffTimeoutMillisForDeviceId(int deviceId) {
+            synchronized (mVirtualDeviceManagerLock) {
+                VirtualDeviceImpl virtualDevice = mVirtualDevices.get(deviceId);
+                return virtualDevice == null ? -1 : virtualDevice.getScreenOffTimeoutMillis();
+            }
+        }
+
+        @Override
         public boolean isValidVirtualDeviceId(int deviceId) {
             return mImpl.isValidVirtualDeviceId(deviceId);
         }
diff --git a/services/core/java/com/android/server/SerialService.java b/services/core/java/com/android/server/SerialService.java
index dbf144f..95ae11e 100644
--- a/services/core/java/com/android/server/SerialService.java
+++ b/services/core/java/com/android/server/SerialService.java
@@ -18,22 +18,30 @@
 
 import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.hardware.ISerialManager;
 import android.hardware.SerialManagerInternal;
 import android.os.ParcelFileDescriptor;
 import android.os.PermissionEnforcer;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.Preconditions;
 
 import java.io.File;
+import java.io.FileDescriptor;
 import java.util.ArrayList;
 import java.util.LinkedHashMap;
 import java.util.function.Supplier;
 
 @android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class SerialService extends ISerialManager.Stub {
+    private static final String TAG = "SerialService";
+
     private final Context mContext;
 
     @GuardedBy("mSerialPorts")
@@ -50,7 +58,7 @@
             final String[] serialPorts = getSerialPorts(context);
             for (String serialPort : serialPorts) {
                 mSerialPorts.put(serialPort, () -> {
-                    return native_open(serialPort);
+                    return tryOpen(serialPort);
                 });
             }
         }
@@ -130,5 +138,14 @@
         }
     };
 
-    private native ParcelFileDescriptor native_open(String path);
+    private static @Nullable ParcelFileDescriptor tryOpen(String path) {
+        try {
+            FileDescriptor fd = Os.open(path, OsConstants.O_RDWR | OsConstants.O_NOCTTY, 0);
+            return new ParcelFileDescriptor(fd);
+        } catch (ErrnoException e) {
+            Slog.e(TAG, "Could not open: " + path, e);
+            // We return null to preserve API semantics from earlier implementation variants.
+            return null;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/TradeInModeService.java b/services/core/java/com/android/server/TradeInModeService.java
index 9ad550b..70a0330 100644
--- a/services/core/java/com/android/server/TradeInModeService.java
+++ b/services/core/java/com/android/server/TradeInModeService.java
@@ -110,6 +110,8 @@
                     stopTradeInMode();
                 } else {
                     watchForSetupCompletion();
+                    watchForNetworkChange();
+                    watchForAccountsCreated();
                 }
             }
         }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 1c3569d..6a9bc5e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -5155,6 +5155,11 @@
         mContext.registerReceiver(new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
+                final String action = intent.getAction();
+                if (action == null) {
+                    return;
+                }
+
                 String[] pkgs = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
                 if (pkgs != null) {
                     for (String pkg : pkgs) {
@@ -11332,7 +11337,9 @@
             }
             pw.println("  mFgsStartTempAllowList:");
             final long currentTimeNow = System.currentTimeMillis();
-            final long elapsedRealtimeNow = SystemClock.elapsedRealtime();
+            final long tempAllowlistCurrentTime =
+                    com.android.server.deviceidle.Flags.useCpuTimeForTempAllowlist()
+                            ? SystemClock.uptimeMillis() : SystemClock.elapsedRealtime();
             mFgsStartTempAllowList.forEach((uid, entry) -> {
                 pw.print("    " + UserHandle.formatUid(uid) + ": ");
                 entry.second.dump(pw);
@@ -11340,7 +11347,7 @@
                 // Convert entry.mExpirationTime, which is an elapsed time since boot,
                 // to a time since epoch (i.e. System.currentTimeMillis()-based time.)
                 final long expirationInCurrentTime =
-                        currentTimeNow - elapsedRealtimeNow + entry.first;
+                        currentTimeNow - tempAllowlistCurrentTime + entry.first;
                 TimeUtils.dumpTimeWithDelta(pw, expirationInCurrentTime, currentTimeNow);
                 pw.println();
             });
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 416c110..da5b1fd 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -101,7 +101,7 @@
 import java.util.Set;
 import java.util.concurrent.Executor;
 
-public final class CachedAppOptimizer {
+public class CachedAppOptimizer {
 
     // Flags stored in the DeviceConfig API.
     @VisibleForTesting static final String KEY_USE_COMPACTION = "use_compaction";
diff --git a/services/core/java/com/android/server/am/FgsTempAllowList.java b/services/core/java/com/android/server/am/FgsTempAllowList.java
index c286556..5569b6d 100644
--- a/services/core/java/com/android/server/am/FgsTempAllowList.java
+++ b/services/core/java/com/android/server/am/FgsTempAllowList.java
@@ -29,7 +29,7 @@
 
 /**
  * List of keys that have expiration time.
- * If the expiration time is less than current elapsedRealtime, the key has expired.
+ * If the expiration time is less than current uptime, the key has expired.
  * Otherwise it is valid (or allowed).
  *
  * <p>This is used for both FGS-BG-start restriction, and FGS-while-in-use permissions check.</p>
@@ -42,7 +42,7 @@
     private static final int DEFAULT_MAX_SIZE = 100;
 
     /**
-     * The value is Pair type, Pair.first is the expirationTime(an elapsedRealtime),
+     * The value is Pair type, Pair.first is the expirationTime(in cpu uptime),
      * Pair.second is the optional information entry about this key.
      */
     private final SparseArray<Pair<Long, E>> mTempAllowList = new SparseArray<>();
@@ -82,7 +82,9 @@
             }
             // The temp allowlist should be a short list with only a few entries in it.
             // for a very large list, HashMap structure should be used.
-            final long now = SystemClock.elapsedRealtime();
+            final long now = com.android.server.deviceidle.Flags.useCpuTimeForTempAllowlist()
+                    ? SystemClock.uptimeMillis()
+                    : SystemClock.elapsedRealtime();
             final int size = mTempAllowList.size();
             if (size > mMaxSize) {
                 Slog.w(TAG_AM, "FgsTempAllowList length:" + size + " exceeds maxSize"
@@ -112,12 +114,15 @@
             final int index = mTempAllowList.indexOfKey(uid);
             if (index < 0) {
                 return null;
-            } else if (mTempAllowList.valueAt(index).first < SystemClock.elapsedRealtime()) {
+            }
+            final long timeNow = com.android.server.deviceidle.Flags.useCpuTimeForTempAllowlist()
+                    ? SystemClock.uptimeMillis()
+                    : SystemClock.elapsedRealtime();
+            if (mTempAllowList.valueAt(index).first < timeNow) {
                 mTempAllowList.removeAt(index);
                 return null;
-            } else {
-                return mTempAllowList.valueAt(index);
             }
+            return mTempAllowList.valueAt(index);
         }
     }
 
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index de3e2c9..a85f496 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -19,6 +19,7 @@
 import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL;
 import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL_IMPLICIT;
 import static android.app.ActivityManager.PROCESS_CAPABILITY_BFSL;
+import static android.app.ActivityManager.PROCESS_CAPABILITY_CPU_TIME;
 import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL;
 import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_CAMERA;
 import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION;
@@ -114,6 +115,7 @@
 import static com.android.server.am.ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ;
 import static com.android.server.am.ProcessList.PERSISTENT_SERVICE_ADJ;
 import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ;
+import static com.android.server.am.ProcessList.PREVIOUS_APP_MAX_ADJ;
 import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND;
 import static com.android.server.am.ProcessList.SCHED_GROUP_DEFAULT;
 import static com.android.server.am.ProcessList.SCHED_GROUP_FOREGROUND_WINDOW;
@@ -154,6 +156,7 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.Trace;
+import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Slog;
@@ -468,7 +471,6 @@
             }
             Process.setThreadPriority(tid, priority);
         }
-
     }
 
     // TODO(b/346822474): hook up global state usage.
@@ -498,7 +500,8 @@
     }
 
     OomAdjuster(ActivityManagerService service, ProcessList processList, ActiveUids activeUids,
-            ServiceThread adjusterThread, GlobalState globalState, Injector injector) {
+            ServiceThread adjusterThread, GlobalState globalState,
+            CachedAppOptimizer cachedAppOptimizer, Injector injector) {
         mService = service;
         mGlobalState = globalState;
         mInjector = injector;
@@ -507,7 +510,7 @@
         mActiveUids = activeUids;
 
         mConstants = mService.mConstants;
-        mCachedAppOptimizer = new CachedAppOptimizer(mService);
+        mCachedAppOptimizer = cachedAppOptimizer;
         mCacheOomRanker = new CacheOomRanker(service);
 
         mLogger = new OomAdjusterDebugLogger(this, mService.mConstants);
@@ -696,7 +699,7 @@
             // In case the app goes from non-cached to cached but it doesn't have other reachable
             // processes, its adj could be still unknown as of now, assign one.
             processes.add(app);
-            assignCachedAdjIfNecessary(processes);
+            applyLruAdjust(processes);
             applyOomAdjLSP(app, false, mInjector.getUptimeMillis(),
                     mInjector.getElapsedRealtimeMillis(), oomAdjReason);
         }
@@ -1086,7 +1089,7 @@
         }
         mProcessesInCycle.clear();
 
-        assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
+        applyLruAdjust(mProcessList.getLruProcessesLOSP());
 
         postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime, true);
 
@@ -1148,8 +1151,9 @@
     }
 
     @GuardedBy({"mService", "mProcLock"})
-    protected void assignCachedAdjIfNecessary(ArrayList<ProcessRecord> lruList) {
+    protected void applyLruAdjust(ArrayList<ProcessRecord> lruList) {
         final int numLru = lruList.size();
+        int nextPreviousAppAdj = PREVIOUS_APP_ADJ;
         if (mConstants.USE_TIERED_CACHED_ADJ) {
             final long now = mInjector.getUptimeMillis();
             int uiTargetAdj = 10;
@@ -1159,9 +1163,12 @@
                 ProcessRecord app = lruList.get(i);
                 final ProcessStateRecord state = app.mState;
                 final ProcessCachedOptimizerRecord opt = app.mOptRecord;
-                if (!app.isKilledByAm() && app.getThread() != null
-                        && (state.getCurAdj() >= UNKNOWN_ADJ
-                            || (state.hasShownUi() && state.getCurAdj() >= CACHED_APP_MIN_ADJ))) {
+                final int curAdj = state.getCurAdj();
+                if (PREVIOUS_APP_ADJ <= curAdj && curAdj <= PREVIOUS_APP_MAX_ADJ) {
+                    state.setCurAdj(nextPreviousAppAdj);
+                    nextPreviousAppAdj = Math.min(nextPreviousAppAdj + 1, PREVIOUS_APP_MAX_ADJ);
+                } else if (!app.isKilledByAm() && app.getThread() != null && (curAdj >= UNKNOWN_ADJ
+                            || (state.hasShownUi() && curAdj >= CACHED_APP_MIN_ADJ))) {
                     final ProcessServiceRecord psr = app.mServices;
                     int targetAdj = CACHED_APP_MIN_ADJ;
 
@@ -1228,10 +1235,13 @@
             for (int i = numLru - 1; i >= 0; i--) {
                 ProcessRecord app = lruList.get(i);
                 final ProcessStateRecord state = app.mState;
-                // If we haven't yet assigned the final cached adj
-                // to the process, do that now.
-                if (!app.isKilledByAm() && app.getThread() != null && state.getCurAdj()
-                    >= UNKNOWN_ADJ) {
+                final int curAdj = state.getCurAdj();
+                if (PREVIOUS_APP_ADJ <= curAdj && curAdj <= PREVIOUS_APP_MAX_ADJ) {
+                    state.setCurAdj(nextPreviousAppAdj);
+                    nextPreviousAppAdj = Math.min(nextPreviousAppAdj + 1, PREVIOUS_APP_MAX_ADJ);
+                } else if (!app.isKilledByAm() && app.getThread() != null
+                               && curAdj >= UNKNOWN_ADJ) {
+                    // If we haven't yet assigned the final cached adj to the process, do that now.
                     final ProcessServiceRecord psr = app.mServices;
                     switch (state.getCurProcState()) {
                         case PROCESS_STATE_LAST_ACTIVITY:
@@ -2582,6 +2592,7 @@
         }
 
         capability |= getDefaultCapability(app, procState);
+        capability |= getCpuCapability(app, now);
 
         // Procstates below BFGS should never have this capability.
         if (procState > PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
@@ -2724,8 +2735,12 @@
             if (app.mOptRecord.setShouldNotFreeze(true, dryRun,
                     app.mOptRecord.shouldNotFreezeReason()
                     | client.mOptRecord.shouldNotFreezeReason(), mAdjSeq)) {
-                // Bail out early, as we only care about the return value for a dryrun.
-                return true;
+                if (Flags.useCpuTimeCapability()) {
+                    // Do nothing, capability updated check will handle the dryrun output.
+                } else {
+                    // Bail out early, as we only care about the return value for a dryrun.
+                    return true;
+                }
             }
         }
 
@@ -2736,6 +2751,8 @@
         // we check the final procstate, and remove it if the procsate is below BFGS.
         capability |= getBfslCapabilityFromClient(client);
 
+        capability |= getCpuCapabilityFromClient(client);
+
         if (cr.notHasFlag(Context.BIND_WAIVE_PRIORITY)) {
             if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) {
                 capability |= cstate.getCurCapability();
@@ -2794,9 +2811,14 @@
                             app.mOptRecord.shouldNotFreezeReason()
                             | ProcessCachedOptimizerRecord
                             .SHOULD_NOT_FREEZE_REASON_BINDER_ALLOW_OOM_MANAGEMENT, mAdjSeq)) {
-                        // Bail out early, as we only care about the return value for a dryrun.
-                        return true;
+                        if (Flags.useCpuTimeCapability()) {
+                            // Do nothing, capability updated check will handle the dryrun output.
+                        } else {
+                            // Bail out early, as we only care about the return value for a dryrun.
+                            return true;
+                        }
                     }
+                    capability |= PROCESS_CAPABILITY_CPU_TIME;
                 }
                 // Not doing bind OOM management, so treat
                 // this guy more like a started service.
@@ -3038,9 +3060,14 @@
                         app.mOptRecord.shouldNotFreezeReason()
                         | ProcessCachedOptimizerRecord
                         .SHOULD_NOT_FREEZE_REASON_BIND_WAIVE_PRIORITY, mAdjSeq)) {
-                    // Bail out early, as we only care about the return value for a dryrun.
-                    return true;
+                    if (Flags.useCpuTimeCapability()) {
+                        // Do nothing, capability updated check will handle the dryrun output.
+                    } else {
+                        // Bail out early, as we only care about the return value for a dryrun.
+                        return true;
+                    }
                 }
+                capability |= PROCESS_CAPABILITY_CPU_TIME;
             }
         }
         if (cr.hasFlag(Context.BIND_TREAT_LIKE_ACTIVITY)) {
@@ -3093,9 +3120,24 @@
             capability &= ~PROCESS_CAPABILITY_BFSL;
         }
         if (!updated) {
-            updated = adj < prevRawAdj || procState < prevProcState || schedGroup > prevSchedGroup
-                || (capability != prevCapability
-                        && (capability & prevCapability) == prevCapability);
+            if (adj < prevRawAdj || procState < prevProcState || schedGroup > prevSchedGroup) {
+                updated = true;
+            }
+
+            if (Flags.useCpuTimeCapability()) {
+                if ((capability != prevCapability)
+                        && ((capability & prevCapability) == prevCapability)) {
+                    updated = true;
+                }
+            } else {
+                // Ignore PROCESS_CAPABILITY_CPU_TIME in capability comparison
+                final int curFiltered = capability & ~PROCESS_CAPABILITY_CPU_TIME;
+                final int prevFiltered = prevCapability & ~PROCESS_CAPABILITY_CPU_TIME;
+                if ((curFiltered != prevFiltered)
+                        && ((curFiltered & prevFiltered) == prevFiltered)) {
+                    updated = true;
+                }
+            }
         }
 
         if (dryRun) {
@@ -3171,6 +3213,8 @@
         // we check the final procstate, and remove it if the procsate is below BFGS.
         capability |= getBfslCapabilityFromClient(client);
 
+        capability |= getCpuCapabilityFromClient(client);
+
         if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) {
             // If the other app is cached for any reason, for purposes here
             // we are going to consider it empty.
@@ -3181,8 +3225,12 @@
             if (app.mOptRecord.setShouldNotFreeze(true, dryRun,
                     app.mOptRecord.shouldNotFreezeReason()
                     | client.mOptRecord.shouldNotFreezeReason(), mAdjSeq)) {
-                // Bail out early, as we only care about the return value for a dryrun.
-                return true;
+                if (Flags.useCpuTimeCapability()) {
+                    // Do nothing, capability updated check will handle the dryrun output.
+                } else {
+                    // Bail out early, as we only care about the return value for a dryrun.
+                    return true;
+                }
             }
         }
 
@@ -3258,10 +3306,25 @@
             capability &= ~PROCESS_CAPABILITY_BFSL;
         }
 
-        if (dryRun && (adj < prevRawAdj || procState < prevProcState || schedGroup > prevSchedGroup
-                || (capability != prevCapability
-                        && (capability & prevCapability) == prevCapability))) {
-            return true;
+        if (dryRun) {
+            if (adj < prevRawAdj || procState < prevProcState || schedGroup > prevSchedGroup) {
+                return true;
+            }
+
+            if (Flags.useCpuTimeCapability()) {
+                if ((capability != prevCapability)
+                        && ((capability & prevCapability) == prevCapability)) {
+                    return true;
+                }
+            } else {
+                // Ignore PROCESS_CAPABILITY_CPU_TIME in capability comparison
+                final int curFiltered = capability & ~PROCESS_CAPABILITY_CPU_TIME;
+                final int prevFiltered = prevCapability & ~PROCESS_CAPABILITY_CPU_TIME;
+                if ((curFiltered != prevFiltered)
+                        && ((curFiltered & prevFiltered) == prevFiltered)) {
+                    return true;
+                }
+            }
         }
 
         if (adj < prevRawAdj) {
@@ -3313,6 +3376,29 @@
         return baseCapabilities | networkCapabilities;
     }
 
+    private static int getCpuCapability(ProcessRecord app, long nowUptime) {
+        final UidRecord uidRec = app.getUidRecord();
+        if (uidRec != null && uidRec.isCurAllowListed()) {
+            // Process has user visible activities.
+            return PROCESS_CAPABILITY_CPU_TIME;
+        }
+        if (UserHandle.isCore(app.uid)) {
+            // Make sure all system components are not frozen.
+            return PROCESS_CAPABILITY_CPU_TIME;
+        }
+        if (app.mState.getCachedHasVisibleActivities()) {
+            // Process has user visible activities.
+            return PROCESS_CAPABILITY_CPU_TIME;
+        }
+        if (app.mServices.hasUndemotedShortForegroundService(nowUptime)) {
+            // It running a short fgs, just give it cpu time.
+            return PROCESS_CAPABILITY_CPU_TIME;
+        }
+        // TODO(b/370817323): Populate this method with all of the reasons to keep a process
+        //  unfrozen.
+        return 0;
+    }
+
     /**
      * @return the BFSL capability from a client (of a service binding or provider).
      */
@@ -3361,6 +3447,15 @@
     }
 
     /**
+     * @return the CPU capability from a client (of a service binding or provider).
+     */
+    private static int getCpuCapabilityFromClient(ProcessRecord client) {
+        // Just grant CPU capability every time
+        // TODO(b/370817323): Populate with reasons to not propagate cpu capability across bindings.
+        return client.mState.getCurCapability() & PROCESS_CAPABILITY_CPU_TIME;
+    }
+
+    /**
      * Checks if for the given app and client, there's a cycle that should skip over the client
      * for now or use partial values to evaluate the effect of the client binding.
      * @param app
@@ -3941,6 +4036,39 @@
         mCacheOomRanker.dump(pw);
     }
 
+    /**
+     * Return whether or not a process should be frozen.
+     */
+    boolean getFreezePolicy(ProcessRecord proc) {
+        // Reasons to not freeze:
+        if (Flags.useCpuTimeCapability()) {
+            if ((proc.mState.getCurCapability() & PROCESS_CAPABILITY_CPU_TIME) != 0) {
+                /// App is important enough (see {@link #getCpuCapability}) or bound by something
+                /// important enough to not be frozen.
+                return false;
+            }
+        } else {
+            // The CPU capability handling covers all setShouldNotFreeze paths. Must check
+            // shouldNotFreeze, if the CPU capability is not being used.
+            if (proc.mOptRecord.shouldNotFreeze()) {
+                return false;
+            }
+        }
+
+        if (proc.mOptRecord.isFreezeExempt()) {
+            return false;
+        }
+
+        // Reasons to freeze:
+        if (proc.mState.getCurAdj() >= FREEZER_CUTOFF_ADJ) {
+            // Oomscore is in a high enough state, it is safe to freeze.
+            return true;
+        }
+
+        // Default, do not freeze a process.
+        return false;
+    }
+
     @GuardedBy({"mService", "mProcLock"})
     void updateAppFreezeStateLSP(ProcessRecord app, @OomAdjReason int oomAdjReason,
             boolean immediate, int oldOomAdj) {
@@ -3955,43 +4083,44 @@
                     (state.getCurAdj() >= FREEZER_CUTOFF_ADJ ^ oldOomAdj >= FREEZER_CUTOFF_ADJ)
                     || oldOomAdj == UNKNOWN_ADJ;
             final boolean shouldNotFreezeChanged = opt.shouldNotFreezeAdjSeq() == mAdjSeq;
-            if ((oomAdjChanged || shouldNotFreezeChanged)
+            final boolean hasCpuCapability =
+                    (PROCESS_CAPABILITY_CPU_TIME & app.mState.getCurCapability())
+                            == PROCESS_CAPABILITY_CPU_TIME;
+            final boolean usedToHaveCpuCapability =
+                    (PROCESS_CAPABILITY_CPU_TIME & app.mState.getSetCapability())
+                            == PROCESS_CAPABILITY_CPU_TIME;
+            final boolean cpuCapabilityChanged = hasCpuCapability != usedToHaveCpuCapability;
+            if ((oomAdjChanged || shouldNotFreezeChanged || cpuCapabilityChanged)
                     && Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
                 Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                         CachedAppOptimizer.ATRACE_FREEZER_TRACK,
                         "updateAppFreezeStateLSP " + app.processName
+                        + " pid: " + app.getPid()
                         + " isFreezeExempt: " + opt.isFreezeExempt()
                         + " isFrozen: " + opt.isFrozen()
                         + " shouldNotFreeze: " + opt.shouldNotFreeze()
                         + " shouldNotFreezeReason: " + opt.shouldNotFreezeReason()
                         + " curAdj: " + state.getCurAdj()
                         + " oldOomAdj: " + oldOomAdj
-                        + " immediate: " + immediate);
+                        + " immediate: " + immediate
+                        + " cpuCapability: " + hasCpuCapability);
             }
         }
 
-        if (app.mOptRecord.isFreezeExempt()) {
-            return;
-        }
-
-        // if an app is already frozen and shouldNotFreeze becomes true, immediately unfreeze
-        if (opt.isFrozen() && opt.shouldNotFreeze()) {
-            mCachedAppOptimizer.unfreezeAppLSP(app,
-                    CachedAppOptimizer.getUnfreezeReasonCodeFromOomAdjReason(oomAdjReason));
-            return;
-        }
-
-        // Use current adjustment when freezing, set adjustment when unfreezing.
-        if (state.getCurAdj() >= FREEZER_CUTOFF_ADJ && !opt.isFrozen()
-                && !opt.shouldNotFreeze()) {
-            if (!immediate) {
-                mCachedAppOptimizer.freezeAppAsyncLSP(app);
-            } else {
+        if (getFreezePolicy(app)) {
+            // This process should be frozen.
+            if (immediate && !opt.isFrozen()) {
+                // And it will be frozen immediately.
                 mCachedAppOptimizer.freezeAppAsyncAtEarliestLSP(app);
+            } else if (!opt.isFrozen() || !opt.isPendingFreeze()) {
+                mCachedAppOptimizer.freezeAppAsyncLSP(app);
             }
-        } else if (state.getSetAdj() < FREEZER_CUTOFF_ADJ) {
-            mCachedAppOptimizer.unfreezeAppLSP(app,
-                    CachedAppOptimizer.getUnfreezeReasonCodeFromOomAdjReason(oomAdjReason));
+        } else {
+            // This process should not be frozen.
+            if (opt.isFrozen() || opt.isPendingFreeze()) {
+                mCachedAppOptimizer.unfreezeAppLSP(app,
+                        CachedAppOptimizer.getUnfreezeReasonCodeFromOomAdjReason(oomAdjReason));
+            }
         }
     }
 
@@ -4015,7 +4144,8 @@
         final int size = processes.size();
         for (int i = 0; i < size; i++) {
             ProcessRecord proc = processes.get(i);
-            mCachedAppOptimizer.unfreezeTemporarily(proc, reason);
+            mCachedAppOptimizer.unfreezeTemporarily(proc,
+                    CachedAppOptimizer.getUnfreezeReasonCodeFromOomAdjReason(reason));
         }
         processes.clear();
     }
diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
index e452c45..1b7e8f0 100644
--- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
+++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
@@ -54,6 +54,7 @@
 import static com.android.server.am.ProcessList.PERSISTENT_PROC_ADJ;
 import static com.android.server.am.ProcessList.PERSISTENT_SERVICE_ADJ;
 import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ;
+import static com.android.server.am.ProcessList.PREVIOUS_APP_MAX_ADJ;
 import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND;
 import static com.android.server.am.ProcessList.SERVICE_ADJ;
 import static com.android.server.am.ProcessList.SERVICE_B_ADJ;
@@ -757,8 +758,9 @@
 
     OomAdjusterModernImpl(ActivityManagerService service, ProcessList processList,
             ActiveUids activeUids, ServiceThread adjusterThread, GlobalState globalState,
-            Injector injector) {
-        super(service, processList, activeUids, adjusterThread, globalState, injector);
+            CachedAppOptimizer cachedAppOptimizer, Injector injector) {
+        super(service, processList, activeUids, adjusterThread, globalState, cachedAppOptimizer,
+                injector);
     }
 
     private final ProcessRecordNodes mProcessRecordProcStateNodes = new ProcessRecordNodes(
@@ -968,7 +970,7 @@
         mTmpOomAdjusterArgs.update(topApp, now, UNKNOWN_ADJ, oomAdjReason, null, true);
         computeConnectionsLSP();
 
-        assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
+        applyLruAdjust(mProcessList.getLruProcessesLOSP());
         postUpdateOomAdjInnerLSP(oomAdjReason, mActiveUids, now, nowElapsed, oldTime, true);
     }
 
@@ -1049,20 +1051,24 @@
         // Now traverse and compute the connections of processes with changed importance.
         computeConnectionsLSP();
 
-        boolean unassignedAdj = false;
+        boolean needLruAdjust = false;
         for (int i = 0, size = reachables.size(); i < size; i++) {
             final ProcessStateRecord state = reachables.get(i).mState;
             state.setReachable(false);
             state.setCompletedAdjSeq(mAdjSeq);
-            if (state.getCurAdj() >= UNKNOWN_ADJ) {
-                unassignedAdj = true;
+            final int curAdj = state.getCurAdj();
+            // Processes assigned the PREV oomscore will have a laddered oomscore with respect to
+            // their positions in the LRU list. i.e. prev+0, prev+1, prev+2, etc.
+            final boolean isPrevApp = PREVIOUS_APP_ADJ <= curAdj && curAdj <= PREVIOUS_APP_MAX_ADJ;
+            if (curAdj >= UNKNOWN_ADJ || (Flags.oomadjusterPrevLaddering() && isPrevApp)) {
+                needLruAdjust = true;
             }
         }
 
         // If all processes have an assigned adj, no need to calculate and assign cached adjs.
-        if (unassignedAdj) {
+        if (needLruAdjust) {
             // TODO: b/319163103 - optimize cache adj assignment to not require the whole lru list.
-            assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
+            applyLruAdjust(mProcessList.getLruProcessesLOSP());
         }
 
         // Repopulate any uid record that may have changed.
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index cdb0188..f86474f 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -225,6 +225,7 @@
     // UI flow such as clicking on a URI in the e-mail app to view in the browser,
     // and then pressing back to return to e-mail.
     public static final int PREVIOUS_APP_ADJ = 700;
+    public static final int PREVIOUS_APP_MAX_ADJ = Flags.oomadjusterPrevLaddering() ? 799 : 700;
 
     // This is a process holding the home application -- we want to try
     // avoiding killing it, even if it would normally be in the background,
diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java
index 5cb8b95..3644974 100644
--- a/services/core/java/com/android/server/am/ProcessServiceRecord.java
+++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java
@@ -256,18 +256,24 @@
         }
         // Now we need to look at all short-FGS within the process and see if all of them are
         // procstate-timed-out or not.
+        return !hasUndemotedShortForegroundService(nowUptime);
+    }
+
+    boolean hasUndemotedShortForegroundService(long nowUptime) {
         for (int i = mServices.size() - 1; i >= 0; i--) {
             final ServiceRecord sr = mServices.valueAt(i);
             if (!sr.isShortFgs() || !sr.hasShortFgsInfo()) {
                 continue;
             }
             if (sr.getShortFgsInfo().getProcStateDemoteTime() >= nowUptime) {
-                return false;
+                // This short fgs has not timed out yet.
+                return true;
             }
         }
-        return true;
+        return false;
     }
 
+
     int getReportedForegroundServiceTypes() {
         return mRepFgServiceTypes;
     }
diff --git a/services/core/java/com/android/server/am/ProcessStateController.java b/services/core/java/com/android/server/am/ProcessStateController.java
index 01468c6..5789922 100644
--- a/services/core/java/com/android/server/am/ProcessStateController.java
+++ b/services/core/java/com/android/server/am/ProcessStateController.java
@@ -29,6 +29,7 @@
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.ServiceThread;
 
 /**
@@ -44,13 +45,14 @@
     private final GlobalState mGlobalState = new GlobalState();
 
     private ProcessStateController(ActivityManagerService ams, ProcessList processList,
-            ActiveUids activeUids, ServiceThread handlerThread, OomAdjuster.Injector oomAdjInjector,
+            ActiveUids activeUids, ServiceThread handlerThread,
+            CachedAppOptimizer cachedAppOptimizer, OomAdjuster.Injector oomAdjInjector,
             boolean useOomAdjusterModernImpl) {
         mOomAdjuster = useOomAdjusterModernImpl
                 ? new OomAdjusterModernImpl(ams, processList, activeUids, handlerThread,
-                mGlobalState, oomAdjInjector)
+                mGlobalState, cachedAppOptimizer, oomAdjInjector)
                 : new OomAdjuster(ams, processList, activeUids, handlerThread, mGlobalState,
-                        oomAdjInjector);
+                        cachedAppOptimizer, oomAdjInjector);
     }
 
     /**
@@ -594,6 +596,7 @@
         private final ActiveUids mActiveUids;
 
         private ServiceThread mHandlerThread = null;
+        private CachedAppOptimizer mCachedAppOptimizer = null;
         private OomAdjuster.Injector mOomAdjInjector = null;
         private boolean mUseOomAdjusterModernImpl = false;
 
@@ -610,24 +613,38 @@
             if (mHandlerThread == null) {
                 mHandlerThread = OomAdjuster.createAdjusterThread();
             }
+            if (mCachedAppOptimizer == null) {
+                mCachedAppOptimizer = new CachedAppOptimizer(mAms);
+            }
             if (mOomAdjInjector == null) {
                 mOomAdjInjector = new OomAdjuster.Injector();
             }
             return new ProcessStateController(mAms, mProcessList, mActiveUids, mHandlerThread,
-                    mOomAdjInjector, mUseOomAdjusterModernImpl);
+                    mCachedAppOptimizer, mOomAdjInjector, mUseOomAdjusterModernImpl);
         }
 
         /**
          * For Testing Purposes. Set what thread OomAdjuster will offload tasks on to.
          */
+        @VisibleForTesting
         public Builder setHandlerThread(ServiceThread handlerThread) {
             mHandlerThread = handlerThread;
             return this;
         }
 
         /**
+         * For Testing Purposes. Set the CachedAppOptimzer used by OomAdjuster.
+         */
+        @VisibleForTesting
+        public Builder setCachedAppOptimizer(CachedAppOptimizer cachedAppOptimizer) {
+            mCachedAppOptimizer = cachedAppOptimizer;
+            return this;
+        }
+
+        /**
          * For Testing Purposes. Set an injector for OomAdjuster.
          */
+        @VisibleForTesting
         public Builder setOomAdjusterInjector(OomAdjuster.Injector injector) {
             mOomAdjInjector = injector;
             return this;
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index 56cfdfb..bde3ff6 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -242,4 +242,19 @@
     metadata {
         purpose: PURPOSE_BUGFIX
     }
-}
\ No newline at end of file
+}
+
+flag {
+    name: "oomadjuster_prev_laddering"
+    namespace: "system_performance"
+    is_fixed_read_only: true
+    description: "Add +X to the prev scores according to their positions in the process LRU list"
+    bug: "359912586"
+}
+
+flag {
+    name: "use_cpu_time_capability"
+    namespace: "backstage_power"
+    description: "Use PROCESS_CAPABILITY_CPU_TIME to control unfreeze state."
+    bug: "370817323"
+}
diff --git a/services/core/java/com/android/server/appbinding/AppBindingService.java b/services/core/java/com/android/server/appbinding/AppBindingService.java
index 5db6dc7..6ccb3ee 100644
--- a/services/core/java/com/android/server/appbinding/AppBindingService.java
+++ b/services/core/java/com/android/server/appbinding/AppBindingService.java
@@ -235,6 +235,9 @@
             }
 
             final String action = intent.getAction();
+            if (action == null) {
+                return;
+            }
 
             if (Intent.ACTION_USER_REMOVED.equals(action)) {
                 onUserRemoved(userId);
diff --git a/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java b/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java
index ae93991..0855815b 100644
--- a/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java
@@ -65,27 +65,31 @@
 
     @Override
     public boolean setGlobalRestriction(Object clientToken, int code, boolean restricted) {
+        boolean changed;
         if (restricted) {
             if (!mGlobalRestrictions.containsKey(clientToken)) {
                 mGlobalRestrictions.put(clientToken, new SparseBooleanArray());
             }
             SparseBooleanArray restrictedCodes = mGlobalRestrictions.get(clientToken);
             Objects.requireNonNull(restrictedCodes);
-            boolean changed = !restrictedCodes.get(code);
+            changed = !restrictedCodes.get(code);
             restrictedCodes.put(code, true);
-            return changed;
         } else {
             SparseBooleanArray restrictedCodes = mGlobalRestrictions.get(clientToken);
             if (restrictedCodes == null) {
                 return false;
             }
-            boolean changed = restrictedCodes.get(code);
+            changed = restrictedCodes.get(code);
             restrictedCodes.delete(code);
             if (restrictedCodes.size() == 0) {
                 mGlobalRestrictions.remove(clientToken);
             }
-            return changed;
         }
+
+        if (changed) {
+            AppOpsManager.invalidateAppOpModeCache();
+        }
+        return changed;
     }
 
     @Override
@@ -104,7 +108,11 @@
 
     @Override
     public boolean clearGlobalRestrictions(Object clientToken) {
-        return mGlobalRestrictions.remove(clientToken) != null;
+        boolean changed = mGlobalRestrictions.remove(clientToken) != null;
+        if (changed) {
+            AppOpsManager.invalidateAppOpModeCache();
+        }
+        return changed;
     }
 
     @RequiresPermission(anyOf = {
@@ -122,6 +130,9 @@
             changed |= putUserRestrictionExclusions(clientToken, userIds[i],
                     excludedPackageTags);
         }
+        if (changed) {
+            AppOpsManager.invalidateAppOpModeCache();
+        }
         return changed;
     }
 
@@ -191,6 +202,9 @@
         changed |= mUserRestrictions.remove(clientToken) != null;
         changed |= mUserRestrictionExcludedPackageTags.remove(clientToken) != null;
         notifyAllUserRestrictions(allUserRestrictedCodes);
+        if (changed) {
+            AppOpsManager.invalidateAppOpModeCache();
+        }
         return changed;
     }
 
@@ -244,6 +258,9 @@
             }
         }
 
+        if (changed) {
+            AppOpsManager.invalidateAppOpModeCache();
+        }
         return changed;
     }
 
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 702ad95..5e74d67 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -998,6 +998,7 @@
                     @Override
                     public void onUidModeChanged(int uid, int code, int mode,
                             String persistentDeviceId) {
+                        AppOpsManager.invalidateAppOpModeCache();
                         mHandler.sendMessage(PooledLambda.obtainMessage(
                                 AppOpsService::notifyOpChangedForAllPkgsInUid, AppOpsService.this,
                                 code, uid, false, persistentDeviceId));
@@ -1006,6 +1007,7 @@
                     @Override
                     public void onPackageModeChanged(String packageName, int userId, int code,
                             int mode) {
+                        AppOpsManager.invalidateAppOpModeCache();
                         mHandler.sendMessage(PooledLambda.obtainMessage(
                                 AppOpsService::notifyOpChangedForPkg, AppOpsService.this,
                                 packageName, code, mode, userId));
@@ -1032,6 +1034,11 @@
         // To migrate storageFile to recentAccessesFile, these reads must be called in this order.
         readRecentAccesses();
         mAppOpsCheckingService.readState();
+        // The system property used by the cache is created the first time it is written, that only
+        // happens inside invalidateCache().  Until the service calls invalidateCache() the property
+        // will not exist and the nonce will be UNSET.
+        AppOpsManager.invalidateAppOpModeCache();
+        AppOpsManager.disableAppOpModeCache();
     }
 
     public void publish() {
@@ -2830,6 +2837,13 @@
     @Override
     public int checkOperationRaw(int code, int uid, String packageName,
             @Nullable String attributionTag) {
+        if (Binder.getCallingPid() != Process.myPid()
+                && Flags.appopAccessTrackingLoggingEnabled()) {
+            FrameworkStatsLog.write(
+                    APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED, uid, code,
+                    APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__CHECK_OPERATION,
+                    false);
+        }
         return mCheckOpsDelegateDispatcher.checkOperation(code, uid, packageName, attributionTag,
                 Context.DEVICE_ID_DEFAULT, true /*raw*/);
     }
@@ -2837,6 +2851,13 @@
     @Override
     public int checkOperationRawForDevice(int code, int uid, @Nullable String packageName,
             @Nullable String attributionTag, int virtualDeviceId) {
+        if (Binder.getCallingPid() != Process.myPid()
+                && Flags.appopAccessTrackingLoggingEnabled()) {
+            FrameworkStatsLog.write(
+                    APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED, uid, code,
+                    APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__CHECK_OPERATION,
+                    false);
+        }
         return mCheckOpsDelegateDispatcher.checkOperation(code, uid, packageName, attributionTag,
                 virtualDeviceId, true /*raw*/);
     }
@@ -2894,8 +2915,14 @@
                 return AppOpsManager.MODE_IGNORED;
             }
         }
-        return checkOperationUnchecked(code, uid, resolvedPackageName, attributionTag,
-                virtualDeviceId, raw);
+
+        if (Flags.appopModeCachingEnabled()) {
+            return getAppOpMode(code, uid, resolvedPackageName, attributionTag, virtualDeviceId,
+                    raw, true);
+        } else {
+            return checkOperationUnchecked(code, uid, resolvedPackageName, attributionTag,
+                    virtualDeviceId, raw);
+        }
     }
 
     /**
@@ -2961,6 +2988,54 @@
         }
     }
 
+    /**
+     * This method unifies mode checking logic between checkOperationUnchecked and
+     * noteOperationUnchecked. It can replace those two methods once the flag is fully rolled out.
+     *
+     * @param isCheckOp This param is only used in user's op restriction. When checking if a package
+     *                  can bypass user's restriction we should account for attributionTag as well.
+     *                  But existing checkOp APIs don't accept attributionTag so we added a hack to
+     *                  skip attributionTag check for checkOp. After we add an overload of checkOp
+     *                  that accepts attributionTag we should remove this param.
+     */
+    private @Mode int getAppOpMode(int code, int uid, @NonNull String packageName,
+            @Nullable String attributionTag, int virtualDeviceId, boolean raw, boolean isCheckOp) {
+        PackageVerificationResult pvr;
+        try {
+            pvr = verifyAndGetBypass(uid, packageName, attributionTag);
+        } catch (SecurityException e) {
+            logVerifyAndGetBypassFailure(uid, e, "getAppOpMode");
+            return MODE_IGNORED;
+        }
+
+        if (isOpRestrictedDueToSuspend(code, packageName, uid)) {
+            return MODE_IGNORED;
+        }
+
+        synchronized (this) {
+            if (isOpRestrictedLocked(uid, code, packageName, attributionTag, virtualDeviceId,
+                    pvr.bypass, isCheckOp)) {
+                return MODE_IGNORED;
+            }
+            if (isOpAllowedForUid(uid)) {
+                return MODE_ALLOWED;
+            }
+
+            int switchCode = AppOpsManager.opToSwitch(code);
+            int rawUidMode = mAppOpsCheckingService.getUidMode(uid,
+                    getPersistentId(virtualDeviceId), switchCode);
+
+            if (rawUidMode != AppOpsManager.opToDefaultMode(switchCode)) {
+                return raw ? rawUidMode : evaluateForegroundMode(uid, switchCode, rawUidMode);
+            }
+
+            int rawPackageMode = mAppOpsCheckingService.getPackageMode(packageName, switchCode,
+                    UserHandle.getUserId(uid));
+            return raw ? rawPackageMode : evaluateForegroundMode(uid, switchCode, rawPackageMode);
+        }
+    }
+
+
     @Override
     public int checkAudioOperation(int code, int usage, int uid, String packageName) {
         return mCheckOpsDelegateDispatcher.checkAudioOperation(code, usage, uid, packageName);
@@ -3213,7 +3288,6 @@
         PackageVerificationResult pvr;
         try {
             pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
-            boolean wasNull = attributionTag == null;
             if (!pvr.isAttributionTagValid) {
                 attributionTag = null;
             }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 985155d..0cf55bb 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -493,7 +493,7 @@
     private static final int MSG_INIT_ADI_DEVICE_STATES = 103;
 
     private static final int MSG_INIT_INPUT_GAINS = 104;
-    private static final int MSG_SET_INPUT_GAIN_INDEX = 105;
+    private static final int MSG_APPLY_INPUT_GAIN_INDEX = 105;
     private static final int MSG_PERSIST_INPUT_GAIN_INDEX = 106;
 
     // end of messages handled under wakelock
@@ -1626,7 +1626,6 @@
                 new InputDeviceVolumeHelper(
                         mSettings,
                         mContentResolver,
-                        mSettingsLock,
                         System.INPUT_GAIN_INDEX_SETTINGS);
     }
 
@@ -5804,7 +5803,7 @@
             // to persist).
             sendMsg(
                     mAudioHandler,
-                    MSG_SET_INPUT_GAIN_INDEX,
+                    MSG_APPLY_INPUT_GAIN_INDEX,
                     SENDMSG_QUEUE,
                     /*arg1*/ index,
                     /*arg2*/ 0,
@@ -5813,22 +5812,22 @@
         }
     }
 
-    private void setInputGainIndexInt(@NonNull AudioDeviceAttributes ada, int index) {
+    private void onApplyInputGainIndex(@NonNull AudioDeviceAttributes ada, int index) {
         // TODO(b/364923030): call AudioSystem to apply input gain in native layer.
 
         // Post a persist input gain msg.
         sendMsg(
                 mAudioHandler,
                 MSG_PERSIST_INPUT_GAIN_INDEX,
-                SENDMSG_QUEUE,
-                /*arg1*/ index,
+                SENDMSG_REPLACE,
+                /*arg1*/ 0,
                 /*arg2*/ 0,
                 /*obj*/ ada,
                 PERSIST_DELAY);
     }
 
-    private void persistInputGainIndex(@NonNull AudioDeviceAttributes ada, int index) {
-        mInputDeviceVolumeHelper.persistInputGainIndex(ada, index);
+    private void onPersistInputGainIndex(@NonNull AudioDeviceAttributes ada) {
+        mInputDeviceVolumeHelper.persistInputGainIndex(ada);
     }
 
     /**
@@ -10213,12 +10212,12 @@
                     vgs.persistVolumeGroup(msg.arg1);
                     break;
 
-                case MSG_SET_INPUT_GAIN_INDEX:
-                    setInputGainIndexInt((AudioDeviceAttributes) msg.obj, msg.arg1);
+                case MSG_APPLY_INPUT_GAIN_INDEX:
+                    onApplyInputGainIndex((AudioDeviceAttributes) msg.obj, msg.arg1);
                     break;
 
                 case MSG_PERSIST_INPUT_GAIN_INDEX:
-                    persistInputGainIndex((AudioDeviceAttributes) msg.obj, msg.arg1);
+                    onPersistInputGainIndex((AudioDeviceAttributes) msg.obj);
                     break;
 
                 case MSG_PERSIST_RINGER_MODE:
diff --git a/services/core/java/com/android/server/audio/InputDeviceVolumeHelper.java b/services/core/java/com/android/server/audio/InputDeviceVolumeHelper.java
index d83dca6..d7d1ac9 100644
--- a/services/core/java/com/android/server/audio/InputDeviceVolumeHelper.java
+++ b/services/core/java/com/android/server/audio/InputDeviceVolumeHelper.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2024 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.
@@ -23,10 +23,10 @@
 import android.content.ContentResolver;
 import android.media.AudioDeviceAttributes;
 import android.media.AudioDeviceInfo;
-import android.media.AudioManager;
 import android.media.AudioSystem;
 import android.os.UserHandle;
 import android.util.IntArray;
+import android.util.Log;
 import android.util.SparseIntArray;
 
 import java.util.HashSet;
@@ -43,73 +43,59 @@
 
     private final SettingsAdapter mSettings;
     private final ContentResolver mContentResolver;
-    private final Object mSettingsLock;
     private final String mInputGainIndexSettingsName;
 
     // A map between device internal type (e.g. AudioSystem.DEVICE_IN_BUILTIN_MIC) to its input gain
     // index.
     private final SparseIntArray mInputGainIndexMap;
-    private final Set<Integer> mSupportedDeviceTypes;
+    private final Set<Integer> mSupportedDeviceTypes = new HashSet<>();
 
     InputDeviceVolumeHelper(
             SettingsAdapter settings,
             ContentResolver contentResolver,
-            Object settingsLock,
             String settingsName) {
         mSettings = settings;
         mContentResolver = contentResolver;
-        mSettingsLock = settingsLock;
         mInputGainIndexSettingsName = settingsName;
 
         IntArray internalDeviceTypes = new IntArray();
         int status = AudioSystem.getSupportedDeviceTypes(GET_DEVICES_INPUTS, internalDeviceTypes);
-        mInputGainIndexMap =
-                new SparseIntArray(
-                        status == AudioManager.SUCCESS
-                                ? internalDeviceTypes.size()
-                                : AudioSystem.DEVICE_IN_ALL_SET.size());
+        if (status != AudioSystem.SUCCESS) {
+            Log.e(TAG, "AudioSystem.getSupportedDeviceTypes(GET_DEVICES_INPUTS) failed. status:"
+                    + status);
+        }
 
-        if (status == AudioManager.SUCCESS) {
-            Set<Integer> supportedDeviceTypes = new HashSet<>();
-            for (int i = 0; i < internalDeviceTypes.size(); i++) {
-                supportedDeviceTypes.add(internalDeviceTypes.get(i));
-            }
-            mSupportedDeviceTypes = supportedDeviceTypes;
-        } else {
-            mSupportedDeviceTypes = AudioSystem.DEVICE_IN_ALL_SET;
+        // Note that in a rare case, if AudioSystem.getSupportedDeviceTypes call fails, both
+        // mInputGainIndexMap and mSupportedDeviceTypes will be empty.
+        mInputGainIndexMap = new SparseIntArray(internalDeviceTypes.size());
+        for (int i = 0; i < internalDeviceTypes.size(); i++) {
+            mSupportedDeviceTypes.add(internalDeviceTypes.get(i));
         }
 
         readSettings();
     }
 
-    public void readSettings() {
+    private void readSettings() {
         synchronized (InputDeviceVolumeHelper.class) {
             for (int inputDeviceType : mSupportedDeviceTypes) {
                 // Retrieve current input gain for device. If no input gain stored for current
                 // device, use default input gain.
-                int index;
-                if (!hasValidSettingsName()) {
-                    index = INDEX_DEFAULT;
-                } else {
-                    String name = getSettingNameForDevice(inputDeviceType);
-                    index =
-                            mSettings.getSystemIntForUser(
-                                    mContentResolver, name, INDEX_DEFAULT, UserHandle.USER_CURRENT);
-                }
+                String name = getSettingNameForDevice(inputDeviceType);
+                int index = name == null
+                        ? INDEX_DEFAULT
+                        : mSettings.getSystemIntForUser(
+                                mContentResolver, name, INDEX_DEFAULT, UserHandle.USER_CURRENT);
 
                 mInputGainIndexMap.put(inputDeviceType, getValidIndex(index));
             }
         }
     }
 
-    public boolean hasValidSettingsName() {
-        return mInputGainIndexSettingsName != null && !mInputGainIndexSettingsName.isEmpty();
-    }
-
-    public @Nullable String getSettingNameForDevice(int inputDeviceType) {
-        if (!hasValidSettingsName()) {
+    private @Nullable String getSettingNameForDevice(int inputDeviceType) {
+        if (mInputGainIndexSettingsName == null || mInputGainIndexSettingsName.isEmpty()) {
             return null;
         }
+
         final String suffix = AudioSystem.getInputDeviceName(inputDeviceType);
         if (suffix.isEmpty()) {
             return mInputGainIndexSettingsName;
@@ -158,29 +144,27 @@
         ensureValidInputDeviceType(inputDeviceType);
 
         int oldIndex;
-        synchronized (mSettingsLock) {
-            synchronized (InputDeviceVolumeHelper.class) {
-                oldIndex = getInputGainIndex(ada);
-                index = getValidIndex(index);
+        synchronized (InputDeviceVolumeHelper.class) {
+            oldIndex = getInputGainIndex(ada);
+            index = getValidIndex(index);
 
-                if (oldIndex == index) {
-                    return false;
-                }
-
-                mInputGainIndexMap.put(inputDeviceType, index);
-                return true;
+            if (oldIndex == index) {
+                return false;
             }
+
+            mInputGainIndexMap.put(inputDeviceType, index);
+            return true;
         }
     }
 
-    public void persistInputGainIndex(@NonNull AudioDeviceAttributes ada, int index) {
+    public void persistInputGainIndex(@NonNull AudioDeviceAttributes ada) {
         int inputDeviceType = AudioDeviceInfo.convertDeviceTypeToInternalInputDevice(ada.getType());
-        ensureValidInputDeviceType(inputDeviceType);
-
-        if (hasValidSettingsName()) {
+        String name = getSettingNameForDevice(inputDeviceType);
+        if (name != null) {
+            int index = getInputGainIndex(ada);
             mSettings.putSystemIntForUser(
                     mContentResolver,
-                    getSettingNameForDevice(inputDeviceType),
+                    name,
                     index,
                     UserHandle.USER_CURRENT);
         }
diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
index 6e38733..471b7b4 100644
--- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
+++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
@@ -167,6 +167,12 @@
      */
     public abstract int getDeviceIdForDisplayId(int displayId);
 
+    /** Returns the dim duration for the displays of the device with the given ID. */
+    public abstract long getDimDurationMillisForDeviceId(int deviceId);
+
+    /** Returns the screen off timeout of the displays of the device with the given ID. */
+    public abstract long getScreenOffTimeoutMillisForDeviceId(int deviceId);
+
     /**
      * Gets the persistent ID for the VirtualDevice with the given device ID.
      *
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 4211453..dabef84 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -51,6 +51,7 @@
 import android.os.IBinder;
 import android.os.IBinder.DeathRecipient;
 import android.os.Message;
+import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.util.ArrayMap;
@@ -64,6 +65,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.brightness.BrightnessUtils;
 import com.android.server.display.feature.DisplayManagerFlags;
 
 import java.io.PrintWriter;
@@ -323,7 +325,7 @@
         private int mWidth;
         private int mHeight;
         private int mDensityDpi;
-        private float mRequestedRefreshRate;
+        private final float mRequestedRefreshRate;
         private Surface mSurface;
         private DisplayDeviceInfo mInfo;
         private int mDisplayState;
@@ -332,7 +334,9 @@
         private Display.Mode mMode;
         private int mDisplayIdToMirror;
         private boolean mIsWindowManagerMirroring;
-        private DisplayCutout mDisplayCutout;
+        private final DisplayCutout mDisplayCutout;
+        private final float mDefaultBrightness;
+        private float mCurrentBrightness;
 
         public VirtualDisplayDevice(IBinder displayToken, IBinder appToken,
                 int ownerUid, String ownerPackageName, Surface surface, int flags,
@@ -349,6 +353,8 @@
             mDensityDpi = virtualDisplayConfig.getDensityDpi();
             mRequestedRefreshRate = virtualDisplayConfig.getRequestedRefreshRate();
             mDisplayCutout = virtualDisplayConfig.getDisplayCutout();
+            mDefaultBrightness = virtualDisplayConfig.getDefaultBrightness();
+            mCurrentBrightness = mDefaultBrightness;
             mMode = createMode(mWidth, mHeight, getRefreshRate());
             mSurface = surface;
             mFlags = flags;
@@ -457,6 +463,12 @@
                     mCallback.dispatchDisplayResumed();
                 }
             }
+            if (android.companion.virtualdevice.flags.Flags.deviceAwareDisplayPower()
+                    && BrightnessUtils.isValidBrightnessValue(brightnessState)
+                    && brightnessState != mCurrentBrightness) {
+                mCurrentBrightness = brightnessState;
+                mCallback.dispatchRequestedBrightnessChanged(mCurrentBrightness);
+            }
             return null;
         }
 
@@ -623,6 +635,10 @@
                     mInfo.state = mDisplayState;
                 }
 
+                mInfo.brightnessMinimum = PowerManager.BRIGHTNESS_MIN;
+                mInfo.brightnessMaximum = PowerManager.BRIGHTNESS_MAX;
+                mInfo.brightnessDefault = mDefaultBrightness;
+
                 mInfo.ownerUid = mOwnerUid;
                 mInfo.ownerPackageName = mOwnerPackageName;
 
@@ -642,6 +658,7 @@
         private static final int MSG_ON_DISPLAY_PAUSED = 0;
         private static final int MSG_ON_DISPLAY_RESUMED = 1;
         private static final int MSG_ON_DISPLAY_STOPPED = 2;
+        private static final int MSG_ON_REQUESTED_BRIGHTNESS_CHANGED = 3;
 
         private final IVirtualDisplayCallback mCallback;
 
@@ -663,6 +680,9 @@
                     case MSG_ON_DISPLAY_STOPPED:
                         mCallback.onStopped();
                         break;
+                    case MSG_ON_REQUESTED_BRIGHTNESS_CHANGED:
+                        mCallback.onRequestedBrightnessChanged((Float) msg.obj);
+                        break;
                 }
             } catch (RemoteException e) {
                 Slog.w(TAG, "Failed to notify listener of virtual display event.", e);
@@ -677,6 +697,11 @@
             sendEmptyMessage(MSG_ON_DISPLAY_RESUMED);
         }
 
+        public void dispatchRequestedBrightnessChanged(float brightness) {
+            Message msg = obtainMessage(MSG_ON_REQUESTED_BRIGHTNESS_CHANGED, brightness);
+            sendMessage(msg);
+        }
+
         public void dispatchDisplayStopped() {
             sendEmptyMessage(MSG_ON_DISPLAY_STOPPED);
         }
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 19305de..76e5ef0 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -605,7 +605,7 @@
     private ComponentName chooseDreamForUser(boolean doze, int userId) {
         if (doze) {
             ComponentName dozeComponent = getDozeComponent(userId);
-            return validateDream(dozeComponent) ? dozeComponent : null;
+            return validateDream(dozeComponent, userId) ? dozeComponent : null;
         }
 
         if (mSystemDreamComponent != null) {
@@ -616,11 +616,11 @@
         return dreams != null && dreams.length != 0 ? dreams[0] : null;
     }
 
-    private boolean validateDream(ComponentName component) {
+    private boolean validateDream(ComponentName component, int userId) {
         if (component == null) return false;
-        final ServiceInfo serviceInfo = getServiceInfo(component);
+        final ServiceInfo serviceInfo = getServiceInfo(component, userId);
         if (serviceInfo == null) {
-            Slog.w(TAG, "Dream " + component + " does not exist");
+            Slog.w(TAG, "Dream " + component + " does not exist on user " + userId);
             return false;
         } else if (serviceInfo.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP
                 && !BIND_DREAM_SERVICE.equals(serviceInfo.permission)) {
@@ -647,7 +647,7 @@
         List<ComponentName> validComponents = new ArrayList<>();
         if (components != null) {
             for (ComponentName component : components) {
-                if (validateDream(component)) {
+                if (validateDream(component, userId)) {
                     validComponents.add(component);
                 }
             }
@@ -718,9 +718,10 @@
         return userId == mainUserId;
     }
 
-    private ServiceInfo getServiceInfo(ComponentName name) {
+    private ServiceInfo getServiceInfo(ComponentName name, int userId) {
+        final Context userContext = mContext.createContextAsUser(UserHandle.of(userId), 0);
         try {
-            return name != null ? mContext.getPackageManager().getServiceInfo(name,
+            return name != null ? userContext.getPackageManager().getServiceInfo(name,
                     PackageManager.MATCH_DEBUG_TRIAGED_MISSING) : null;
         } catch (NameNotFoundException e) {
             return null;
@@ -813,7 +814,7 @@
 
     private void writePulseGestureEnabled() {
         ComponentName name = getDozeComponent();
-        boolean dozeEnabled = validateDream(name);
+        boolean dozeEnabled = validateDream(name, ActivityManager.getCurrentUser());
         LocalServices.getService(InputManagerInternal.class).setPulseGestureEnabled(dozeEnabled);
     }
 
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index b696c54..1b527da 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -273,13 +273,8 @@
     private class DelayedStandbyOnActiveSourceLostRunnable implements Runnable {
         @Override
         public void run() {
-            if (mService.getPowerManagerInternal().wasDeviceIdleFor(
-                    STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS)) {
+            if (!isActiveSource()) {
                 mService.standby();
-            } else {
-                mService.setAndBroadcastActiveSource(mService.getPhysicalAddress(),
-                        getDeviceInfo().getDeviceType(), Constants.ADDR_TV,
-                        "DelayedActiveSourceLostStandbyRunnable");
             }
         }
     }
diff --git a/services/core/java/com/android/server/input/AppLaunchShortcutManager.java b/services/core/java/com/android/server/input/AppLaunchShortcutManager.java
new file mode 100644
index 0000000..aef207f
--- /dev/null
+++ b/services/core/java/com/android/server/input/AppLaunchShortcutManager.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright 2024 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.input;
+
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.app.role.RoleManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.XmlResourceParser;
+import android.hardware.input.AppLaunchData;
+import android.hardware.input.InputGestureData;
+import android.hardware.input.KeyGestureEvent;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
+import android.util.LongSparseArray;
+import android.util.SparseArray;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+
+import com.android.internal.R;
+import com.android.internal.policy.IShortcutService;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Manages quick launch app shortcuts by parsing {@code bookmarks.xml} and intercepting the
+ * correct key combinations for the app shortcuts defined.
+ *
+ * Currently there are 2 ways of defining shortcuts:
+ * - Adding shortcuts to {@code bookmarks.xml}
+ * - Calling into {@code registerShortcutKey()}.
+ */
+final class AppLaunchShortcutManager {
+    private static final String TAG = "AppShortcutManager";
+
+    private static final String TAG_BOOKMARKS = "bookmarks";
+    private static final String TAG_BOOKMARK = "bookmark";
+
+    private static final String ATTRIBUTE_PACKAGE = "package";
+    private static final String ATTRIBUTE_CLASS = "class";
+    private static final String ATTRIBUTE_SHORTCUT = "shortcut";
+    private static final String ATTRIBUTE_CATEGORY = "category";
+    private static final String ATTRIBUTE_SHIFT = "shift";
+    private static final String ATTRIBUTE_ROLE = "role";
+
+    private static final int SHORTCUT_CODE_META_MASK =
+            KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON
+                    | KeyEvent.META_META_ON;
+
+    private LongSparseArray<IShortcutService> mShortcutKeyServices = new LongSparseArray<>();
+
+    /* Table of Application Launch keys.  Maps from key codes to intent categories.
+     *
+     * These are special keys that are used to launch particular kinds of applications,
+     * such as a web browser.  HID defines nearly a hundred of them in the Consumer (0x0C)
+     * usage page.  We don't support quite that many yet...
+     */
+    private static final SparseArray<String> sApplicationLaunchKeyCategories;
+    private static final SparseArray<String> sApplicationLaunchKeyRoles;
+    static {
+        sApplicationLaunchKeyRoles = new SparseArray<>();
+        sApplicationLaunchKeyCategories = new SparseArray<>();
+        sApplicationLaunchKeyRoles.append(
+                KeyEvent.KEYCODE_EXPLORER, RoleManager.ROLE_BROWSER);
+        sApplicationLaunchKeyCategories.append(
+                KeyEvent.KEYCODE_ENVELOPE, Intent.CATEGORY_APP_EMAIL);
+        sApplicationLaunchKeyCategories.append(
+                KeyEvent.KEYCODE_CONTACTS, Intent.CATEGORY_APP_CONTACTS);
+        sApplicationLaunchKeyCategories.append(
+                KeyEvent.KEYCODE_CALENDAR, Intent.CATEGORY_APP_CALENDAR);
+        sApplicationLaunchKeyCategories.append(
+                KeyEvent.KEYCODE_MUSIC, Intent.CATEGORY_APP_MUSIC);
+        sApplicationLaunchKeyCategories.append(
+                KeyEvent.KEYCODE_CALCULATOR, Intent.CATEGORY_APP_CALCULATOR);
+    }
+
+    private final Context mContext;
+    private boolean mSearchKeyShortcutPending = false;
+    private boolean mConsumeSearchKeyUp = true;
+    private final Map<InputGestureData.Trigger, InputGestureData> mBookmarks = new HashMap<>();
+
+    @SuppressLint("MissingPermission")
+    AppLaunchShortcutManager(Context context) {
+        mContext = context;
+    }
+
+    public void systemRunning() {
+        loadShortcuts();
+    }
+
+    private void loadShortcuts() {
+        try {
+            XmlResourceParser parser = mContext.getResources().getXml(R.xml.bookmarks);
+            XmlUtils.beginDocument(parser, TAG_BOOKMARKS);
+            KeyCharacterMap virtualKcm = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
+
+            while (true) {
+                XmlUtils.nextElement(parser);
+
+                if (parser.getEventType() == XmlPullParser.END_DOCUMENT) {
+                    break;
+                }
+
+                if (!TAG_BOOKMARK.equals(parser.getName())) {
+                    Log.w(TAG, "TAG_BOOKMARK not found");
+                    break;
+                }
+
+                String packageName = parser.getAttributeValue(null, ATTRIBUTE_PACKAGE);
+                String className = parser.getAttributeValue(null, ATTRIBUTE_CLASS);
+                String categoryName = parser.getAttributeValue(null, ATTRIBUTE_CATEGORY);
+                String shiftName = parser.getAttributeValue(null, ATTRIBUTE_SHIFT);
+                String roleName = parser.getAttributeValue(null, ATTRIBUTE_ROLE);
+
+                // TODO(b/358569822): Shift bookmarks to use keycode instead of shortcutChar
+                int keycode = KeyEvent.KEYCODE_UNKNOWN;
+                String shortcut = parser.getAttributeValue(null, ATTRIBUTE_SHORTCUT);
+                if (!TextUtils.isEmpty(shortcut)) {
+                    KeyEvent[] events = virtualKcm.getEvents(new char[]{shortcut.toLowerCase(
+                            Locale.ROOT).charAt(0)});
+                    // Single key press can generate the character
+                    if (events != null && events.length == 2) {
+                        keycode = events[0].getKeyCode();
+                    }
+                }
+                if (keycode == KeyEvent.KEYCODE_UNKNOWN) {
+                    Log.w(TAG, "Keycode required for bookmark with category=" + categoryName
+                            + " packageName=" + packageName + " className=" + className
+                            + " role=" + roleName + " shiftName=" + shiftName
+                            + " shortcut=" + shortcut);
+                    continue;
+                }
+
+                final boolean isShiftShortcut = (shiftName != null && shiftName.toLowerCase(
+                        Locale.ROOT).equals("true"));
+                AppLaunchData launchData = null;
+                if (!TextUtils.isEmpty(packageName) && !TextUtils.isEmpty(className)) {
+                    launchData = AppLaunchData.createLaunchDataForComponent(packageName, className);
+                } else if (!TextUtils.isEmpty(categoryName)) {
+                    launchData = AppLaunchData.createLaunchDataForCategory(categoryName);
+                } else if (!TextUtils.isEmpty(roleName)) {
+                    launchData = AppLaunchData.createLaunchDataForRole(roleName);
+                }
+                if (launchData != null) {
+                    Log.d(TAG, "adding shortcut " + launchData + "shift="
+                            + isShiftShortcut + " keycode=" + keycode);
+                    // All bookmarks are based on Action key
+                    int modifierState =
+                            KeyEvent.META_META_ON | (isShiftShortcut ? KeyEvent.META_SHIFT_ON : 0);
+                    InputGestureData bookmark = new InputGestureData.Builder()
+                            .setTrigger(InputGestureData.createKeyTrigger(keycode, modifierState))
+                            .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION)
+                            .setAppLaunchData(launchData)
+                            .build();
+                    mBookmarks.put(bookmark.getTrigger(), bookmark);
+                }
+            }
+        } catch (XmlPullParserException | IOException e) {
+            Log.e(TAG, "Got exception parsing bookmarks.", e);
+        }
+    }
+
+    public void registerShortcutKey(long shortcutCode, IShortcutService shortcutService)
+            throws RemoteException {
+        IShortcutService service = mShortcutKeyServices.get(shortcutCode);
+        if (service != null && service.asBinder().pingBinder()) {
+            throw new RemoteException("Key: " + shortcutCode + ", already exists.");
+        }
+
+        mShortcutKeyServices.put(shortcutCode, shortcutService);
+    }
+
+    /**
+     * Handle the shortcut to {@link IShortcutService}
+     * @param keyCode The key code of the event.
+     * @param metaState The meta key modifier state.
+     * @return True if invoked the shortcut, otherwise false.
+     */
+    private boolean handleShortcutService(int keyCode, int metaState) {
+        final long shortcutCodeMeta = metaState & SHORTCUT_CODE_META_MASK;
+        if (shortcutCodeMeta == 0) {
+            return false;
+        }
+        long shortcutCode = keyCode | (shortcutCodeMeta << Integer.SIZE);
+        IShortcutService shortcutService = mShortcutKeyServices.get(shortcutCode);
+        if (shortcutService != null) {
+            try {
+                shortcutService.notifyShortcutKeyPressed(shortcutCode);
+            } catch (RemoteException e) {
+                Log.w(TAG,
+                        "Shortcut key service not found, deleting shortcut code: " + shortcutCode);
+                mShortcutKeyServices.delete(shortcutCode);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Handle the shortcut to Launch application.
+     *
+     * @param keyEvent The key event.
+     */
+    @SuppressLint("MissingPermission")
+    @Nullable
+    private AppLaunchData interceptShortcut(KeyEvent keyEvent) {
+        final int keyCode = keyEvent.getKeyCode();
+        final int modifierState = keyEvent.getMetaState() & SHORTCUT_CODE_META_MASK;
+        // Shortcuts are invoked through Search+key, so intercept those here
+        // Any printing key that is chorded with Search should be consumed
+        // even if no shortcut was invoked.  This prevents text from being
+        // inadvertently inserted when using a keyboard that has built-in macro
+        // shortcut keys (that emit Search+x) and some of them are not registered.
+        if (mSearchKeyShortcutPending) {
+            KeyCharacterMap kcm = keyEvent.getKeyCharacterMap();
+            if (kcm != null && kcm.isPrintingKey(keyCode)) {
+                mConsumeSearchKeyUp = true;
+                mSearchKeyShortcutPending = false;
+            } else {
+                return null;
+            }
+        } else if (modifierState == 0) {
+            AppLaunchData appLaunchData = null;
+            // Handle application launch keys.
+            String role = sApplicationLaunchKeyRoles.get(keyCode);
+            String category = sApplicationLaunchKeyCategories.get(keyCode);
+            if (!TextUtils.isEmpty(role)) {
+                appLaunchData = AppLaunchData.createLaunchDataForRole(role);
+            } else if (!TextUtils.isEmpty(category)) {
+                appLaunchData = AppLaunchData.createLaunchDataForCategory(category);
+            }
+
+            return appLaunchData;
+        }
+
+        if (keyCode == KeyEvent.KEYCODE_UNKNOWN) {
+            return null;
+        }
+        InputGestureData gesture = mBookmarks.get(
+                InputGestureData.createKeyTrigger(keyCode, modifierState));
+        if (gesture == null) {
+            return null;
+        }
+        return gesture.getAction().appLaunchData();
+    }
+
+    /**
+     * Handle the shortcut from {@link KeyEvent}
+     *
+     * @param event Description of the key event.
+     */
+    public InterceptKeyResult interceptKey(KeyEvent event) {
+        if (event.getRepeatCount() != 0) {
+            return InterceptKeyResult.DO_NOTHING;
+        }
+
+        final int metaState = event.getModifiers();
+        final int keyCode = event.getKeyCode();
+        if (keyCode == KeyEvent.KEYCODE_SEARCH) {
+            if (event.getAction() == KeyEvent.ACTION_DOWN) {
+                mSearchKeyShortcutPending = true;
+                mConsumeSearchKeyUp = false;
+            } else {
+                mSearchKeyShortcutPending = false;
+                if (mConsumeSearchKeyUp) {
+                    mConsumeSearchKeyUp = false;
+                    return InterceptKeyResult.CONSUME_KEY;
+                }
+            }
+            return InterceptKeyResult.DO_NOTHING;
+        }
+
+        if (event.getAction() != KeyEvent.ACTION_DOWN) {
+            return InterceptKeyResult.DO_NOTHING;
+        }
+
+        // Intercept shortcuts defined in bookmarks or through application launch keycodes
+        AppLaunchData appLaunchData = interceptShortcut(event);
+
+        // TODO(b/358569822): Ideally shortcut service custom shortcuts should be either
+        //  migrated to bookmarks or customizable shortcut APIs.
+        if (appLaunchData == null && handleShortcutService(keyCode, metaState)) {
+            return InterceptKeyResult.CONSUME_KEY;
+        }
+
+        return new InterceptKeyResult(/* consumed =*/ false, appLaunchData);
+    }
+
+    /**
+     * @return a list of {@link InputGestureData} containing the application launch shortcuts parsed
+     * at boot time from {@code bookmarks.xml}.
+     */
+    public List<InputGestureData> getBookmarks() {
+        return new ArrayList<>(mBookmarks.values());
+    }
+
+    public void dump(IndentingPrintWriter ipw) {
+        ipw.println("AppLaunchShortcutManager:");
+        ipw.increaseIndent();
+        for (InputGestureData data : mBookmarks.values()) {
+            ipw.println(data);
+        }
+        ipw.decreaseIndent();
+    }
+
+    public record InterceptKeyResult(boolean consumed, @Nullable AppLaunchData appLaunchData) {
+        private static final InterceptKeyResult DO_NOTHING = new InterceptKeyResult(false, null);
+        private static final InterceptKeyResult CONSUME_KEY = new InterceptKeyResult(true, null);
+    }
+}
diff --git a/services/core/java/com/android/server/input/InputGestureManager.java b/services/core/java/com/android/server/input/InputGestureManager.java
index f4bd402..cf1cdaf 100644
--- a/services/core/java/com/android/server/input/InputGestureManager.java
+++ b/services/core/java/com/android/server/input/InputGestureManager.java
@@ -16,11 +16,21 @@
 
 package com.android.server.input;
 
+import static android.hardware.input.InputGestureData.createKeyTrigger;
+import static com.android.hardware.input.Flags.keyboardA11yShortcutControl;
+import static com.android.server.flags.Flags.newBugreportKeyboardShortcut;
+import static com.android.window.flags.Flags.enableMoveToNextDisplayShortcut;
+import static com.android.window.flags.Flags.enableTaskResizingKeyboardShortcuts;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.content.Context;
 import android.hardware.input.InputGestureData;
 import android.hardware.input.InputManager;
+import android.hardware.input.InputSettings;
+import android.hardware.input.KeyGestureEvent;
+import android.os.SystemProperties;
 import android.util.IndentingPrintWriter;
 import android.util.SparseArray;
 import android.view.KeyEvent;
@@ -29,15 +39,17 @@
 
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * A thread-safe component of {@link InputManagerService} responsible for managing pre-defined input
  * gestures and custom gestures defined by other system components using Input APIs.
  *
- * TODO(b/365064144): Add implementation to persist data, identify clashes with existing shortcuts.
+ * TODO(b/365064144): Add implementation to persist data.
  *
  */
 final class InputGestureManager {
@@ -47,13 +59,242 @@
             KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON
                     | KeyEvent.META_META_ON;
 
-    @GuardedBy("mCustomInputGestures")
+    private final Context mContext;
+
+    private static final Object mGestureLock = new Object();
+    @GuardedBy("mGestureLock")
     private final SparseArray<Map<InputGestureData.Trigger, InputGestureData>>
             mCustomInputGestures = new SparseArray<>();
 
+    @GuardedBy("mGestureLock")
+    private final Map<InputGestureData.Trigger, InputGestureData> mSystemShortcuts =
+            new HashMap<>();
+
+    @GuardedBy("mGestureLock")
+    private final Set<InputGestureData.Trigger> mBlockListedTriggers = new HashSet<>(Set.of(
+            createKeyTrigger(KeyEvent.KEYCODE_TAB, KeyEvent.META_ALT_ON),
+            createKeyTrigger(KeyEvent.KEYCODE_TAB, KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON),
+            createKeyTrigger(KeyEvent.KEYCODE_SPACE, KeyEvent.META_CTRL_ON),
+            createKeyTrigger(KeyEvent.KEYCODE_SPACE,
+                    KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON),
+            createKeyTrigger(KeyEvent.KEYCODE_Z,
+                    KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON),
+            createKeyTrigger(KeyEvent.KEYCODE_A, KeyEvent.META_CTRL_ON),
+            createKeyTrigger(KeyEvent.KEYCODE_C, KeyEvent.META_CTRL_ON),
+            createKeyTrigger(KeyEvent.KEYCODE_V, KeyEvent.META_CTRL_ON),
+            createKeyTrigger(KeyEvent.KEYCODE_X, KeyEvent.META_CTRL_ON),
+            createKeyTrigger(KeyEvent.KEYCODE_Z, KeyEvent.META_CTRL_ON),
+            createKeyTrigger(KeyEvent.KEYCODE_Y, KeyEvent.META_CTRL_ON)
+    ));
+
+    public InputGestureManager(Context context) {
+        mContext = context;
+    }
+
+    public void systemRunning() {
+        initSystemShortcuts();
+        blockListBookmarkedTriggers();
+    }
+
+    private void initSystemShortcuts() {
+        // Initialize all system shortcuts
+        List<InputGestureData> systemShortcuts = new ArrayList<>(List.of(
+                createKeyGesture(
+                        KeyEvent.KEYCODE_A,
+                        KeyEvent.META_META_ON,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT
+                ),
+                createKeyGesture(
+                        KeyEvent.KEYCODE_H,
+                        KeyEvent.META_META_ON,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_HOME
+                ),
+                createKeyGesture(
+                        KeyEvent.KEYCODE_ENTER,
+                        KeyEvent.META_META_ON,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_HOME
+                ),
+                createKeyGesture(
+                        KeyEvent.KEYCODE_I,
+                        KeyEvent.META_META_ON,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS
+                ),
+                createKeyGesture(
+                        KeyEvent.KEYCODE_L,
+                        KeyEvent.META_META_ON,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN
+                ),
+                createKeyGesture(
+                        KeyEvent.KEYCODE_N,
+                        KeyEvent.META_META_ON,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL
+                ),
+                createKeyGesture(
+                        KeyEvent.KEYCODE_N,
+                        KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES
+                ),
+                createKeyGesture(
+                        KeyEvent.KEYCODE_S,
+                        KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT
+                ),
+                createKeyGesture(
+                        KeyEvent.KEYCODE_DEL,
+                        KeyEvent.META_META_ON,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_BACK
+                ),
+                createKeyGesture(
+                        KeyEvent.KEYCODE_ESCAPE,
+                        KeyEvent.META_META_ON,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_BACK
+                ),
+                createKeyGesture(
+                        KeyEvent.KEYCODE_DPAD_UP,
+                        KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION
+                ),
+                createKeyGesture(
+                        KeyEvent.KEYCODE_DPAD_DOWN,
+                        KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE
+                ),
+                createKeyGesture(
+                        KeyEvent.KEYCODE_DPAD_LEFT,
+                        KeyEvent.META_META_ON,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_BACK
+                ),
+                createKeyGesture(
+                        KeyEvent.KEYCODE_DPAD_LEFT,
+                        KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT
+                ),
+                createKeyGesture(
+                        KeyEvent.KEYCODE_DPAD_LEFT,
+                        KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT
+                ),
+                createKeyGesture(
+                        KeyEvent.KEYCODE_DPAD_RIGHT,
+                        KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT
+                ),
+                createKeyGesture(
+                        KeyEvent.KEYCODE_DPAD_RIGHT,
+                        KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT
+                ),
+                createKeyGesture(
+                        KeyEvent.KEYCODE_SLASH,
+                        KeyEvent.META_META_ON,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER
+                ),
+                createKeyGesture(
+                        KeyEvent.KEYCODE_TAB,
+                        KeyEvent.META_META_ON,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS
+                )
+        ));
+        if (newBugreportKeyboardShortcut() && "1".equals(SystemProperties.get("ro.debuggable"))) {
+            systemShortcuts.add(createKeyGesture(
+                    KeyEvent.KEYCODE_DEL,
+                    KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
+                    KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT
+            ));
+        }
+        if (enableMoveToNextDisplayShortcut()) {
+            systemShortcuts.add(createKeyGesture(
+                    KeyEvent.KEYCODE_D,
+                    KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
+                    KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY
+            ));
+        }
+        if (keyboardA11yShortcutControl()) {
+            systemShortcuts.add(createKeyGesture(
+                    KeyEvent.KEYCODE_T,
+                    KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+                    KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK
+            ));
+            if (InputSettings.isAccessibilityBounceKeysFeatureEnabled()) {
+                systemShortcuts.add(createKeyGesture(
+                        KeyEvent.KEYCODE_3,
+                        KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS
+                ));
+            }
+            if (InputSettings.isAccessibilityMouseKeysFeatureFlagEnabled()) {
+                systemShortcuts.add(createKeyGesture(
+                        KeyEvent.KEYCODE_4,
+                        KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS
+                ));
+            }
+            if (InputSettings.isAccessibilityStickyKeysFeatureEnabled()) {
+                systemShortcuts.add(createKeyGesture(
+                        KeyEvent.KEYCODE_5,
+                        KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS
+                ));
+            }
+            if (InputSettings.isAccessibilitySlowKeysFeatureFlagEnabled()) {
+                systemShortcuts.add(createKeyGesture(
+                        KeyEvent.KEYCODE_6,
+                        KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS
+                ));
+            }
+            if (enableTaskResizingKeyboardShortcuts()) {
+                systemShortcuts.add(createKeyGesture(
+                        KeyEvent.KEYCODE_LEFT_BRACKET,
+                        KeyEvent.META_ALT_ON,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW
+                ));
+                systemShortcuts.add(createKeyGesture(
+                        KeyEvent.KEYCODE_RIGHT_BRACKET,
+                        KeyEvent.META_ALT_ON,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW
+                ));
+                systemShortcuts.add(createKeyGesture(
+                        KeyEvent.KEYCODE_EQUALS,
+                        KeyEvent.META_ALT_ON,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW
+                ));
+                systemShortcuts.add(createKeyGesture(
+                        KeyEvent.KEYCODE_MINUS,
+                        KeyEvent.META_ALT_ON,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE
+                ));
+            }
+        }
+        synchronized (mGestureLock) {
+            for (InputGestureData systemShortcut : systemShortcuts) {
+                mSystemShortcuts.put(systemShortcut.getTrigger(), systemShortcut);
+            }
+        }
+    }
+
+    private void blockListBookmarkedTriggers() {
+        synchronized (mGestureLock) {
+            InputManager im = Objects.requireNonNull(mContext.getSystemService(InputManager.class));
+            for (InputGestureData bookmark : im.getAppLaunchBookmarks()) {
+                mBlockListedTriggers.add(bookmark.getTrigger());
+            }
+        }
+    }
+
     @InputManager.CustomInputGestureResult
     public int addCustomInputGesture(int userId, InputGestureData newGesture) {
-        synchronized (mCustomInputGestures) {
+        synchronized (mGestureLock) {
+            if (mBlockListedTriggers.contains(newGesture.getTrigger())
+                    || mSystemShortcuts.containsKey(newGesture.getTrigger())) {
+                return InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE;
+            }
+            if (newGesture.getTrigger() instanceof InputGestureData.KeyTrigger keyTrigger) {
+                if (KeyEvent.isModifierKey(keyTrigger.getKeycode()) ||
+                        KeyEvent.isSystemKey(keyTrigger.getKeycode())) {
+                    return InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE;
+                }
+            }
             if (!mCustomInputGestures.contains(userId)) {
                 mCustomInputGestures.put(userId, new HashMap<>());
             }
@@ -69,7 +310,7 @@
 
     @InputManager.CustomInputGestureResult
     public int removeCustomInputGesture(int userId, InputGestureData data) {
-        synchronized (mCustomInputGestures) {
+        synchronized (mGestureLock) {
             if (!mCustomInputGestures.contains(userId)) {
                 return InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_DOES_NOT_EXIST;
             }
@@ -88,14 +329,14 @@
     }
 
     public void removeAllCustomInputGestures(int userId) {
-        synchronized (mCustomInputGestures) {
+        synchronized (mGestureLock) {
             mCustomInputGestures.remove(userId);
         }
     }
 
     @NonNull
     public List<InputGestureData> getCustomInputGestures(int userId) {
-        synchronized (mCustomInputGestures) {
+        synchronized (mGestureLock) {
             if (!mCustomInputGestures.contains(userId)) {
                 return List.of();
             }
@@ -109,7 +350,7 @@
         if (keyCode == KeyEvent.KEYCODE_UNKNOWN) {
             return null;
         }
-        synchronized (mCustomInputGestures) {
+        synchronized (mGestureLock) {
             Map<InputGestureData.Trigger, InputGestureData> customGestures =
                     mCustomInputGestures.get(userId);
             if (customGestures == null) {
@@ -120,10 +361,44 @@
         }
     }
 
+    @Nullable
+    public InputGestureData getSystemShortcutForKeyEvent(KeyEvent event) {
+        final int keyCode = event.getKeyCode();
+        if (keyCode == KeyEvent.KEYCODE_UNKNOWN) {
+            return null;
+        }
+        synchronized (mGestureLock) {
+            int modifierState = event.getMetaState() & KEY_GESTURE_META_MASK;
+            return mSystemShortcuts.get(InputGestureData.createKeyTrigger(keyCode, modifierState));
+        }
+    }
+
+    private static InputGestureData createKeyGesture(int keycode, int modifierState,
+            int keyGestureType) {
+        return new InputGestureData.Builder()
+                .setTrigger(createKeyTrigger(keycode, modifierState))
+                .setKeyGestureType(keyGestureType)
+                .build();
+    }
+
     public void dump(IndentingPrintWriter ipw) {
         ipw.println("InputGestureManager:");
         ipw.increaseIndent();
-        synchronized (mCustomInputGestures) {
+        synchronized (mGestureLock) {
+            ipw.println("System Shortcuts:");
+            ipw.increaseIndent();
+            for (InputGestureData systemShortcut : mSystemShortcuts.values()) {
+                ipw.println(systemShortcut);
+            }
+            ipw.decreaseIndent();
+            ipw.println("Blocklisted Triggers:");
+            ipw.increaseIndent();
+            for (InputGestureData.Trigger blocklistedTrigger : mBlockListedTriggers) {
+                ipw.println(blocklistedTrigger);
+            }
+            ipw.decreaseIndent();
+            ipw.println("Custom Gestures:");
+            ipw.increaseIndent();
             int size = mCustomInputGestures.size();
             for (int i = 0; i < size; i++) {
                 Map<InputGestureData.Trigger, InputGestureData> customGestures =
diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java
index 1c5bd59..265e453 100644
--- a/services/core/java/com/android/server/input/InputManagerInternal.java
+++ b/services/core/java/com/android/server/input/InputManagerInternal.java
@@ -23,11 +23,13 @@
 import android.hardware.display.DisplayViewport;
 import android.hardware.input.KeyGestureEvent;
 import android.os.IBinder;
+import android.os.RemoteException;
 import android.util.SparseBooleanArray;
 import android.view.InputChannel;
 import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.internal.inputmethod.InputMethodSubtypeHandle;
+import com.android.internal.policy.IShortcutService;
 
 import java.util.List;
 
@@ -278,6 +280,15 @@
      */
     public abstract void setAccessibilityPointerIconScaleFactor(int displayId, float scaleFactor);
 
+
+    /**
+     * Register shortcuts for input manager to dispatch.
+     * Shortcut code is packed as (metaState << Integer.SIZE) | keyCode
+     * @hide
+     */
+    public abstract void registerShortcutKey(long shortcutCode,
+            IShortcutService shortcutKeyReceiver) throws RemoteException;
+
     /**
      * Set whether the given input device can wake up the kernel from sleep
      * when it generates input events. By default, usually only internal (built-in)
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 26929f5..78e3b84 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -64,7 +64,6 @@
 import android.hardware.input.IStickyModifierStateListener;
 import android.hardware.input.ITabletModeChangedListener;
 import android.hardware.input.InputDeviceIdentifier;
-import android.hardware.input.InputGestureData;
 import android.hardware.input.InputManager;
 import android.hardware.input.InputSensorInfo;
 import android.hardware.input.InputSettings;
@@ -130,6 +129,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.inputmethod.InputMethodSubtypeHandle;
 import com.android.internal.os.SomeArgs;
+import com.android.internal.policy.IShortcutService;
 import com.android.internal.policy.KeyInterceptionInfo;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.Preconditions;
@@ -2313,6 +2313,12 @@
 
     // Native callback.
     @SuppressWarnings("unused")
+    private void notifyTouchpadThreeFingerTap() {
+        mKeyGestureController.handleTouchpadThreeFingerTap();
+    }
+
+    // Native callback.
+    @SuppressWarnings("unused")
     private void notifySwitch(long whenNanos, int switchValues, int switchMask) {
         if (DEBUG) {
             Slog.d(TAG, "notifySwitch: values=" + Integer.toHexString(switchValues)
@@ -3020,6 +3026,11 @@
         return mKeyGestureController.getCustomInputGestures(UserHandle.getCallingUserId());
     }
 
+    @Override
+    public AidlInputGestureData[] getAppLaunchBookmarks() {
+        return mKeyGestureController.getAppLaunchBookmarks();
+    }
+
     private void handleCurrentUserChanged(@UserIdInt int userId) {
         mCurrentUserId = userId;
         mKeyGestureController.setCurrentUserId(userId);
@@ -3564,6 +3575,12 @@
         }
 
         @Override
+        public void registerShortcutKey(long shortcutCode, IShortcutService shortcutKeyReceiver)
+                throws RemoteException {
+            mKeyGestureController.registerShortcutKey(shortcutCode, shortcutKeyReceiver);
+        }
+
+        @Override
         public boolean setKernelWakeEnabled(int deviceId, boolean enabled) {
             return mNative.setKernelWakeEnabled(deviceId, enabled);
         }
diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
index d1a6d3b..420db90 100644
--- a/services/core/java/com/android/server/input/InputSettingsObserver.java
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -143,6 +143,10 @@
             observer.accept("just booted");
         }
 
+        // TODO(b/365063048): add an entry to mObservers that calls this instead, once we have a
+        //   setting that can be observed.
+        updateTouchpadThreeFingerTapShortcutEnabled();
+
         configureUserActivityPokeInterval();
     }
 
@@ -205,6 +209,11 @@
         mNative.setTouchpadRightClickZoneEnabled(InputSettings.useTouchpadRightClickZone(mContext));
     }
 
+    private void updateTouchpadThreeFingerTapShortcutEnabled() {
+        mNative.setTouchpadThreeFingerTapShortcutEnabled(
+                InputSettings.useTouchpadThreeFingerTapShortcut(mContext));
+    }
+
     private void updateShowTouches() {
         mNative.setShowTouches(getBoolean(Settings.System.SHOW_TOUCHES, false));
     }
diff --git a/services/core/java/com/android/server/input/KeyGestureController.java b/services/core/java/com/android/server/input/KeyGestureController.java
index ebeef65..e0991ec 100644
--- a/services/core/java/com/android/server/input/KeyGestureController.java
+++ b/services/core/java/com/android/server/input/KeyGestureController.java
@@ -20,12 +20,8 @@
 import static android.content.pm.PackageManager.FEATURE_WATCH;
 import static android.view.WindowManagerPolicyConstants.FLAG_INTERACTIVE;
 
-import static com.android.hardware.input.Flags.keyboardA11yShortcutControl;
 import static com.android.hardware.input.Flags.useKeyGestureEventHandler;
 import static com.android.hardware.input.Flags.useKeyGestureEventHandlerMultiPressGestures;
-import static com.android.server.flags.Flags.newBugreportKeyboardShortcut;
-import static com.android.window.flags.Flags.enableMoveToNextDisplayShortcut;
-import static com.android.window.flags.Flags.enableTaskResizingKeyboardShortcuts;
 
 import android.annotation.BinderThread;
 import android.annotation.MainThread;
@@ -69,6 +65,7 @@
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.policy.IShortcutService;
 import com.android.server.policy.KeyCombinationManager;
 
 import java.util.ArrayDeque;
@@ -119,7 +116,8 @@
     private final int mSystemPid;
     private final KeyCombinationManager mKeyCombinationManager;
     private final SettingsObserver mSettingsObserver;
-    private final InputGestureManager mInputGestureManager = new InputGestureManager();
+    private final AppLaunchShortcutManager mAppLaunchShortcutManager;
+    private final InputGestureManager mInputGestureManager;
     private static final Object mUserLock = new Object();
     @UserIdInt
     @GuardedBy("mUserLock")
@@ -131,7 +129,6 @@
     private boolean mPendingHideRecentSwitcher;
 
     // Platform behaviors
-    private boolean mEnableBugReportKeyboardShortcut;
     private boolean mHasFeatureWatch;
     private boolean mHasFeatureLeanback;
 
@@ -178,13 +175,13 @@
         });
         mKeyCombinationManager = new KeyCombinationManager(mHandler);
         mSettingsObserver = new SettingsObserver(mHandler);
+        mAppLaunchShortcutManager = new AppLaunchShortcutManager(mContext);
+        mInputGestureManager = new InputGestureManager(mContext);
         initBehaviors();
         initKeyCombinationRules();
     }
 
     private void initBehaviors() {
-        mEnableBugReportKeyboardShortcut = "1".equals(SystemProperties.get("ro.debuggable"));
-
         PackageManager pm = mContext.getPackageManager();
         mHasFeatureWatch = pm.hasSystemFeature(FEATURE_WATCH);
         mHasFeatureLeanback = pm.hasSystemFeature(FEATURE_LEANBACK);
@@ -437,6 +434,8 @@
 
     public void systemRunning() {
         mSettingsObserver.observe();
+        mAppLaunchShortcutManager.systemRunning();
+        mInputGestureManager.systemRunning();
     }
 
     public boolean interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
@@ -513,15 +512,34 @@
             mPendingCapsLockToggle = false;
         }
 
+        // Handle App launch shortcuts
+        AppLaunchShortcutManager.InterceptKeyResult result = mAppLaunchShortcutManager.interceptKey(
+                event);
+        if (result.consumed()) {
+            return true;
+        }
+        if (result.appLaunchData() != null) {
+            return handleKeyGesture(deviceId, new int[]{keyCode}, metaState,
+                    KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+                    KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+                    focusedToken, /* flags = */0, result.appLaunchData());
+        }
+
+        // Handle system shortcuts
+        if (firstDown) {
+            InputGestureData systemShortcut = mInputGestureManager.getSystemShortcutForKeyEvent(
+                    event);
+            if (systemShortcut != null) {
+                return handleKeyGesture(deviceId, new int[]{keyCode}, metaState,
+                        systemShortcut.getAction().keyGestureType(),
+                        KeyGestureEvent.ACTION_GESTURE_COMPLETE,
+                        displayId, focusedToken, /* flags = */0,
+                        systemShortcut.getAction().appLaunchData());
+            }
+        }
+
+        // Handle system keys
         switch (keyCode) {
-            case KeyEvent.KEYCODE_A:
-                if (firstDown && event.isMetaPressed()) {
-                    return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON,
-                            KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT,
-                            KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, /* flags = */0, /* appLaunchData = */null);
-                }
-                break;
             case KeyEvent.KEYCODE_RECENT_APPS:
                 if (firstDown) {
                     handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
@@ -544,257 +562,6 @@
                             /* appLaunchData = */null);
                 }
                 return true;
-            case KeyEvent.KEYCODE_H:
-            case KeyEvent.KEYCODE_ENTER:
-                if (firstDown && event.isMetaPressed()) {
-                    return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON,
-                            KeyGestureEvent.KEY_GESTURE_TYPE_HOME,
-                            KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, /* flags = */0, /* appLaunchData = */null);
-                }
-                break;
-            case KeyEvent.KEYCODE_I:
-                if (firstDown && event.isMetaPressed()) {
-                    return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON,
-                            KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS,
-                            KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, /* flags = */0, /* appLaunchData = */null);
-                }
-                break;
-            case KeyEvent.KEYCODE_L:
-                if (firstDown && event.isMetaPressed()) {
-                    return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON,
-                            KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN,
-                            KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, /* flags = */0, /* appLaunchData = */null);
-                }
-                break;
-            case KeyEvent.KEYCODE_N:
-                if (firstDown && event.isMetaPressed()) {
-                    if (event.isCtrlPressed()) {
-                        return handleKeyGesture(deviceId, new int[]{keyCode},
-                                KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
-                                KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES,
-                                KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                                focusedToken, /* flags = */0, /* appLaunchData = */null);
-                    } else {
-                        return handleKeyGesture(deviceId, new int[]{keyCode},
-                                KeyEvent.META_META_ON,
-                                KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
-                                KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                                focusedToken, /* flags = */0, /* appLaunchData = */null);
-                    }
-                }
-                break;
-            case KeyEvent.KEYCODE_S:
-                if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
-                    return handleKeyGesture(deviceId, new int[]{keyCode},
-                            KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
-                            KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT,
-                            KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, /* flags = */0, /* appLaunchData = */null);
-                }
-                break;
-            case KeyEvent.KEYCODE_T:
-                if (keyboardA11yShortcutControl()) {
-                    if (firstDown && event.isMetaPressed() && event.isAltPressed()) {
-                        return handleKeyGesture(deviceId, new int[]{keyCode},
-                                KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
-                                KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK,
-                                KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                                focusedToken, /* flags = */0, /* appLaunchData = */null);
-                    }
-                }
-                break;
-            case KeyEvent.KEYCODE_3:
-                if (InputSettings.isAccessibilityBounceKeysFeatureEnabled()
-                        && keyboardA11yShortcutControl()) {
-                    if (firstDown && event.isMetaPressed() && event.isAltPressed()) {
-                        return handleKeyGesture(deviceId, new int[]{keyCode},
-                                KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
-                                KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS,
-                                KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                                focusedToken, /* flags = */0, /* appLaunchData = */null);
-                    }
-                }
-                break;
-            case KeyEvent.KEYCODE_4:
-                if (InputSettings.isAccessibilityMouseKeysFeatureFlagEnabled()
-                        && keyboardA11yShortcutControl()) {
-                    if (firstDown && event.isMetaPressed() && event.isAltPressed()) {
-                        return handleKeyGesture(deviceId, new int[]{keyCode},
-                                KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
-                                KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS,
-                                KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                                focusedToken, /* flags = */0, /* appLaunchData = */null);
-                    }
-                }
-                break;
-            case KeyEvent.KEYCODE_5:
-                if (InputSettings.isAccessibilityStickyKeysFeatureEnabled()
-                        && keyboardA11yShortcutControl()) {
-                    if (firstDown && event.isMetaPressed() && event.isAltPressed()) {
-                        return handleKeyGesture(deviceId, new int[]{keyCode},
-                                KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
-                                KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS,
-                                KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                                focusedToken, /* flags = */0, /* appLaunchData = */null);
-                    }
-                }
-                break;
-            case KeyEvent.KEYCODE_6:
-                if (InputSettings.isAccessibilitySlowKeysFeatureFlagEnabled()
-                        && keyboardA11yShortcutControl()) {
-                    if (firstDown && event.isMetaPressed() && event.isAltPressed()) {
-                        return handleKeyGesture(deviceId, new int[]{keyCode},
-                                KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
-                                KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS,
-                                KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                                focusedToken, /* flags = */0, /* appLaunchData = */null);
-                    }
-                }
-                break;
-            case KeyEvent.KEYCODE_DEL:
-                if (newBugreportKeyboardShortcut()) {
-                    if (firstDown && mEnableBugReportKeyboardShortcut && event.isMetaPressed()
-                            && event.isCtrlPressed()) {
-                        return handleKeyGesture(deviceId, new int[]{keyCode},
-                                KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
-                                KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT,
-                                KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                                focusedToken, /* flags = */0, /* appLaunchData = */null);
-                    }
-                }
-                // fall through
-            case KeyEvent.KEYCODE_ESCAPE:
-                if (firstDown && event.isMetaPressed()) {
-                    return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON,
-                            KeyGestureEvent.KEY_GESTURE_TYPE_BACK,
-                            KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, /* flags = */0, /* appLaunchData = */null);
-                }
-                break;
-            case KeyEvent.KEYCODE_DPAD_UP:
-                if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
-                    return handleKeyGesture(deviceId, new int[]{keyCode},
-                            KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
-                            KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION,
-                            KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, /* flags = */0, /* appLaunchData = */null);
-                }
-                break;
-            case KeyEvent.KEYCODE_DPAD_DOWN:
-                if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
-                    return handleKeyGesture(deviceId, new int[]{keyCode},
-                            KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
-                            KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE,
-                            KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, /* flags = */0, /* appLaunchData = */null);
-                }
-                break;
-            case KeyEvent.KEYCODE_DPAD_LEFT:
-                if (firstDown && event.isMetaPressed()) {
-                    if (event.isCtrlPressed()) {
-                        return handleKeyGesture(deviceId, new int[]{keyCode},
-                                KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
-                                KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT,
-                                KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                                focusedToken, /* flags = */0, /* appLaunchData = */null);
-                    } else if (event.isAltPressed()) {
-                        return handleKeyGesture(deviceId, new int[]{keyCode},
-                                KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
-                                KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT,
-                                KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                                focusedToken, /* flags = */0, /* appLaunchData = */null);
-                    } else {
-                        return handleKeyGesture(deviceId, new int[]{keyCode},
-                                KeyEvent.META_META_ON,
-                                KeyGestureEvent.KEY_GESTURE_TYPE_BACK,
-                                KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                                focusedToken, /* flags = */0, /* appLaunchData = */null);
-                    }
-                }
-                break;
-            case KeyEvent.KEYCODE_DPAD_RIGHT:
-                if (firstDown && event.isMetaPressed()) {
-                    if (event.isCtrlPressed()) {
-                        return handleKeyGesture(deviceId, new int[]{keyCode},
-                                KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
-                                KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT,
-                                KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                                focusedToken, /* flags = */0, /* appLaunchData = */null);
-                    } else if (event.isAltPressed()) {
-                        return handleKeyGesture(deviceId, new int[]{keyCode},
-                                KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
-                                KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT,
-                                KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                                focusedToken, /* flags = */0, /* appLaunchData = */null);
-                    }
-                }
-                break;
-            case KeyEvent.KEYCODE_D:
-                if (enableMoveToNextDisplayShortcut()) {
-                    if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
-                        return handleKeyGesture(deviceId, new int[]{keyCode},
-                                KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
-                                KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY,
-                                KeyGestureEvent.ACTION_GESTURE_COMPLETE,
-                                displayId, focusedToken, /* flags = */0, /* appLaunchData = */null);
-                    }
-                }
-                break;
-            case KeyEvent.KEYCODE_LEFT_BRACKET:
-                if (enableTaskResizingKeyboardShortcuts()) {
-                    if (firstDown && event.isAltPressed()) {
-                        return handleKeyGesture(deviceId, new int[]{keyCode},
-                                KeyEvent.META_ALT_ON,
-                                KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW,
-                                KeyGestureEvent.ACTION_GESTURE_COMPLETE,
-                                displayId, focusedToken, /* flags = */0, /* appLaunchData = */null);
-                    }
-                }
-                break;
-            case KeyEvent.KEYCODE_RIGHT_BRACKET:
-                if (enableTaskResizingKeyboardShortcuts()) {
-                    if (firstDown && event.isAltPressed()) {
-                        return handleKeyGesture(deviceId, new int[]{keyCode},
-                                KeyEvent.META_ALT_ON,
-                                KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW,
-                                KeyGestureEvent.ACTION_GESTURE_COMPLETE,
-                                displayId, focusedToken, /* flags = */0, /* appLaunchData = */null);
-                    }
-                }
-                break;
-            case KeyEvent.KEYCODE_EQUALS:
-                if (enableTaskResizingKeyboardShortcuts()) {
-                    if (firstDown && event.isAltPressed()) {
-                        return handleKeyGesture(deviceId, new int[]{keyCode},
-                                KeyEvent.META_ALT_ON,
-                                KeyGestureEvent.KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW,
-                                KeyGestureEvent.ACTION_GESTURE_COMPLETE,
-                                displayId, focusedToken, /* flags = */0, /* appLaunchData = */null);
-                    }
-                }
-                break;
-            case KeyEvent.KEYCODE_MINUS:
-                if (enableTaskResizingKeyboardShortcuts()) {
-                    if (firstDown && event.isAltPressed()) {
-                        return handleKeyGesture(deviceId, new int[]{keyCode},
-                                KeyEvent.META_ALT_ON,
-                                KeyGestureEvent.KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE,
-                                KeyGestureEvent.ACTION_GESTURE_COMPLETE,
-                                displayId, focusedToken, /* flags = */0, /* appLaunchData = */null);
-                    }
-                }
-                break;
-            case KeyEvent.KEYCODE_SLASH:
-                if (firstDown && event.isMetaPressed()) {
-                    return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON,
-                            KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER,
-                            KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, /* flags = */0, /* appLaunchData = */null);
-                }
-                break;
             case KeyEvent.KEYCODE_BRIGHTNESS_UP:
             case KeyEvent.KEYCODE_BRIGHTNESS_DOWN:
                 if (down) {
@@ -938,12 +705,7 @@
                 return true;
             case KeyEvent.KEYCODE_TAB:
                 if (firstDown) {
-                    if (event.isMetaPressed()) {
-                        return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON,
-                                KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS,
-                                KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                                focusedToken, /* flags = */0, /* appLaunchData = */null);
-                    } else if (!mPendingHideRecentSwitcher) {
+                    if (!mPendingHideRecentSwitcher) {
                         final int shiftlessModifiers =
                                 event.getModifiers() & ~KeyEvent.META_SHIFT_MASK;
                         if (KeyEvent.metaStateHasModifiers(
@@ -1004,6 +766,7 @@
                 return true;
         }
 
+        // Handle custom shortcuts
         if (firstDown) {
             InputGestureData customGesture;
             synchronized (mUserLock) {
@@ -1134,6 +897,13 @@
         handleKeyGesture(event, null /*focusedToken*/);
     }
 
+    public void handleTouchpadThreeFingerTap() {
+        // TODO(b/365063048): trigger a custom shortcut based on the three-finger tap.
+        if (DEBUG) {
+            Slog.d(TAG, "Three-finger touchpad tap occurred");
+        }
+    }
+
     @MainThread
     public void setCurrentUserId(@UserIdInt int userId) {
         synchronized (mUserLock) {
@@ -1251,6 +1021,16 @@
         return result;
     }
 
+    @BinderThread
+    public AidlInputGestureData[] getAppLaunchBookmarks() {
+        List<InputGestureData> bookmarks = mAppLaunchShortcutManager.getBookmarks();
+        AidlInputGestureData[] result = new AidlInputGestureData[bookmarks.size()];
+        for (int i = 0; i < bookmarks.size(); i++) {
+            result[i] = bookmarks.get(i).getAidlData();
+        }
+        return result;
+    }
+
     private void onKeyGestureEventListenerDied(int pid) {
         synchronized (mKeyGestureEventListenerRecords) {
             mKeyGestureEventListenerRecords.remove(pid);
@@ -1322,6 +1102,15 @@
         }
     }
 
+    public void registerShortcutKey(long shortcutCode, IShortcutService shortcutKeyReceiver)
+            throws RemoteException {
+        mAppLaunchShortcutManager.registerShortcutKey(shortcutCode, shortcutKeyReceiver);
+    }
+
+    public List<InputGestureData> getBookmarks() {
+        return mAppLaunchShortcutManager.getBookmarks();
+    }
+
     private void onKeyGestureHandlerDied(int pid) {
         synchronized (mKeyGestureHandlerRecords) {
             mKeyGestureHandlerRecords.remove(pid);
@@ -1464,6 +1253,7 @@
         }
         ipw.decreaseIndent();
         mKeyCombinationManager.dump("", ipw);
+        mAppLaunchShortcutManager.dump(ipw);
         mInputGestureManager.dump(ipw);
     }
 }
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 283fdea..8903c27 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -143,6 +143,8 @@
 
     void setTouchpadRightClickZoneEnabled(boolean enabled);
 
+    void setTouchpadThreeFingerTapShortcutEnabled(boolean enabled);
+
     void setShowTouches(boolean enabled);
 
     void setNonInteractiveDisplays(int[] displayIds);
@@ -427,6 +429,9 @@
         public native void setTouchpadRightClickZoneEnabled(boolean enabled);
 
         @Override
+        public native void setTouchpadThreeFingerTapShortcutEnabled(boolean enabled);
+
+        @Override
         public native void setShowTouches(boolean enabled);
 
         @Override
diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java
index 146ce17..46be1ca 100644
--- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java
+++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java
@@ -27,6 +27,7 @@
 import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.Xml;
+import android.view.inputmethod.Flags;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodSubtype;
 
@@ -59,8 +60,14 @@
     private static final String NODE_SUBTYPE = "subtype";
     private static final String NODE_IMI = "imi";
     private static final String ATTR_ID = "id";
+    /** The resource ID of the subtype name. */
     private static final String ATTR_LABEL = "label";
+    /** The untranslatable name of the subtype. */
     private static final String ATTR_NAME_OVERRIDE = "nameOverride";
+    /** The layout label string resource identifier. */
+    private static final String ATTR_LAYOUT_LABEL = "layoutLabel";
+    /** The non-localized layout label. */
+    private static final String ATTR_LAYOUT_LABEL_NON_LOCALIZED = "layoutLabelNonLocalized";
     private static final String ATTR_NAME_PK_LANGUAGE_TAG = "pkLanguageTag";
     private static final String ATTR_NAME_PK_LAYOUT_TYPE = "pkLayoutType";
     private static final String ATTR_ICON = "icon";
@@ -173,6 +180,11 @@
                     out.attributeInt(null, ATTR_ICON, subtype.getIconResId());
                     out.attributeInt(null, ATTR_LABEL, subtype.getNameResId());
                     out.attribute(null, ATTR_NAME_OVERRIDE, subtype.getNameOverride().toString());
+                    if (Flags.imeSwitcherRevampApi()) {
+                        out.attributeInt(null, ATTR_LAYOUT_LABEL, subtype.getLayoutLabelResource());
+                        out.attribute(null, ATTR_LAYOUT_LABEL_NON_LOCALIZED,
+                                subtype.getLayoutLabelNonLocalized().toString());
+                    }
                     ULocale pkLanguageTag = subtype.getPhysicalKeyboardHintLanguageTag();
                     if (pkLanguageTag != null) {
                         out.attribute(null, ATTR_NAME_PK_LANGUAGE_TAG,
@@ -264,6 +276,16 @@
                     final int label = parser.getAttributeInt(null, ATTR_LABEL);
                     final String untranslatableName = parser.getAttributeValue(null,
                             ATTR_NAME_OVERRIDE);
+                    final int layoutLabelResource;
+                    final String layoutLabelNonLocalized;
+                    if (Flags.imeSwitcherRevampApi()) {
+                        layoutLabelResource = parser.getAttributeInt(null, ATTR_LAYOUT_LABEL);
+                        layoutLabelNonLocalized = parser.getAttributeValue(null,
+                                ATTR_LAYOUT_LABEL_NON_LOCALIZED);
+                    } else {
+                        layoutLabelResource = 0;
+                        layoutLabelNonLocalized = null;
+                    }
                     final String pkLanguageTag = parser.getAttributeValue(null,
                             ATTR_NAME_PK_LANGUAGE_TAG);
                     final String pkLayoutType = parser.getAttributeValue(null,
@@ -283,6 +305,7 @@
                     final InputMethodSubtype.InputMethodSubtypeBuilder
                             builder = new InputMethodSubtype.InputMethodSubtypeBuilder()
                             .setSubtypeNameResId(label)
+                            .setLayoutLabelResource(layoutLabelResource)
                             .setPhysicalKeyboardHint(
                                     pkLanguageTag == null ? null : new ULocale(pkLanguageTag),
                                     pkLayoutType == null ? "" : pkLayoutType)
@@ -301,6 +324,9 @@
                     if (untranslatableName != null) {
                         builder.setSubtypeNameOverride(untranslatableName);
                     }
+                    if (layoutLabelNonLocalized != null) {
+                        builder.setLayoutLabelNonLocalized(layoutLabelNonLocalized);
+                    }
                     tempSubtypesArray.add(builder.build());
                 }
             }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 0104373..d8483f7 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -4969,7 +4969,7 @@
                     final var userData = getUserData(userId);
                     if (Flags.refactorInsetsController()) {
                         setImeVisibilityOnFocusedWindowClient(false, userData,
-                                null /* TODO(b329229469) check statsToken */);
+                                null /* TODO(b/353463205) check statsToken */);
                     } else {
 
                         hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow,
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java
index 6abd5aa..9f94905 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java
@@ -238,8 +238,8 @@
                 prevImeId = imeId;
             }
 
-            menuItems.add(new SubtypeItem(item.mImeName, item.mSubtypeName, item.mImi,
-                    item.mSubtypeIndex));
+            menuItems.add(new SubtypeItem(item.mImeName, item.mSubtypeName, item.mLayoutName,
+                    item.mImi, item.mSubtypeIndex));
         }
 
         return menuItems;
@@ -348,6 +348,13 @@
         @Nullable
         final CharSequence mSubtypeName;
 
+        /**
+         * The name of the subtype's layout, or {@code null} if this item doesn't have a subtype,
+         * or doesn't specify a layout.
+         */
+        @Nullable
+        private final CharSequence mLayoutName;
+
         /** The info of the input method. */
         @NonNull
         final InputMethodInfo mImi;
@@ -360,10 +367,11 @@
         final int mSubtypeIndex;
 
         SubtypeItem(@NonNull CharSequence imeName, @Nullable CharSequence subtypeName,
-                @NonNull InputMethodInfo imi,
+                @Nullable CharSequence layoutName, @NonNull InputMethodInfo imi,
                 @IntRange(from = NOT_A_SUBTYPE_INDEX) int subtypeIndex) {
             mImeName = imeName;
             mSubtypeName = subtypeName;
+            mLayoutName = layoutName;
             mImi = imi;
             mSubtypeIndex = subtypeIndex;
         }
@@ -521,6 +529,9 @@
             /** The name of the item. */
             @NonNull
             private final TextView mName;
+            /** The layout name. */
+            @NonNull
+            private final TextView mLayout;
             /** Indicator for the selected status of the item. */
             @NonNull
             private final ImageView mCheckmark;
@@ -536,6 +547,7 @@
 
                 mContainer = itemView;
                 mName = itemView.requireViewById(com.android.internal.R.id.text);
+                mLayout = itemView.requireViewById(com.android.internal.R.id.text2);
                 mCheckmark = itemView.requireViewById(com.android.internal.R.id.image);
 
                 mContainer.setOnClickListener((v) -> {
@@ -563,6 +575,9 @@
                 // Trigger the ellipsize marquee behaviour by selecting the name.
                 mName.setSelected(isSelected);
                 mName.setText(name);
+                mLayout.setText(item.mLayoutName);
+                mLayout.setVisibility(
+                        !TextUtils.isEmpty(item.mLayoutName) ? View.VISIBLE : View.GONE);
                 mCheckmark.setVisibility(isSelected ? View.VISIBLE : View.GONE);
             }
         }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
index 96b3e08..51b85e9 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
@@ -85,6 +85,12 @@
         public final CharSequence mImeName;
         @Nullable
         public final CharSequence mSubtypeName;
+        /**
+         * The subtype's layout name, or {@code null} if this item doesn't have a subtype,
+         * or doesn't specify a layout.
+         */
+        @Nullable
+        public final CharSequence mLayoutName;
         @NonNull
         public final InputMethodInfo mImi;
         /**
@@ -96,10 +102,11 @@
         public final boolean mIsSystemLanguage;
 
         ImeSubtypeListItem(@NonNull CharSequence imeName, @Nullable CharSequence subtypeName,
-                @NonNull InputMethodInfo imi, int subtypeIndex, @Nullable String subtypeLocale,
-                @NonNull String systemLocale) {
+                @Nullable CharSequence layoutName, @NonNull InputMethodInfo imi, int subtypeIndex,
+                @Nullable String subtypeLocale, @NonNull String systemLocale) {
             mImeName = imeName;
             mSubtypeName = subtypeName;
+            mLayoutName = layoutName;
             mImi = imi;
             mSubtypeIndex = subtypeIndex;
             if (TextUtils.isEmpty(subtypeLocale)) {
@@ -252,8 +259,11 @@
                                 subtype.overridesImplicitlyEnabledSubtype() ? null : subtype
                                         .getDisplayName(userAwareContext, imi.getPackageName(),
                                                 imi.getServiceInfo().applicationInfo);
-                        imList.add(new ImeSubtypeListItem(imeLabel,
-                                subtypeLabel, imi, j, subtype.getLocale(), mSystemLocaleStr));
+                        final var layoutName = subtype.overridesImplicitlyEnabledSubtype() ? null
+                                : subtype.getLayoutDisplayName(userAwareContext,
+                                        imi.getServiceInfo().applicationInfo);
+                        imList.add(new ImeSubtypeListItem(imeLabel, subtypeLabel, layoutName,
+                                imi, j, subtype.getLocale(), mSystemLocaleStr));
 
                         // Removing this subtype from enabledSubtypeSet because we no
                         // longer need to add an entry of this subtype to imList to avoid
@@ -262,8 +272,8 @@
                     }
                 }
             } else {
-                imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_INDEX, null,
-                        mSystemLocaleStr));
+                imList.add(new ImeSubtypeListItem(imeLabel, null /* subtypeName */,
+                        null /* layoutName */, imi, NOT_A_SUBTYPE_INDEX, null, mSystemLocaleStr));
             }
         }
         Collections.sort(imList);
@@ -311,13 +321,16 @@
                                 subtype.overridesImplicitlyEnabledSubtype() ? null : subtype
                                         .getDisplayName(userAwareContext, imi.getPackageName(),
                                                 imi.getServiceInfo().applicationInfo);
-                        imList.add(new ImeSubtypeListItem(imeLabel,
-                                subtypeLabel, imi, j, subtype.getLocale(), mSystemLocaleStr));
+                        final var layoutName = subtype.overridesImplicitlyEnabledSubtype() ? null
+                                : subtype.getLayoutDisplayName(userAwareContext,
+                                        imi.getServiceInfo().applicationInfo);
+                        imList.add(new ImeSubtypeListItem(imeLabel, subtypeLabel, layoutName,
+                                imi, j, subtype.getLocale(), mSystemLocaleStr));
                     }
                 }
             } else {
-                imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_INDEX, null,
-                        mSystemLocaleStr));
+                imList.add(new ImeSubtypeListItem(imeLabel, null /* subtypeName */,
+                        null /* layoutName */, imi, NOT_A_SUBTYPE_INDEX, null, mSystemLocaleStr));
             }
         }
         return imList;
diff --git a/services/core/java/com/android/server/integrity/parser/RuleMetadataParser.java b/services/core/java/com/android/server/integrity/parser/RuleMetadataParser.java
deleted file mode 100644
index e831e40..0000000
--- a/services/core/java/com/android/server/integrity/parser/RuleMetadataParser.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.parser;
-
-import android.annotation.Nullable;
-import android.util.Xml;
-
-import com.android.modules.utils.TypedXmlPullParser;
-import com.android.server.integrity.model.RuleMetadata;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-/** Helper class for parsing rule metadata. */
-public class RuleMetadataParser {
-
-    public static final String RULE_PROVIDER_TAG = "P";
-    public static final String VERSION_TAG = "V";
-
-    /** Parse the rule metadata from an input stream. */
-    @Nullable
-    public static RuleMetadata parse(InputStream inputStream)
-            throws XmlPullParserException, IOException {
-
-        String ruleProvider = "";
-        String version = "";
-
-        TypedXmlPullParser xmlPullParser = Xml.resolvePullParser(inputStream);
-
-        int eventType;
-        while ((eventType = xmlPullParser.next()) != XmlPullParser.END_DOCUMENT) {
-            if (eventType == XmlPullParser.START_TAG) {
-                String tag = xmlPullParser.getName();
-                switch (tag) {
-                    case RULE_PROVIDER_TAG:
-                        ruleProvider = xmlPullParser.nextText();
-                        break;
-                    case VERSION_TAG:
-                        version = xmlPullParser.nextText();
-                        break;
-                    default:
-                        throw new IllegalStateException("Unknown tag in metadata: " + tag);
-                }
-            }
-        }
-
-        return new RuleMetadata(ruleProvider, version);
-    }
-}
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
deleted file mode 100644
index 8ba5870..0000000
--- a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
+++ /dev/null
@@ -1,324 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.serializer;
-
-import static com.android.server.integrity.model.ComponentBitSize.ATOMIC_FORMULA_START;
-import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_END;
-import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_START;
-import static com.android.server.integrity.model.ComponentBitSize.CONNECTOR_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.DEFAULT_FORMAT_VERSION;
-import static com.android.server.integrity.model.ComponentBitSize.EFFECT_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.FORMAT_VERSION_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.INSTALLER_ALLOWED_BY_MANIFEST_START;
-import static com.android.server.integrity.model.ComponentBitSize.KEY_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.SEPARATOR_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS;
-import static com.android.server.integrity.model.IndexingFileConstants.END_INDEXING_KEY;
-import static com.android.server.integrity.model.IndexingFileConstants.INDEXING_BLOCK_SIZE;
-import static com.android.server.integrity.model.IndexingFileConstants.START_INDEXING_KEY;
-import static com.android.server.integrity.serializer.RuleIndexingDetails.APP_CERTIFICATE_INDEXED;
-import static com.android.server.integrity.serializer.RuleIndexingDetails.NOT_INDEXED;
-import static com.android.server.integrity.serializer.RuleIndexingDetails.PACKAGE_NAME_INDEXED;
-
-import android.content.integrity.AtomicFormula;
-import android.content.integrity.CompoundFormula;
-import android.content.integrity.InstallerAllowedByManifestFormula;
-import android.content.integrity.IntegrityFormula;
-import android.content.integrity.IntegrityUtils;
-import android.content.integrity.Rule;
-
-import com.android.internal.util.Preconditions;
-import com.android.server.integrity.model.BitOutputStream;
-import com.android.server.integrity.model.ByteTrackedOutputStream;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.charset.StandardCharsets;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.stream.Collectors;
-
-/** A helper class to serialize rules from the {@link Rule} model to Binary representation. */
-public class RuleBinarySerializer implements RuleSerializer {
-    static final int TOTAL_RULE_SIZE_LIMIT = 200000;
-    static final int INDEXED_RULE_SIZE_LIMIT = 100000;
-    static final int NONINDEXED_RULE_SIZE_LIMIT = 1000;
-
-    // Get the byte representation for a list of rules.
-    @Override
-    public byte[] serialize(List<Rule> rules, Optional<Integer> formatVersion)
-            throws RuleSerializeException {
-        try {
-            ByteArrayOutputStream rulesOutputStream = new ByteArrayOutputStream();
-            serialize(rules, formatVersion, rulesOutputStream, new ByteArrayOutputStream());
-            return rulesOutputStream.toByteArray();
-        } catch (Exception e) {
-            throw new RuleSerializeException(e.getMessage(), e);
-        }
-    }
-
-    // Get the byte representation for a list of rules, and write them to an output stream.
-    @Override
-    public void serialize(
-            List<Rule> rules,
-            Optional<Integer> formatVersion,
-            OutputStream rulesFileOutputStream,
-            OutputStream indexingFileOutputStream)
-            throws RuleSerializeException {
-        try {
-            if (rules == null) {
-                throw new IllegalArgumentException("Null rules cannot be serialized.");
-            }
-
-            if (rules.size() > TOTAL_RULE_SIZE_LIMIT) {
-                throw new IllegalArgumentException("Too many rules provided: " + rules.size());
-            }
-
-            // Determine the indexing groups and the order of the rules within each indexed group.
-            Map<Integer, Map<String, List<Rule>>> indexedRules =
-                    RuleIndexingDetailsIdentifier.splitRulesIntoIndexBuckets(rules);
-
-            // Validate the rule blocks are not larger than expected limits.
-            verifySize(indexedRules.get(PACKAGE_NAME_INDEXED), INDEXED_RULE_SIZE_LIMIT);
-            verifySize(indexedRules.get(APP_CERTIFICATE_INDEXED), INDEXED_RULE_SIZE_LIMIT);
-            verifySize(indexedRules.get(NOT_INDEXED), NONINDEXED_RULE_SIZE_LIMIT);
-
-            // Serialize the rules.
-            ByteTrackedOutputStream ruleFileByteTrackedOutputStream =
-                    new ByteTrackedOutputStream(rulesFileOutputStream);
-            serializeRuleFileMetadata(formatVersion, ruleFileByteTrackedOutputStream);
-            LinkedHashMap<String, Integer> packageNameIndexes =
-                    serializeRuleList(
-                            indexedRules.get(PACKAGE_NAME_INDEXED),
-                            ruleFileByteTrackedOutputStream);
-            LinkedHashMap<String, Integer> appCertificateIndexes =
-                    serializeRuleList(
-                            indexedRules.get(APP_CERTIFICATE_INDEXED),
-                            ruleFileByteTrackedOutputStream);
-            LinkedHashMap<String, Integer> unindexedRulesIndexes =
-                    serializeRuleList(
-                            indexedRules.get(NOT_INDEXED), ruleFileByteTrackedOutputStream);
-
-            // Serialize their indexes.
-            BitOutputStream indexingBitOutputStream = new BitOutputStream(indexingFileOutputStream);
-            serializeIndexGroup(packageNameIndexes, indexingBitOutputStream, /* isIndexed= */ true);
-            serializeIndexGroup(
-                    appCertificateIndexes, indexingBitOutputStream, /* isIndexed= */ true);
-            serializeIndexGroup(
-                    unindexedRulesIndexes, indexingBitOutputStream, /* isIndexed= */ false);
-            indexingBitOutputStream.flush();
-        } catch (Exception e) {
-            throw new RuleSerializeException(e.getMessage(), e);
-        }
-    }
-
-    private void verifySize(Map<String, List<Rule>> ruleListMap, int ruleSizeLimit) {
-        int totalRuleCount =
-                ruleListMap.values().stream()
-                        .map(list -> list.size())
-                        .collect(Collectors.summingInt(Integer::intValue));
-        if (totalRuleCount > ruleSizeLimit) {
-            throw new IllegalArgumentException(
-                    "Too many rules provided in the indexing group. Provided "
-                            + totalRuleCount
-                            + " limit "
-                            + ruleSizeLimit);
-        }
-    }
-
-    private void serializeRuleFileMetadata(
-            Optional<Integer> formatVersion, ByteTrackedOutputStream outputStream)
-            throws IOException {
-        int formatVersionValue = formatVersion.orElse(DEFAULT_FORMAT_VERSION);
-
-        BitOutputStream bitOutputStream = new BitOutputStream(outputStream);
-        bitOutputStream.setNext(FORMAT_VERSION_BITS, formatVersionValue);
-        bitOutputStream.flush();
-    }
-
-    private LinkedHashMap<String, Integer> serializeRuleList(
-            Map<String, List<Rule>> rulesMap, ByteTrackedOutputStream outputStream)
-            throws IOException {
-        Preconditions.checkArgument(
-                rulesMap != null, "serializeRuleList should never be called with null rule list.");
-
-        BitOutputStream bitOutputStream = new BitOutputStream(outputStream);
-        LinkedHashMap<String, Integer> indexMapping = new LinkedHashMap();
-        indexMapping.put(START_INDEXING_KEY, outputStream.getWrittenBytesCount());
-
-        List<String> sortedKeys = rulesMap.keySet().stream().sorted().collect(Collectors.toList());
-        int indexTracker = 0;
-        for (String key : sortedKeys) {
-            if (indexTracker >= INDEXING_BLOCK_SIZE) {
-                indexMapping.put(key, outputStream.getWrittenBytesCount());
-                indexTracker = 0;
-            }
-
-            for (Rule rule : rulesMap.get(key)) {
-                serializeRule(rule, bitOutputStream);
-                bitOutputStream.flush();
-                indexTracker++;
-            }
-        }
-        indexMapping.put(END_INDEXING_KEY, outputStream.getWrittenBytesCount());
-
-        return indexMapping;
-    }
-
-    private void serializeRule(Rule rule, BitOutputStream bitOutputStream) throws IOException {
-        if (rule == null) {
-            throw new IllegalArgumentException("Null rule can not be serialized");
-        }
-
-        // Start with a '1' bit to mark the start of a rule.
-        bitOutputStream.setNext();
-
-        serializeFormula(rule.getFormula(), bitOutputStream);
-        bitOutputStream.setNext(EFFECT_BITS, rule.getEffect());
-
-        // End with a '1' bit to mark the end of a rule.
-        bitOutputStream.setNext();
-    }
-
-    private void serializeFormula(IntegrityFormula formula, BitOutputStream bitOutputStream)
-            throws IOException {
-        if (formula instanceof AtomicFormula) {
-            serializeAtomicFormula((AtomicFormula) formula, bitOutputStream);
-        } else if (formula instanceof CompoundFormula) {
-            serializeCompoundFormula((CompoundFormula) formula, bitOutputStream);
-        } else if (formula instanceof InstallerAllowedByManifestFormula) {
-            bitOutputStream.setNext(SEPARATOR_BITS, INSTALLER_ALLOWED_BY_MANIFEST_START);
-        } else {
-            throw new IllegalArgumentException(
-                    String.format("Invalid formula type: %s", formula.getClass()));
-        }
-    }
-
-    private void serializeCompoundFormula(
-            CompoundFormula compoundFormula, BitOutputStream bitOutputStream) throws IOException {
-        if (compoundFormula == null) {
-            throw new IllegalArgumentException("Null compound formula can not be serialized");
-        }
-
-        bitOutputStream.setNext(SEPARATOR_BITS, COMPOUND_FORMULA_START);
-        bitOutputStream.setNext(CONNECTOR_BITS, compoundFormula.getConnector());
-        for (IntegrityFormula formula : compoundFormula.getFormulas()) {
-            serializeFormula(formula, bitOutputStream);
-        }
-        bitOutputStream.setNext(SEPARATOR_BITS, COMPOUND_FORMULA_END);
-    }
-
-    private void serializeAtomicFormula(
-            AtomicFormula atomicFormula, BitOutputStream bitOutputStream) throws IOException {
-        if (atomicFormula == null) {
-            throw new IllegalArgumentException("Null atomic formula can not be serialized");
-        }
-
-        bitOutputStream.setNext(SEPARATOR_BITS, ATOMIC_FORMULA_START);
-        bitOutputStream.setNext(KEY_BITS, atomicFormula.getKey());
-        if (atomicFormula.getTag() == AtomicFormula.STRING_ATOMIC_FORMULA_TAG) {
-            AtomicFormula.StringAtomicFormula stringAtomicFormula =
-                    (AtomicFormula.StringAtomicFormula) atomicFormula;
-            bitOutputStream.setNext(OPERATOR_BITS, AtomicFormula.EQ);
-            serializeStringValue(
-                    stringAtomicFormula.getValue(),
-                    stringAtomicFormula.getIsHashedValue(),
-                    bitOutputStream);
-        } else if (atomicFormula.getTag() == AtomicFormula.LONG_ATOMIC_FORMULA_TAG) {
-            AtomicFormula.LongAtomicFormula longAtomicFormula =
-                    (AtomicFormula.LongAtomicFormula) atomicFormula;
-            bitOutputStream.setNext(OPERATOR_BITS, longAtomicFormula.getOperator());
-            // TODO(b/147880712): Temporary hack until we support long values in bitOutputStream
-            long value = longAtomicFormula.getValue();
-            serializeIntValue((int) (value >>> 32), bitOutputStream);
-            serializeIntValue((int) value, bitOutputStream);
-        } else if (atomicFormula.getTag() == AtomicFormula.BOOLEAN_ATOMIC_FORMULA_TAG) {
-            AtomicFormula.BooleanAtomicFormula booleanAtomicFormula =
-                    (AtomicFormula.BooleanAtomicFormula) atomicFormula;
-            bitOutputStream.setNext(OPERATOR_BITS, AtomicFormula.EQ);
-            serializeBooleanValue(booleanAtomicFormula.getValue(), bitOutputStream);
-        } else {
-            throw new IllegalArgumentException(
-                    String.format("Invalid atomic formula type: %s", atomicFormula.getClass()));
-        }
-    }
-
-    private void serializeIndexGroup(
-            LinkedHashMap<String, Integer> indexes,
-            BitOutputStream bitOutputStream,
-            boolean isIndexed)
-            throws IOException {
-        // Output the starting location of this indexing group.
-        serializeStringValue(START_INDEXING_KEY, /* isHashedValue= */ false, bitOutputStream);
-        serializeIntValue(indexes.get(START_INDEXING_KEY), bitOutputStream);
-
-        // If the group is indexed, output the locations of the indexes.
-        if (isIndexed) {
-            for (Map.Entry<String, Integer> entry : indexes.entrySet()) {
-                if (!entry.getKey().equals(START_INDEXING_KEY)
-                        && !entry.getKey().equals(END_INDEXING_KEY)) {
-                    serializeStringValue(
-                            entry.getKey(), /* isHashedValue= */ false, bitOutputStream);
-                    serializeIntValue(entry.getValue(), bitOutputStream);
-                }
-            }
-        }
-
-        // Output the end location of this indexing group.
-        serializeStringValue(END_INDEXING_KEY, /*isHashedValue= */ false, bitOutputStream);
-        serializeIntValue(indexes.get(END_INDEXING_KEY), bitOutputStream);
-    }
-
-    private void serializeStringValue(
-            String value, boolean isHashedValue, BitOutputStream bitOutputStream)
-            throws IOException {
-        if (value == null) {
-            throw new IllegalArgumentException("String value can not be null.");
-        }
-        byte[] valueBytes = getBytesForString(value, isHashedValue);
-
-        bitOutputStream.setNext(isHashedValue);
-        bitOutputStream.setNext(VALUE_SIZE_BITS, valueBytes.length);
-        for (byte valueByte : valueBytes) {
-            bitOutputStream.setNext(/* numOfBits= */ 8, valueByte);
-        }
-    }
-
-    private void serializeIntValue(int value, BitOutputStream bitOutputStream) throws IOException {
-        bitOutputStream.setNext(/* numOfBits= */ 32, value);
-    }
-
-    private void serializeBooleanValue(boolean value, BitOutputStream bitOutputStream)
-            throws IOException {
-        bitOutputStream.setNext(value);
-    }
-
-    // Get the byte array for a value.
-    // If the value is not hashed, use its byte array form directly.
-    // If the value is hashed, get the raw form decoding of the value. All hashed values are
-    // hex-encoded. Serialized values are in raw form.
-    private static byte[] getBytesForString(String value, boolean isHashedValue) {
-        if (!isHashedValue) {
-            return value.getBytes(StandardCharsets.UTF_8);
-        }
-        return IntegrityUtils.getBytesFromHexDigest(value);
-    }
-}
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java
deleted file mode 100644
index 2cbd4ede..0000000
--- a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.serializer;
-
-import android.annotation.IntDef;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/** Holds the indexing type and indexing key of a given formula. */
-class RuleIndexingDetails {
-
-    static final int NOT_INDEXED = 0;
-    static final int PACKAGE_NAME_INDEXED = 1;
-    static final int APP_CERTIFICATE_INDEXED = 2;
-
-    static final String DEFAULT_RULE_KEY = "N/A";
-
-    /** Represents which indexed file the rule should be located. */
-    @IntDef(
-            value = {
-                    NOT_INDEXED,
-                    PACKAGE_NAME_INDEXED,
-                    APP_CERTIFICATE_INDEXED
-            })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface IndexType {
-    }
-
-    private @IndexType int mIndexType;
-    private String mRuleKey;
-
-    /** Constructor without a ruleKey for {@code NOT_INDEXED}. */
-    RuleIndexingDetails(@IndexType int indexType) {
-        this.mIndexType = indexType;
-        this.mRuleKey = DEFAULT_RULE_KEY;
-    }
-
-    /** Constructor with a ruleKey for indexed rules. */
-    RuleIndexingDetails(@IndexType int indexType, String ruleKey) {
-        this.mIndexType = indexType;
-        this.mRuleKey = ruleKey;
-    }
-
-    /** Returns the indexing type for the rule. */
-    @IndexType
-    public int getIndexType() {
-        return mIndexType;
-    }
-
-    /** Returns the identified rule key. */
-    public String getRuleKey() {
-        return mRuleKey;
-    }
-}
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java
deleted file mode 100644
index e723559..0000000
--- a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.serializer;
-
-import static com.android.server.integrity.serializer.RuleIndexingDetails.APP_CERTIFICATE_INDEXED;
-import static com.android.server.integrity.serializer.RuleIndexingDetails.NOT_INDEXED;
-import static com.android.server.integrity.serializer.RuleIndexingDetails.PACKAGE_NAME_INDEXED;
-
-import android.content.integrity.AtomicFormula;
-import android.content.integrity.CompoundFormula;
-import android.content.integrity.IntegrityFormula;
-import android.content.integrity.Rule;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-
-/** A helper class for identifying the indexing type and key of a given rule. */
-class RuleIndexingDetailsIdentifier {
-
-    /**
-     * Splits a given rule list into three indexing categories. Each rule category is returned as a
-     * TreeMap that is sorted by their indexing keys -- where keys correspond to package name for
-     * PACKAGE_NAME_INDEXED rules, app certificate for APP_CERTIFICATE_INDEXED rules and N/A for
-     * NOT_INDEXED rules.
-     */
-    public static Map<Integer, Map<String, List<Rule>>> splitRulesIntoIndexBuckets(
-            List<Rule> rules) {
-        if (rules == null) {
-            throw new IllegalArgumentException(
-                    "Index buckets cannot be created for null rule list.");
-        }
-
-        Map<Integer, Map<String, List<Rule>>> typeOrganizedRuleMap = new HashMap();
-        typeOrganizedRuleMap.put(NOT_INDEXED, new HashMap());
-        typeOrganizedRuleMap.put(PACKAGE_NAME_INDEXED, new HashMap<>());
-        typeOrganizedRuleMap.put(APP_CERTIFICATE_INDEXED, new HashMap<>());
-
-        // Split the rules into the appropriate indexed pattern. The Tree Maps help us to keep the
-        // entries sorted by their index key.
-        for (Rule rule : rules) {
-            RuleIndexingDetails indexingDetails;
-            try {
-                indexingDetails = getIndexingDetails(rule.getFormula());
-            } catch (Exception e) {
-                throw new IllegalArgumentException(
-                        String.format("Malformed rule identified. [%s]", rule.toString()));
-            }
-
-            int ruleIndexType = indexingDetails.getIndexType();
-            String ruleKey = indexingDetails.getRuleKey();
-
-            if (!typeOrganizedRuleMap.get(ruleIndexType).containsKey(ruleKey)) {
-                typeOrganizedRuleMap.get(ruleIndexType).put(ruleKey, new ArrayList());
-            }
-
-            typeOrganizedRuleMap.get(ruleIndexType).get(ruleKey).add(rule);
-        }
-
-        return typeOrganizedRuleMap;
-    }
-
-    private static RuleIndexingDetails getIndexingDetails(IntegrityFormula formula) {
-        switch (formula.getTag()) {
-            case IntegrityFormula.COMPOUND_FORMULA_TAG:
-                return getIndexingDetailsForCompoundFormula((CompoundFormula) formula);
-            case IntegrityFormula.STRING_ATOMIC_FORMULA_TAG:
-                return getIndexingDetailsForStringAtomicFormula(
-                        (AtomicFormula.StringAtomicFormula) formula);
-            case IntegrityFormula.LONG_ATOMIC_FORMULA_TAG:
-            case IntegrityFormula.INSTALLER_ALLOWED_BY_MANIFEST_FORMULA_TAG:
-            case IntegrityFormula.BOOLEAN_ATOMIC_FORMULA_TAG:
-                // Package name and app certificate related formulas are string atomic formulas.
-                return new RuleIndexingDetails(NOT_INDEXED);
-            default:
-                throw new IllegalArgumentException(
-                        String.format("Invalid formula tag type: %s", formula.getTag()));
-        }
-    }
-
-    private static RuleIndexingDetails getIndexingDetailsForCompoundFormula(
-            CompoundFormula compoundFormula) {
-        int connector = compoundFormula.getConnector();
-        List<IntegrityFormula> formulas = compoundFormula.getFormulas();
-
-        switch (connector) {
-            case CompoundFormula.AND:
-            case CompoundFormula.OR:
-                // If there is a package name related atomic rule, return package name indexed.
-                Optional<RuleIndexingDetails> packageNameRule =
-                        formulas.stream()
-                                .map(formula -> getIndexingDetails(formula))
-                                .filter(ruleIndexingDetails -> ruleIndexingDetails.getIndexType()
-                                        == PACKAGE_NAME_INDEXED)
-                                .findAny();
-                if (packageNameRule.isPresent()) {
-                    return packageNameRule.get();
-                }
-
-                // If there is an app certificate related atomic rule but no package name related
-                // atomic rule, return app certificate indexed.
-                Optional<RuleIndexingDetails> appCertificateRule =
-                        formulas.stream()
-                                .map(formula -> getIndexingDetails(formula))
-                                .filter(ruleIndexingDetails -> ruleIndexingDetails.getIndexType()
-                                        == APP_CERTIFICATE_INDEXED)
-                                .findAny();
-                if (appCertificateRule.isPresent()) {
-                    return appCertificateRule.get();
-                }
-
-                // Do not index when there is not package name or app certificate indexing.
-                return new RuleIndexingDetails(NOT_INDEXED);
-            default:
-                // Having a NOT operator in the indexing messes up the indexing; e.g., deny
-                // installation if app certificate is NOT X (should not be indexed with app cert
-                // X). We will not keep these rules indexed.
-                // Also any other type of unknown operators will not be indexed.
-                return new RuleIndexingDetails(NOT_INDEXED);
-        }
-    }
-
-    private static RuleIndexingDetails getIndexingDetailsForStringAtomicFormula(
-            AtomicFormula.StringAtomicFormula atomicFormula) {
-        switch (atomicFormula.getKey()) {
-            case AtomicFormula.PACKAGE_NAME:
-                return new RuleIndexingDetails(PACKAGE_NAME_INDEXED, atomicFormula.getValue());
-            case AtomicFormula.APP_CERTIFICATE:
-                return new RuleIndexingDetails(APP_CERTIFICATE_INDEXED, atomicFormula.getValue());
-            default:
-                return new RuleIndexingDetails(NOT_INDEXED);
-        }
-    }
-}
-
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleMetadataSerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleMetadataSerializer.java
deleted file mode 100644
index 022b4b8..0000000
--- a/services/core/java/com/android/server/integrity/serializer/RuleMetadataSerializer.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.serializer;
-
-import static com.android.server.integrity.parser.RuleMetadataParser.RULE_PROVIDER_TAG;
-import static com.android.server.integrity.parser.RuleMetadataParser.VERSION_TAG;
-
-import android.util.Xml;
-
-import com.android.modules.utils.TypedXmlSerializer;
-import com.android.server.integrity.model.RuleMetadata;
-
-import org.xmlpull.v1.XmlSerializer;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.charset.StandardCharsets;
-
-/** Helper class for writing rule metadata. */
-public class RuleMetadataSerializer {
-    /** Serialize the rule metadata to an output stream. */
-    public static void serialize(RuleMetadata ruleMetadata, OutputStream outputStream)
-            throws IOException {
-        TypedXmlSerializer xmlSerializer = Xml.resolveSerializer(outputStream);
-
-        serializeTaggedValue(xmlSerializer, RULE_PROVIDER_TAG, ruleMetadata.getRuleProvider());
-        serializeTaggedValue(xmlSerializer, VERSION_TAG, ruleMetadata.getVersion());
-
-        xmlSerializer.endDocument();
-    }
-
-    private static void serializeTaggedValue(TypedXmlSerializer xmlSerializer, String tag,
-            String value) throws IOException {
-        xmlSerializer.startTag(/* namespace= */ null, tag);
-        xmlSerializer.text(value);
-        xmlSerializer.endTag(/* namespace= */ null, tag);
-    }
-}
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleSerializeException.java b/services/core/java/com/android/server/integrity/serializer/RuleSerializeException.java
deleted file mode 100644
index 60cfc48..0000000
--- a/services/core/java/com/android/server/integrity/serializer/RuleSerializeException.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.serializer;
-
-import android.annotation.NonNull;
-
-/**
- * Thrown when rule serialization fails.
- */
-public class RuleSerializeException extends Exception {
-    public RuleSerializeException(@NonNull String message) {
-        super(message);
-    }
-
-    public RuleSerializeException(@NonNull String message, @NonNull Throwable cause) {
-        super(message, cause);
-    }
-}
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleSerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleSerializer.java
deleted file mode 100644
index 2941856..0000000
--- a/services/core/java/com/android/server/integrity/serializer/RuleSerializer.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.serializer;
-
-import android.content.integrity.Rule;
-
-import java.io.OutputStream;
-import java.util.List;
-import java.util.Optional;
-
-/** A helper class to serialize rules from the {@link Rule} model. */
-public interface RuleSerializer {
-
-    /** Serialize rules to an output stream */
-    void serialize(
-            List<Rule> rules,
-            Optional<Integer> formatVersion,
-            OutputStream ruleFileOutputStream,
-            OutputStream indexingFileOutputStream)
-            throws RuleSerializeException;
-
-    /** Serialize rules to a ByteArray. */
-    byte[] serialize(List<Rule> rules, Optional<Integer> formatVersion)
-            throws RuleSerializeException;
-}
diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java
index 89555a9..c8a8799 100644
--- a/services/core/java/com/android/server/media/MediaSession2Record.java
+++ b/services/core/java/com/android/server/media/MediaSession2Record.java
@@ -161,6 +161,11 @@
     }
 
     @Override
+    public void onGlobalPrioritySessionActiveChanged(boolean isGlobalPrioritySessionActive) {
+        // NA as MediaSession2 doesn't support UserEngagementStates for FGS.
+    }
+
+    @Override
     public boolean sendMediaButton(String packageName, int pid, int uid, boolean asSystemService,
             KeyEvent ke, int sequenceId, ResultReceiver cb) {
         // TODO(jaewan): Implement.
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index d752429..668ee2a 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -230,51 +230,49 @@
     private final Runnable mUserEngagementTimeoutExpirationRunnable =
             () -> {
                 synchronized (mLock) {
-                    updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ true);
+                    updateUserEngagedStateIfNeededLocked(
+                            /* isTimeoutExpired= */ true,
+                            /* isGlobalPrioritySessionActive= */ false);
                 }
             };
 
     @GuardedBy("mLock")
     private @UserEngagementState int mUserEngagementState = USER_DISENGAGED;
 
-    @IntDef({USER_PERMANENTLY_ENGAGED, USER_TEMPORARY_ENGAGED, USER_DISENGAGED})
+    @IntDef({USER_PERMANENTLY_ENGAGED, USER_TEMPORARILY_ENGAGED, USER_DISENGAGED})
     @Retention(RetentionPolicy.SOURCE)
     private @interface UserEngagementState {}
 
     /**
-     * Indicates that the session is active and in one of the user engaged states.
+     * Indicates that the session is {@linkplain MediaSession#isActive() active} and in one of the
+     * {@linkplain PlaybackState#isActive() active states}.
      *
      * @see #updateUserEngagedStateIfNeededLocked(boolean)
      */
     private static final int USER_PERMANENTLY_ENGAGED = 0;
 
     /**
-     * Indicates that the session is active and in {@link PlaybackState#STATE_PAUSED} state.
+     * Indicates that the session is {@linkplain MediaSession#isActive() active} and has recently
+     * switched to one of the {@linkplain PlaybackState#isActive() inactive states}.
      *
      * @see #updateUserEngagedStateIfNeededLocked(boolean)
      */
-    private static final int USER_TEMPORARY_ENGAGED = 1;
+    private static final int USER_TEMPORARILY_ENGAGED = 1;
 
     /**
-     * Indicates that the session is either not active or in one of the user disengaged states
+     * Indicates that the session is either not {@linkplain MediaSession#isActive() active} or in
+     * one of the {@linkplain PlaybackState#isActive() inactive states}.
      *
      * @see #updateUserEngagedStateIfNeededLocked(boolean)
      */
     private static final int USER_DISENGAGED = 2;
 
     /**
-     * Indicates the duration of the temporary engaged states, in milliseconds.
+     * Indicates the duration of the temporary engaged state, in milliseconds.
      *
-     * <p>Some {@link MediaSession} states like {@link PlaybackState#STATE_PAUSED} are temporarily
-     * engaged, meaning the corresponding session is only considered in an engaged state for the
-     * duration of this timeout, and only if coming from an engaged state.
-     *
-     * <p>For example, if a session is transitioning from a user-engaged state {@link
-     * PlaybackState#STATE_PLAYING} to a temporary user-engaged state {@link
-     * PlaybackState#STATE_PAUSED}, then the session will be considered in a user-engaged state for
-     * the duration of this timeout, starting at the transition instant. However, a temporary
-     * user-engaged state is not considered user-engaged when transitioning from a non-user engaged
-     * state {@link PlaybackState#STATE_STOPPED}.
+     * <p>When switching to an {@linkplain PlaybackState#isActive() inactive state}, the user is
+     * treated as temporarily engaged, meaning the corresponding session is only considered in an
+     * engaged state for the duration of this timeout, and only if coming from an engaged state.
      */
     private static final int TEMP_USER_ENGAGED_TIMEOUT_MS = 600000;
 
@@ -598,7 +596,8 @@
             mSessionCb.mCb.asBinder().unlinkToDeath(this, 0);
             mDestroyed = true;
             mPlaybackState = null;
-            updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ true);
+            updateUserEngagedStateIfNeededLocked(
+                    /* isTimeoutExpired= */ true, /* isGlobalPrioritySessionActive= */ false);
             mHandler.post(MessageHandler.MSG_DESTROYED);
         }
     }
@@ -615,6 +614,24 @@
         mHandler.post(mUserEngagementTimeoutExpirationRunnable);
     }
 
+    @Override
+    public void onGlobalPrioritySessionActiveChanged(boolean isGlobalPrioritySessionActive) {
+        mHandler.post(
+                () -> {
+                    synchronized (mLock) {
+                        if (isGlobalPrioritySessionActive) {
+                            mHandler.removeCallbacks(mUserEngagementTimeoutExpirationRunnable);
+                        } else {
+                            if (mUserEngagementState == USER_TEMPORARILY_ENGAGED) {
+                                mHandler.postDelayed(
+                                        mUserEngagementTimeoutExpirationRunnable,
+                                        TEMP_USER_ENGAGED_TIMEOUT_MS);
+                            }
+                        }
+                    }
+                });
+    }
+
     /**
      * Sends media button.
      *
@@ -1063,21 +1080,20 @@
     }
 
     @GuardedBy("mLock")
-    private void updateUserEngagedStateIfNeededLocked(boolean isTimeoutExpired) {
+    private void updateUserEngagedStateIfNeededLocked(
+            boolean isTimeoutExpired, boolean isGlobalPrioritySessionActive) {
         if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
             return;
         }
         int oldUserEngagedState = mUserEngagementState;
         int newUserEngagedState;
-        if (!isActive() || mPlaybackState == null || mDestroyed) {
+        if (!isActive() || mPlaybackState == null) {
             newUserEngagedState = USER_DISENGAGED;
-        } else if (isActive() && mPlaybackState.isActive()) {
+        } else if (mPlaybackState.isActive()) {
             newUserEngagedState = USER_PERMANENTLY_ENGAGED;
-        } else if (mPlaybackState.getState() == PlaybackState.STATE_PAUSED) {
-            newUserEngagedState =
-                    oldUserEngagedState == USER_PERMANENTLY_ENGAGED || !isTimeoutExpired
-                            ? USER_TEMPORARY_ENGAGED
-                            : USER_DISENGAGED;
+        } else if (oldUserEngagedState == USER_PERMANENTLY_ENGAGED
+                || (oldUserEngagedState == USER_TEMPORARILY_ENGAGED && !isTimeoutExpired)) {
+            newUserEngagedState = USER_TEMPORARILY_ENGAGED;
         } else {
             newUserEngagedState = USER_DISENGAGED;
         }
@@ -1086,7 +1102,7 @@
         }
 
         mUserEngagementState = newUserEngagedState;
-        if (newUserEngagedState == USER_TEMPORARY_ENGAGED) {
+        if (newUserEngagedState == USER_TEMPORARILY_ENGAGED && !isGlobalPrioritySessionActive) {
             mHandler.postDelayed(
                     mUserEngagementTimeoutExpirationRunnable, TEMP_USER_ENGAGED_TIMEOUT_MS);
         } else {
@@ -1141,9 +1157,11 @@
                         .logFgsApiEnd(ActivityManager.FOREGROUND_SERVICE_API_TYPE_MEDIA_PLAYBACK,
                                 callingUid, callingPid);
             }
+            boolean isGlobalPrioritySessionActive = mService.isGlobalPrioritySessionActive();
             synchronized (mLock) {
                 mIsActive = active;
-                updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ false);
+                updateUserEngagedStateIfNeededLocked(
+                        /* isTimeoutExpired= */ false, isGlobalPrioritySessionActive);
             }
             long token = Binder.clearCallingIdentity();
             try {
@@ -1300,9 +1318,11 @@
             boolean shouldUpdatePriority = ALWAYS_PRIORITY_STATES.contains(newState)
                     || (!TRANSITION_PRIORITY_STATES.contains(oldState)
                     && TRANSITION_PRIORITY_STATES.contains(newState));
+            boolean isGlobalPrioritySessionActive = mService.isGlobalPrioritySessionActive();
             synchronized (mLock) {
                 mPlaybackState = state;
-                updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ false);
+                updateUserEngagedStateIfNeededLocked(
+                        /* isTimeoutExpired= */ false, isGlobalPrioritySessionActive);
             }
             final long token = Binder.clearCallingIdentity();
             try {
diff --git a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
index 15f90d4..6c3b123 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
@@ -206,6 +206,10 @@
      */
     public abstract void expireTempEngaged();
 
+    /** Notifies record that the global priority session active state changed. */
+    public abstract void onGlobalPrioritySessionActiveChanged(
+            boolean isGlobalPrioritySessionActive);
+
     @Override
     public final boolean equals(Object o) {
         if (this == o) return true;
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 1ebc856..2b29fbd 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -362,6 +362,7 @@
                                     + record.isActive());
                 }
                 user.pushAddressedPlayerChangedLocked();
+                mHandler.post(this::notifyGlobalPrioritySessionActiveChanged);
             } else {
                 if (!user.mPriorityStack.contains(record)) {
                     Log.w(TAG, "Unknown session updated. Ignoring.");
@@ -394,11 +395,16 @@
 
     // Currently only media1 can become global priority session.
     void setGlobalPrioritySession(MediaSessionRecord record) {
+        boolean globalPrioritySessionActiveChanged = false;
         synchronized (mLock) {
             FullUserRecord user = getFullUserRecordLocked(record.getUserId());
             if (mGlobalPrioritySession != record) {
                 Log.d(TAG, "Global priority session is changed from " + mGlobalPrioritySession
                         + " to " + record);
+                globalPrioritySessionActiveChanged =
+                        (mGlobalPrioritySession == null && record.isActive())
+                                || (mGlobalPrioritySession != null
+                                        && mGlobalPrioritySession.isActive() != record.isActive());
                 mGlobalPrioritySession = record;
                 if (user != null && user.mPriorityStack.contains(record)) {
                     // Handle the global priority session separately.
@@ -409,6 +415,30 @@
                 }
             }
         }
+        if (globalPrioritySessionActiveChanged) {
+            mHandler.post(this::notifyGlobalPrioritySessionActiveChanged);
+        }
+    }
+
+    /** Returns whether the global priority session is active. */
+    boolean isGlobalPrioritySessionActive() {
+        synchronized (mLock) {
+            return isGlobalPriorityActiveLocked();
+        }
+    }
+
+    private void notifyGlobalPrioritySessionActiveChanged() {
+        if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
+            return;
+        }
+        synchronized (mLock) {
+            boolean isGlobalPriorityActive = isGlobalPriorityActiveLocked();
+            for (Set<MediaSessionRecordImpl> records : mUserEngagedSessionsForFgs.values()) {
+                for (MediaSessionRecordImpl record : records) {
+                    record.onGlobalPrioritySessionActiveChanged(isGlobalPriorityActive);
+                }
+            }
+        }
     }
 
     private List<MediaSessionRecord> getActiveSessionsLocked(int userId) {
@@ -646,8 +676,11 @@
 
         if (mGlobalPrioritySession == session) {
             mGlobalPrioritySession = null;
-            if (session.isActive() && user != null) {
-                user.pushAddressedPlayerChangedLocked();
+            if (session.isActive()) {
+                if (user != null) {
+                    user.pushAddressedPlayerChangedLocked();
+                }
+                mHandler.post(this::notifyGlobalPrioritySessionActiveChanged);
             }
         } else {
             if (user != null) {
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 122836e..93482e7 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -21,9 +21,6 @@
 import static android.content.Context.BIND_AUTO_CREATE;
 import static android.content.Context.BIND_FOREGROUND_SERVICE;
 import static android.content.Context.DEVICE_POLICY_SERVICE;
-import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
-import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
-import static android.content.pm.PackageManager.MATCH_INSTANT;
 import static android.os.UserHandle.USER_ALL;
 import static android.os.UserHandle.USER_SYSTEM;
 import static android.service.notification.NotificationListenerService.META_DATA_DEFAULT_AUTOBIND;
@@ -109,8 +106,7 @@
     protected final String TAG = getClass().getSimpleName().replace('$', '.');
     protected final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
-    protected static final int ON_BINDING_DIED_REBIND_DELAY_MS = 10000;
-    protected static final int ON_BINDING_DIED_REBIND_MSG = 1234;
+    private static final int ON_BINDING_DIED_REBIND_DELAY_MS = 10000;
     protected static final String ENABLED_SERVICES_SEPARATOR = ":";
     private static final String DB_VERSION_1 = "1";
     private static final String DB_VERSION_2 = "2";
@@ -879,21 +875,7 @@
             String approvedItem = getApprovedValue(pkgOrComponent);
 
             if (approvedItem != null) {
-                final ComponentName component = ComponentName.unflattenFromString(approvedItem);
                 if (enabled) {
-                    if (Flags.notificationNlsRebind()) {
-                        if (component != null && !isValidService(component, userId)) {
-                            // Only fail if package is available
-                            // If not, it will be validated again in onPackagesChanged
-                            final PackageManager pm = mContext.getPackageManager();
-                            if (pm.isPackageAvailable(component.getPackageName())) {
-                                Slog.w(TAG, "Skip allowing " + mConfig.caption
-                                        + " " + pkgOrComponent + " (userSet: " + userSet
-                                        + ") for invalid service");
-                                return;
-                            }
-                        }
-                    }
                     approved.add(approvedItem);
                 } else {
                     approved.remove(approvedItem);
@@ -991,7 +973,7 @@
                 || isPackageOrComponentAllowed(component.getPackageName(), userId))) {
             return false;
         }
-        return isValidService(component, userId);
+        return componentHasBindPermission(component, userId);
     }
 
     private boolean componentHasBindPermission(ComponentName component, int userId) {
@@ -1238,21 +1220,12 @@
         if (!TextUtils.isEmpty(packageName)) {
             queryIntent.setPackage(packageName);
         }
-
-        if (Flags.notificationNlsRebind()) {
-            // Expand the package query
-            extraFlags |= MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE;
-            extraFlags |= MATCH_INSTANT;
-        }
-
         List<ResolveInfo> installedServices = pm.queryIntentServicesAsUser(
                 queryIntent,
                 PackageManager.GET_SERVICES | PackageManager.GET_META_DATA | extraFlags,
                 userId);
-        if (DEBUG) {
-            Slog.v(TAG, mConfig.serviceInterface + " pkg: " + packageName + " services: "
-                    + installedServices);
-        }
+        if (DEBUG)
+            Slog.v(TAG, mConfig.serviceInterface + " services: " + installedServices);
         if (installedServices != null) {
             for (int i = 0, count = installedServices.size(); i < count; i++) {
                 ResolveInfo resolveInfo = installedServices.get(i);
@@ -1352,12 +1325,11 @@
                     if (TextUtils.equals(getPackageName(approvedPackageOrComponent), packageName)) {
                         final ComponentName component = ComponentName.unflattenFromString(
                                 approvedPackageOrComponent);
-                        if (component != null && !isValidService(component, userId)) {
+                        if (component != null && !componentHasBindPermission(component, userId)) {
                             approved.removeAt(j);
                             if (DEBUG) {
                                 Slog.v(TAG, "Removing " + approvedPackageOrComponent
-                                        + " from approved list; no bind permission or "
-                                        + "service interface filter found "
+                                        + " from approved list; no bind permission found "
                                         + mConfig.bindPermission);
                             }
                         }
@@ -1376,15 +1348,6 @@
         }
     }
 
-    protected boolean isValidService(ComponentName component, int userId) {
-        if (Flags.notificationNlsRebind()) {
-            return componentHasBindPermission(component, userId) && queryPackageForServices(
-                    component.getPackageName(), userId).contains(component);
-        } else {
-            return componentHasBindPermission(component, userId);
-        }
-    }
-
     protected boolean isValidEntry(String packageOrComponent, int userId) {
         return hasMatchingServices(packageOrComponent, userId);
     }
@@ -1542,27 +1505,23 @@
      * Called when user switched to unbind all services from other users.
      */
     @VisibleForTesting
-    void unbindOtherUserServices(int switchedToUser) {
+    void unbindOtherUserServices(int currentUser) {
         TimingsTraceAndSlog t = new TimingsTraceAndSlog();
-        t.traceBegin("ManagedServices.unbindOtherUserServices_current" + switchedToUser);
-        unbindServicesImpl(switchedToUser, true /* allExceptUser */);
+        t.traceBegin("ManagedServices.unbindOtherUserServices_current" + currentUser);
+        unbindServicesImpl(currentUser, true /* allExceptUser */);
         t.traceEnd();
     }
 
-    void unbindUserServices(int removedUser) {
+    void unbindUserServices(int user) {
         TimingsTraceAndSlog t = new TimingsTraceAndSlog();
-        t.traceBegin("ManagedServices.unbindUserServices" + removedUser);
-        unbindServicesImpl(removedUser, false /* allExceptUser */);
+        t.traceBegin("ManagedServices.unbindUserServices" + user);
+        unbindServicesImpl(user, false /* allExceptUser */);
         t.traceEnd();
     }
 
     void unbindServicesImpl(int user, boolean allExceptUser) {
         final SparseArray<Set<ComponentName>> componentsToUnbind = new SparseArray<>();
         synchronized (mMutex) {
-            if (Flags.notificationNlsRebind()) {
-                // Remove enqueued rebinds to avoid rebinding services for a switched user
-                mHandler.removeMessages(ON_BINDING_DIED_REBIND_MSG);
-            }
             final Set<ManagedServiceInfo> removableBoundServices = getRemovableConnectedServices();
             for (ManagedServiceInfo info : removableBoundServices) {
                 if ((allExceptUser && (info.userid != user))
@@ -1757,7 +1716,6 @@
                             mServicesRebinding.add(servicesBindingTag);
                             mHandler.postDelayed(() ->
                                     reregisterService(name, userid),
-                                    ON_BINDING_DIED_REBIND_MSG,
                                     ON_BINDING_DIED_REBIND_DELAY_MS);
                         } else {
                             Slog.v(TAG, getCaption() + " not rebinding in user " + userid
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 2c45fc8..e966c15 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -82,6 +82,8 @@
 import static android.app.NotificationManager.zenModeFromInterruptionFilter;
 import static android.app.StatusBarManager.ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED;
 import static android.app.StatusBarManager.EXTRA_KM_PRIVATE_NOTIFS_ALLOWED;
+import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_CONFIG;
+import static android.app.backup.NotificationLoggingConstants.ERROR_XML_PARSING;
 import static android.content.Context.BIND_ALLOW_WHITELIST_MANAGEMENT;
 import static android.content.Context.BIND_AUTO_CREATE;
 import static android.content.Context.BIND_FOREGROUND_SERVICE;
@@ -162,8 +164,6 @@
 import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER;
 import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER;
 import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER;
-import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_CONFIG;
-import static android.app.backup.NotificationLoggingConstants.ERROR_XML_PARSING;
 import static com.android.server.notification.Flags.expireBitmaps;
 import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_ANIM_BUFFER;
 import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_TIMEOUT;
@@ -1929,6 +1929,12 @@
                 hasSensitiveContent, lifespanMs);
     }
 
+    protected void logClassificationChannelAdjustmentReceived(boolean hasPosted, boolean isAlerting,
+                                                              int classification, int lifespanMs) {
+        FrameworkStatsLog.write(FrameworkStatsLog.NOTIFICATION_CHANNEL_CLASSIFICATION,
+                hasPosted, isAlerting, classification, lifespanMs);
+    }
+
     protected final BroadcastReceiver mLocaleChangeReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -6988,8 +6994,15 @@
                 if (newChannel == null || newChannel.getId().equals(r.getChannel().getId())) {
                     adjustments.remove(KEY_TYPE);
                 } else {
+                    // Save the app-provided type for logging.
+                    int classification = adjustments.getInt(KEY_TYPE);
                     // swap app provided type with the real thing
                     adjustments.putParcelable(KEY_TYPE, newChannel);
+                    // Note that this value of isAlerting does not fully indicate whether a notif
+                    // would make a sound or HUN on device; it is an approximation for metrics.
+                    boolean isAlerting = r.getChannel().getImportance() >= IMPORTANCE_DEFAULT;
+                    logClassificationChannelAdjustmentReceived(isPosted, isAlerting, classification,
+                            r.getLifespanMs(System.currentTimeMillis()));
                 }
             }
             r.addAdjustment(adjustment);
@@ -7770,10 +7783,11 @@
         // Make Notification silent
         r.getNotification().flags |= FLAG_ONLY_ALERT_ONCE;
 
-        // Repost
+        // Repost as the original app (even if it was posted by a delegate originally
+        // because the delegate may now be revoked)
         enqueueNotificationInternal(r.getSbn().getPackageName(),
-                r.getSbn().getOpPkg(), r.getSbn().getUid(),
-                r.getSbn().getInitialPid(), r.getSbn().getTag(),
+                r.getSbn().getPackageName(), r.getSbn().getUid(),
+                MY_PID, r.getSbn().getTag(),
                 r.getSbn().getId(), r.getNotification(),
                 r.getSbn().getUserId(), /* postSilently= */ true,
                 /* byForegroundService= */ false,
@@ -8012,7 +8026,6 @@
         r.setPkgAllowedAsConvo(mMsgPkgsAllowedAsConvos.contains(pkg));
         boolean isImportanceFixed = mPermissionHelper.isPermissionFixed(pkg, userId);
         r.setImportanceFixed(isImportanceFixed);
-
         if (notification.isFgsOrUij()) {
             if (((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_IMPORTANCE) == 0
                         || !channel.isUserVisibleTaskShown())
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index c479acf..f79d9ef 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -194,13 +194,3 @@
   description: "Enables sound uri with vibration source in notification channel"
   bug: "351975435"
 }
-
-flag {
-  name: "notification_nls_rebind"
-  namespace: "systemui"
-  description: "Check for NLS service intent filter when rebinding services"
-  bug: "347674739"
-  metadata {
-    purpose: PURPOSE_BUGFIX
-  }
-}
diff --git a/services/core/java/com/android/server/om/OverlayActorEnforcer.java b/services/core/java/com/android/server/om/OverlayActorEnforcer.java
index 015b7fd..38f3939 100644
--- a/services/core/java/com/android/server/om/OverlayActorEnforcer.java
+++ b/services/core/java/com/android/server/om/OverlayActorEnforcer.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.content.om.OverlayInfo;
 import android.content.om.OverlayableInfo;
+import android.content.res.Flags;
 import android.net.Uri;
 import android.os.Process;
 import android.text.TextUtils;
@@ -162,11 +163,15 @@
             return ActorState.UNABLE_TO_GET_TARGET_OVERLAYABLE;
         }
 
-        if (targetOverlayable == null) {
+        // Framework doesn't have <overlayable> declaration by design, and we still want to be able
+        // to enable its overlays from the packages with the permission.
+        if (targetOverlayable == null
+                && !(Flags.rroControlForAndroidNoOverlayable() && targetPackageName.equals(
+                "android"))) {
             return ActorState.MISSING_OVERLAYABLE;
         }
 
-        String actor = targetOverlayable.actor;
+        final String actor = targetOverlayable == null ? null : targetOverlayable.actor;
         if (TextUtils.isEmpty(actor)) {
             // If there's no actor defined, fallback to the legacy permission check
             try {
diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
index 20cca969..af2bb17 100644
--- a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
@@ -404,9 +404,11 @@
 
     void handlePackageRemove(String packageName, int userId) {
         initBackgroundInstalledPackages();
+        if (mBackgroundInstalledPackages.contains(userId, packageName)) {
+            mCallbackHelper.notifyAllCallbacks(userId, packageName, INSTALL_EVENT_TYPE_UNINSTALL);
+        }
         mBackgroundInstalledPackages.remove(userId, packageName);
         writeBackgroundInstalledPackagesToDisk();
-        mCallbackHelper.notifyAllCallbacks(userId, packageName, INSTALL_EVENT_TYPE_UNINSTALL);
     }
 
     void handleUsageEvent(UsageEvents.Event event, int userId) {
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 355184e..d9e7696 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -85,6 +85,7 @@
 import static com.android.server.pm.PackageManagerService.SCAN_REQUIRE_KNOWN;
 import static com.android.server.pm.PackageManagerService.SCAN_UPDATE_SIGNATURE;
 import static com.android.server.pm.PackageManagerService.TAG;
+import static com.android.server.pm.PackageManagerService.WATCHDOG_TIMEOUT;
 import static com.android.server.pm.PackageManagerServiceUtils.comparePackageSignatures;
 import static com.android.server.pm.PackageManagerServiceUtils.compareSignatures;
 import static com.android.server.pm.PackageManagerServiceUtils.compressedFileExists;
@@ -133,9 +134,11 @@
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.Message;
+import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SELinux;
+import android.os.SystemClock;
 import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -174,7 +177,6 @@
 import com.android.server.EventLogTags;
 import com.android.server.SystemConfig;
 import com.android.server.criticalevents.CriticalEventLog;
-import com.android.server.pm.dex.ArtManagerService;
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.parsing.PackageCacher;
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
@@ -210,6 +212,10 @@
 
 
 final class InstallPackageHelper {
+    // One minute over PM WATCHDOG_TIMEOUT
+    private static final long WAKELOCK_TIMEOUT_MS = WATCHDOG_TIMEOUT + 1000 * 60;
+    private static final String INSTALLER_WAKE_LOCK_TAG = "installer:packages";
+
     private final PackageManagerService mPm;
     private final AppDataHelper mAppDataHelper;
     private final BroadcastHelper mBroadcastHelper;
@@ -218,14 +224,16 @@
     private final IncrementalManager mIncrementalManager;
     private final ApexManager mApexManager;
     private final DexManager mDexManager;
-    private final ArtManagerService mArtManagerService;
     private final Context mContext;
-    private final PackageDexOptimizer mPackageDexOptimizer;
     private final PackageAbiHelper mPackageAbiHelper;
     private final SharedLibrariesImpl mSharedLibraries;
     private final PackageManagerServiceInjector mInjector;
     private final UpdateOwnershipHelper mUpdateOwnershipHelper;
 
+    private final Object mInternalLock = new Object();
+    @GuardedBy("mInternalLock")
+    private PowerManager.WakeLock mInstallingWakeLock;
+
     // TODO(b/198166813): remove PMS dependency
     InstallPackageHelper(PackageManagerService pm,
                          AppDataHelper appDataHelper,
@@ -241,9 +249,7 @@
         mIncrementalManager = pm.mInjector.getIncrementalManager();
         mApexManager = pm.mInjector.getApexManager();
         mDexManager = pm.mInjector.getDexManager();
-        mArtManagerService = pm.mInjector.getArtManagerService();
         mContext = pm.mInjector.getContext();
-        mPackageDexOptimizer = pm.mInjector.getPackageDexOptimizer();
         mPackageAbiHelper = pm.mInjector.getAbiHelper();
         mSharedLibraries = pm.mInjector.getSharedLibrariesImpl();
         mUpdateOwnershipHelper = pm.mInjector.getUpdateOwnershipHelper();
@@ -1013,6 +1019,7 @@
         boolean success = false;
         final Map<String, Boolean> createdAppId = new ArrayMap<>(requests.size());
         final Map<String, Settings.VersionInfo> versionInfos = new ArrayMap<>(requests.size());
+        final long acquireTime = acquireWakeLock(requests.size());
         try {
             CriticalEventLog.getInstance().logInstallPackagesStarted();
             if (prepareInstallPackages(requests)
@@ -1033,6 +1040,46 @@
         } finally {
             completeInstallProcess(requests, createdAppId, success);
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+            releaseWakeLock(acquireTime, requests.size());
+        }
+    }
+
+    private long acquireWakeLock(int count) {
+        if (!mPm.isSystemReady()) {
+            return -1;
+        }
+        synchronized (mInternalLock) {
+            if (mInstallingWakeLock == null) {
+                PowerManager pwm = mContext.getSystemService(PowerManager.class);
+                if (pwm != null) {
+                    mInstallingWakeLock = pwm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+                            INSTALLER_WAKE_LOCK_TAG);
+                } else {
+                    Slog.w(TAG, "Unable to obtain power manager while obtaining wake lock");
+                    return -1;
+                }
+            }
+
+            mInstallingWakeLock.acquire(WAKELOCK_TIMEOUT_MS * count);
+            return SystemClock.elapsedRealtime();
+        }
+    }
+
+    private void releaseWakeLock(final long acquireTime, int count) {
+        if (acquireTime < 0) {
+            return;
+        }
+        synchronized (mInternalLock) {
+            try {
+                if (mInstallingWakeLock == null) {
+                    return;
+                }
+                if (mInstallingWakeLock.isHeld()) {
+                    mInstallingWakeLock.release();
+                }
+            } catch (RuntimeException e) {
+                Slog.wtf(TAG, "Error while releasing installer lock", e);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 7ecfe7f..4986594 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -341,6 +341,10 @@
     private static final String TRON_USER_CREATED = "users_user_created";
     private static final String TRON_DEMO_CREATED = "users_demo_created";
 
+    // The boot user strategy for HSUM.
+    private static final int BOOT_TO_PREVIOUS_OR_FIRST_SWITCHABLE_USER = 0;
+    private static final int BOOT_TO_HSU_FOR_PROVISIONED_DEVICE = 1;
+
     private final Context mContext;
     private final PackageManagerService mPm;
 
@@ -1391,37 +1395,77 @@
         }
 
         if (isHeadlessSystemUserMode()) {
-            if (mContext.getResources()
-                    .getBoolean(com.android.internal.R.bool.config_bootToHeadlessSystemUser)) {
-                return UserHandle.USER_SYSTEM;
+            final int bootStrategy = mContext.getResources()
+                    .getInteger(com.android.internal.R.integer.config_hsumBootStrategy);
+            switch (bootStrategy) {
+                case BOOT_TO_PREVIOUS_OR_FIRST_SWITCHABLE_USER:
+                    return getPreviousOrFirstSwitchableUser();
+                case BOOT_TO_HSU_FOR_PROVISIONED_DEVICE:
+                    return getBootUserBasedOnProvisioning();
+                default:
+                    Slogf.w(LOG_TAG, "Unknown HSUM boot strategy: %d", bootStrategy);
+                    return getPreviousOrFirstSwitchableUser();
             }
-            // Return the previous foreground user, if there is one.
-            final int previousUser = getPreviousFullUserToEnterForeground();
-            if (previousUser != UserHandle.USER_NULL) {
-                Slogf.i(LOG_TAG, "Boot user is previous user %d", previousUser);
-                return previousUser;
-            }
-            // No previous user. Return the first switchable user if there is one.
-            synchronized (mUsersLock) {
-                final int userSize = mUsers.size();
-                for (int i = 0; i < userSize; i++) {
-                    final UserData userData = mUsers.valueAt(i);
-                    if (userData.info.supportsSwitchToByUser()) {
-                        int firstSwitchable = userData.info.id;
-                        Slogf.i(LOG_TAG,
-                                "Boot user is first switchable user %d", firstSwitchable);
-                        return firstSwitchable;
-                    }
-                }
-            }
-            // No switchable users found. Uh oh!
-            throw new UserManager.CheckedUserOperationException(
-                    "No switchable users found", USER_OPERATION_ERROR_UNKNOWN);
         }
         // Not HSUM, return system user.
         return UserHandle.USER_SYSTEM;
     }
 
+    private @UserIdInt int getBootUserBasedOnProvisioning()
+            throws UserManager.CheckedUserOperationException {
+        final boolean provisioned = Settings.Global.getInt(mContext.getContentResolver(),
+                                            Settings.Global.DEVICE_PROVISIONED, 0) != 0;
+        if (provisioned) {
+            return UserHandle.USER_SYSTEM;
+        } else {
+            final int firstSwitchableFullUser = getFirstSwitchableUser(true);
+            if (firstSwitchableFullUser != UserHandle.USER_NULL) {
+                Slogf.i(LOG_TAG,
+                        "Boot user is first switchable full user %d",
+                                firstSwitchableFullUser);
+                return firstSwitchableFullUser;
+            }
+            // No switchable full user found. Uh oh!
+            throw new UserManager.CheckedUserOperationException(
+                "No switchable full user found", USER_OPERATION_ERROR_UNKNOWN);
+        }
+    }
+
+    private @UserIdInt int getPreviousOrFirstSwitchableUser()
+            throws UserManager.CheckedUserOperationException {
+        // Return the previous foreground user, if there is one.
+        final int previousUser = getPreviousFullUserToEnterForeground();
+        if (previousUser != UserHandle.USER_NULL) {
+            Slogf.i(LOG_TAG, "Boot user is previous user %d", previousUser);
+            return previousUser;
+        }
+        // No previous user. Return the first switchable user if there is one.
+        final int firstSwitchableUser = getFirstSwitchableUser(false);
+        if (firstSwitchableUser != UserHandle.USER_NULL) {
+            Slogf.i(LOG_TAG,
+                    "Boot user is first switchable user %d", firstSwitchableUser);
+            return firstSwitchableUser;
+        }
+        // No switchable users found. Uh oh!
+        throw new UserManager.CheckedUserOperationException(
+            "No switchable users found", USER_OPERATION_ERROR_UNKNOWN);
+    }
+
+    private @UserIdInt int getFirstSwitchableUser(boolean fullUserOnly) {
+        synchronized (mUsersLock) {
+            final int userSize = mUsers.size();
+            for (int i = 0; i < userSize; i++) {
+                final UserData userData = mUsers.valueAt(i);
+                if (userData.info.supportsSwitchToByUser() &&
+                        (!fullUserOnly || userData.info.isFull())) {
+                    int firstSwitchable = userData.info.id;
+                    return firstSwitchable;
+                }
+            }
+        }
+       return UserHandle.USER_NULL;
+   }
+
 
     @Override
     public int getPreviousFullUserToEnterForeground() {
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 07fd1cb..acf62dc 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -235,6 +235,7 @@
         NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.BLUETOOTH_SCAN);
         NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.UWB_RANGING);
         NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.NEARBY_WIFI_DEVICES);
+        NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.RANGING);
     }
 
     private static final Set<String> NOTIFICATION_PERMISSIONS = new ArraySet<>();
diff --git a/services/core/java/com/android/server/policy/ModifierShortcutManager.java b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
index a1236e5..4f67318 100644
--- a/services/core/java/com/android/server/policy/ModifierShortcutManager.java
+++ b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
@@ -32,6 +32,7 @@
 import android.content.res.XmlResourceParser;
 import android.graphics.drawable.Icon;
 import android.hardware.input.AppLaunchData;
+import android.hardware.input.InputGestureData;
 import android.hardware.input.KeyGestureEvent;
 import android.os.Handler;
 import android.os.RemoteException;
@@ -769,6 +770,30 @@
                 shortcuts);
     }
 
+    /**
+     * @return a {@link KeyboardShortcutGroup} containing the application launch keyboard
+     *         shortcuts based on provided list of shortcut data.
+     */
+    public KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId,
+            List<InputGestureData> shortcutData) {
+        List<KeyboardShortcutInfo> shortcuts = new ArrayList<>();
+        KeyCharacterMap kcm = KeyCharacterMap.load(deviceId);
+        for (InputGestureData data : shortcutData) {
+            if (data.getTrigger() instanceof InputGestureData.KeyTrigger trigger) {
+                KeyboardShortcutInfo info = shortcutInfoFromIntent(
+                        kcm.getDisplayLabel(trigger.getKeycode()),
+                        getIntentFromAppLaunchData(data.getAction().appLaunchData()),
+                        (trigger.getModifierState() & KeyEvent.META_SHIFT_ON) != 0);
+                if (info != null) {
+                    shortcuts.add(info);
+                }
+            }
+        }
+        return new KeyboardShortcutGroup(
+                mContext.getString(R.string.keyboard_shortcut_group_applications),
+                shortcuts);
+    }
+
     private Intent getIntentFromAppLaunchData(@NonNull AppLaunchData data) {
         Context context = mContext.createContextAsUser(mCurrentUser, 0);
         synchronized (mAppIntentCache) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 2893430..fc24e62d 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3414,6 +3414,10 @@
 
     @Override
     public KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId) {
+        if (useKeyGestureEventHandler()) {
+            return mModifierShortcutManager.getApplicationLaunchKeyboardShortcuts(deviceId,
+                    mInputManager.getAppLaunchBookmarks());
+        }
         return mModifierShortcutManager.getApplicationLaunchKeyboardShortcuts(deviceId);
     }
 
@@ -4004,14 +4008,7 @@
     private boolean interceptSystemKeysAndShortcutsNew(IBinder focusedToken, KeyEvent event) {
         final int keyCode = event.getKeyCode();
         final int metaState = event.getMetaState();
-        final boolean keyguardOn = keyguardOn();
 
-        if (isUserSetupComplete() && !keyguardOn) {
-            if (mModifierShortcutManager.interceptKey(event)) {
-                dismissKeyboardShortcutsMenu();
-                return true;
-            }
-        }
         switch (keyCode) {
             case KeyEvent.KEYCODE_HOME:
                 return handleHomeShortcuts(focusedToken, event);
@@ -4753,6 +4750,10 @@
     public void registerShortcutKey(long shortcutCode, IShortcutService shortcutService)
             throws RemoteException {
         synchronized (mLock) {
+            if (useKeyGestureEventHandler()) {
+                mInputManagerInternal.registerShortcutKey(shortcutCode, shortcutService);
+                return;
+            }
             mModifierShortcutManager.registerShortcutKey(shortcutCode, shortcutService);
         }
     }
diff --git a/services/core/java/com/android/server/power/PowerGroup.java b/services/core/java/com/android/server/power/PowerGroup.java
index a928814..01a2045 100644
--- a/services/core/java/com/android/server/power/PowerGroup.java
+++ b/services/core/java/com/android/server/power/PowerGroup.java
@@ -42,6 +42,8 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.LatencyTracker;
+import com.android.server.LocalServices;
+import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
 import com.android.server.power.feature.PowerManagerFlags;
 
 /**
@@ -56,6 +58,11 @@
     private static final String TAG = PowerGroup.class.getSimpleName();
     private static final boolean DEBUG = false;
 
+    /**
+     * Indicates that the default dim/sleep timeouts should be used.
+     */
+    private static final long INVALID_TIMEOUT = -1;
+
     @VisibleForTesting
     final DisplayPowerRequest mDisplayPowerRequest = new DisplayPowerRequest();
     private final PowerGroupListener mWakefulnessListener;
@@ -91,6 +98,9 @@
     private @PowerManager.GoToSleepReason int mLastSleepReason =
             PowerManager.GO_TO_SLEEP_REASON_UNKNOWN;
 
+    private final long mDimDuration;
+    private final long mScreenOffTimeout;
+
     PowerGroup(int groupId, PowerGroupListener wakefulnessListener, Notifier notifier,
             DisplayManagerInternal displayManagerInternal, int wakefulness, boolean ready,
             boolean supportsSandman, long eventTime, PowerManagerFlags featureFlags) {
@@ -104,6 +114,30 @@
         mLastWakeTime = eventTime;
         mLastSleepTime = eventTime;
         mFeatureFlags = featureFlags;
+
+        long dimDuration = INVALID_TIMEOUT;
+        long screenOffTimeout = INVALID_TIMEOUT;
+        if (android.companion.virtualdevice.flags.Flags.deviceAwareDisplayPower()
+                && mGroupId != Display.DEFAULT_DISPLAY_GROUP) {
+            VirtualDeviceManagerInternal vdm =
+                    LocalServices.getService(VirtualDeviceManagerInternal.class);
+            if (vdm != null) {
+                int[] displayIds = mDisplayManagerInternal.getDisplayIdsForGroup(mGroupId);
+                if (displayIds != null && displayIds.length > 0) {
+                    int deviceId = vdm.getDeviceIdForDisplayId(displayIds[0]);
+                    if (vdm.isValidVirtualDeviceId(deviceId)) {
+                        dimDuration = vdm.getDimDurationMillisForDeviceId(deviceId);
+                        screenOffTimeout = vdm.getScreenOffTimeoutMillisForDeviceId(deviceId);
+                        if (dimDuration > 0 && dimDuration > screenOffTimeout) {
+                            // If the dim duration is set, cap it to the screen off timeout.
+                            dimDuration = screenOffTimeout;
+                        }
+                    }
+                }
+            }
+        }
+        mDimDuration = dimDuration;
+        mScreenOffTimeout = screenOffTimeout;
     }
 
     PowerGroup(int wakefulness, PowerGroupListener wakefulnessListener, Notifier notifier,
@@ -119,6 +153,16 @@
         mLastWakeTime = eventTime;
         mLastSleepTime = eventTime;
         mFeatureFlags = featureFlags;
+        mDimDuration = INVALID_TIMEOUT;
+        mScreenOffTimeout = INVALID_TIMEOUT;
+    }
+
+    long getScreenOffTimeoutOverrideLocked(long defaultScreenOffTimeout) {
+        return mScreenOffTimeout == INVALID_TIMEOUT ? defaultScreenOffTimeout : mScreenOffTimeout;
+    }
+
+    long getScreenDimDurationOverrideLocked(long defaultScreenDimDuration) {
+        return mDimDuration == INVALID_TIMEOUT ? defaultScreenDimDuration : mDimDuration;
     }
 
     long getLastWakeTimeLocked() {
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 3a5afac..0acfe92 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -2971,8 +2971,8 @@
         mHandler.removeMessages(MSG_USER_ACTIVITY_TIMEOUT);
 
         final long attentiveTimeout = getAttentiveTimeoutLocked();
-        final long sleepTimeout = getSleepTimeoutLocked(attentiveTimeout);
-        final long defaultScreenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout,
+        final long defaultSleepTimeout = getSleepTimeoutLocked(attentiveTimeout);
+        final long defaultScreenOffTimeout = getScreenOffTimeoutLocked(defaultSleepTimeout,
                 attentiveTimeout);
         final long defaultScreenDimDuration = getScreenDimDurationLocked(defaultScreenOffTimeout);
 
@@ -2985,13 +2985,25 @@
             final PowerGroup powerGroup = mPowerGroups.valueAt(idx);
             final int wakefulness = powerGroup.getWakefulnessLocked();
 
-            // The default display screen timeout could be overridden by policy.
+            // The timeouts could be overridden by the power group policy.
             long screenOffTimeout = defaultScreenOffTimeout;
             long screenDimDuration = defaultScreenDimDuration;
+            long sleepTimeout = defaultSleepTimeout;
+            // TODO(b/376211497): Consolidate the timeout logic for all power groups.
             if (powerGroup.getGroupId() == Display.DEFAULT_DISPLAY_GROUP) {
                 screenOffTimeout =
-                        getScreenOffTimeoutOverrideLocked(screenOffTimeout, screenDimDuration);
+                        getDefaultGroupScreenOffTimeoutOverrideLocked(screenOffTimeout,
+                                screenDimDuration);
                 screenDimDuration = getScreenDimDurationLocked(screenOffTimeout);
+            } else {
+                screenOffTimeout = powerGroup.getScreenOffTimeoutOverrideLocked(screenOffTimeout);
+                screenDimDuration =
+                        powerGroup.getScreenDimDurationOverrideLocked(screenDimDuration);
+                if (sleepTimeout > 0 && screenOffTimeout > 0) {
+                    // If both sleep and screen off timeouts are set, make sure that the sleep
+                    // timeout is not smaller than the screen off one.
+                    sleepTimeout = Math.max(sleepTimeout, screenOffTimeout);
+                }
             }
 
             if (wakefulness != WAKEFULNESS_ASLEEP) {
@@ -3273,7 +3285,8 @@
 
     @VisibleForTesting
     @GuardedBy("mLock")
-    long getScreenOffTimeoutOverrideLocked(long screenOffTimeout, long screenDimDuration) {
+    long getDefaultGroupScreenOffTimeoutOverrideLocked(long screenOffTimeout,
+            long screenDimDuration) {
         long shortestScreenOffTimeout = screenOffTimeout;
         if (mScreenTimeoutOverridePolicy != null) {
             shortestScreenOffTimeout =
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 48174a6..940a509 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -809,17 +809,16 @@
         mKernelSingleUidTimeReader.addDelta(uid, onBatteryScreenOffCounter, elapsedRealtimeMs);
 
         if (u.mChildUids != null) {
-            LongArrayMultiStateCounter.LongArrayContainer deltaContainer =
-                    getCpuTimeInFreqContainer();
+            long[] delta = getCpuTimeInFreqContainer();
             int childUidCount = u.mChildUids.size();
             for (int j = childUidCount - 1; j >= 0; --j) {
                 LongArrayMultiStateCounter cpuTimeInFreqCounter =
                         u.mChildUids.valueAt(j).cpuTimeInFreqCounter;
                 if (cpuTimeInFreqCounter != null) {
                     mKernelSingleUidTimeReader.addDelta(u.mChildUids.keyAt(j),
-                            cpuTimeInFreqCounter, elapsedRealtimeMs, deltaContainer);
-                    onBatteryCounter.addCounts(deltaContainer);
-                    onBatteryScreenOffCounter.addCounts(deltaContainer);
+                            cpuTimeInFreqCounter, elapsedRealtimeMs, delta);
+                    onBatteryCounter.addCounts(delta);
+                    onBatteryScreenOffCounter.addCounts(delta);
                 }
             }
         }
@@ -890,8 +889,7 @@
                     if (childUid != null) {
                         final LongArrayMultiStateCounter counter = childUid.cpuTimeInFreqCounter;
                         if (counter != null) {
-                            final LongArrayMultiStateCounter.LongArrayContainer deltaContainer =
-                                    getCpuTimeInFreqContainer();
+                            final long[] deltaContainer = getCpuTimeInFreqContainer();
                             mKernelSingleUidTimeReader.addDelta(uid, counter, elapsedRealtimeMs,
                                     deltaContainer);
                             onBatteryCounter.addCounts(deltaContainer);
@@ -1741,7 +1739,7 @@
 
     private long mBatteryTimeToFullSeconds = -1;
 
-    private LongArrayMultiStateCounter.LongArrayContainer mTmpCpuTimeInFreq;
+    private long[] mTmpCpuTimeInFreq;
 
     /**
      * Times spent by the system server threads handling incoming binder requests.
@@ -10956,9 +10954,7 @@
 
                     // Set initial values to all 0. This is a child UID and we want to include
                     // the entirety of its CPU time-in-freq stats into the parent's stats.
-                    cpuTimeInFreqCounter.updateValues(
-                            new LongArrayMultiStateCounter.LongArrayContainer(cpuFreqCount),
-                            timestampMs);
+                    cpuTimeInFreqCounter.updateValues(new long[cpuFreqCount], timestampMs);
                 } else {
                     cpuTimeInFreqCounter = null;
                 }
@@ -11361,11 +11357,9 @@
     }
 
     @GuardedBy("this")
-    private LongArrayMultiStateCounter.LongArrayContainer getCpuTimeInFreqContainer() {
+    private long[] getCpuTimeInFreqContainer() {
         if (mTmpCpuTimeInFreq == null) {
-            mTmpCpuTimeInFreq =
-                    new LongArrayMultiStateCounter.LongArrayContainer(
-                            mCpuScalingPolicies.getScalingStepCount());
+            mTmpCpuTimeInFreq = new long[mCpuScalingPolicies.getScalingStepCount()];
         }
         return mTmpCpuTimeInFreq;
     }
diff --git a/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsAccumulator.java b/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsAccumulator.java
index e798bc4..3f7fcee 100644
--- a/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsAccumulator.java
+++ b/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsAccumulator.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.net.NetworkStats;
 import android.net.NetworkTemplate;
+import android.util.Log;
 
 import java.util.Objects;
 
@@ -33,6 +34,7 @@
  */
 public class NetworkStatsAccumulator {
 
+    private static final String TAG = "NetworkStatsAccumulator";
     private final NetworkTemplate mTemplate;
     private final boolean mWithTags;
     private final long mBucketDurationMillis;
@@ -57,8 +59,9 @@
     @NonNull
     public NetworkStats queryStats(long currentTimeMillis,
             @NonNull StatsQueryFunction queryFunction) {
-        maybeExpandSnapshot(currentTimeMillis, queryFunction);
-        return snapshotPlusFollowingStats(currentTimeMillis, queryFunction);
+        NetworkStats completeStats = snapshotPlusFollowingStats(currentTimeMillis, queryFunction);
+        maybeExpandSnapshot(currentTimeMillis, completeStats, queryFunction);
+        return completeStats;
     }
 
     /**
@@ -72,15 +75,28 @@
      * Expands the internal cumulative stats snapshot, if possible, by querying NetworkStats.
      */
     private void maybeExpandSnapshot(long currentTimeMillis,
+            NetworkStats completeStatsUntilCurrentTime,
             @NonNull StatsQueryFunction queryFunction) {
         // Update snapshot only if it is possible to expand it by at least one full bucket, and only
         // if the new snapshot's end is not in the active bucket.
         long newEndTimeMillis = currentTimeMillis - mBucketDurationMillis;
         if (newEndTimeMillis - mSnapshotEndTimeMillis > mBucketDurationMillis) {
-            NetworkStats extraStats = queryFunction.queryNetworkStats(mTemplate, mWithTags,
-                    mSnapshotEndTimeMillis, newEndTimeMillis);
+            Log.v(TAG,
+                    "Expanding snapshot (mTemplate=" + mTemplate + ", mWithTags=" + mWithTags
+                            + ") from " + mSnapshotEndTimeMillis + " to " + newEndTimeMillis
+                            + " at " + currentTimeMillis);
+            NetworkStats extraStats = queryFunction.queryNetworkStats(
+                    mTemplate, mWithTags, mSnapshotEndTimeMillis, newEndTimeMillis);
             mSnapshot = mSnapshot.add(extraStats);
             mSnapshotEndTimeMillis = newEndTimeMillis;
+
+            // NetworkStats queries interpolate historical data using integers maths, which makes
+            // queries non-transitive: Query(t0, t1) + Query(t1, t2) <= Query(t0, t2).
+            // Compute interpolation data loss from moving the snapshot's end-point, and add it to
+            // the snapshot to avoid under-counting.
+            NetworkStats newStats = snapshotPlusFollowingStats(currentTimeMillis, queryFunction);
+            NetworkStats interpolationLoss = completeStatsUntilCurrentTime.subtract(newStats);
+            mSnapshot = mSnapshot.add(interpolationLoss);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 6964300..73ae51c 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -707,9 +707,6 @@
      */
     private boolean mOccludesParent;
 
-    /** Whether the activity have style floating */
-    private boolean mStyleFloating;
-
     /**
      * Unlike {@link #mOccludesParent} which can be changed at runtime. This is a static attribute
      * from the style of activity. Because we don't want {@link WindowContainer#getOrientation()}
@@ -791,10 +788,10 @@
     // and therefore #isLetterboxedForFixedOrientationAndAspectRatio returns false.
     private boolean mIsEligibleForFixedOrientationLetterbox;
 
-    // activity is not displayed?
-    // TODO: rename to mNoDisplay
-    @VisibleForTesting
-    boolean noDisplay;
+    /**
+     * Whether the activity is to be displayed. See {@link android.R.attr#windowNoDisplay}.
+     */
+    private boolean mNoDisplay;
     final boolean mShowForAllUsers;
     // TODO: Make this final
     int mTargetSdk;
@@ -1178,7 +1175,7 @@
                 pw.print(" inHistory="); pw.print(inHistory);
                 pw.print(" idle="); pw.println(idle);
         pw.print(prefix); pw.print("occludesParent="); pw.print(occludesParent());
-                pw.print(" noDisplay="); pw.print(noDisplay);
+                pw.print(" mNoDisplay="); pw.print(mNoDisplay);
                 pw.print(" immersive="); pw.print(immersive);
                 pw.print(" launchMode="); pw.println(launchMode);
         pw.print(prefix); pw.print("mActivityType=");
@@ -2011,20 +2008,19 @@
         if (ent != null) {
             final boolean styleTranslucent = ent.array.getBoolean(
                     com.android.internal.R.styleable.Window_windowIsTranslucent, false);
-            mStyleFloating = ent.array.getBoolean(
+            final boolean styleFloating = ent.array.getBoolean(
                     com.android.internal.R.styleable.Window_windowIsFloating, false);
-            mOccludesParent = !(styleTranslucent || mStyleFloating)
+            mOccludesParent = !(styleTranslucent || styleFloating)
                     // This style is propagated to the main window attributes with
                     // FLAG_SHOW_WALLPAPER from PhoneWindow#generateLayout.
                     || ent.array.getBoolean(R.styleable.Window_windowShowWallpaper, false);
             mStyleFillsParent = mOccludesParent;
-            noDisplay = ent.array.getBoolean(R.styleable.Window_windowNoDisplay, false);
+            mNoDisplay = ent.array.getBoolean(R.styleable.Window_windowNoDisplay, false);
             mOptOutEdgeToEdge = ent.array.getBoolean(
                     R.styleable.Window_windowOptOutEdgeToEdgeEnforcement, false);
         } else {
-            mStyleFloating = false;
             mStyleFillsParent = mOccludesParent = true;
-            noDisplay = false;
+            mNoDisplay = false;
             mOptOutEdgeToEdge = false;
         }
 
@@ -3091,8 +3087,16 @@
         return occludesParent(true /* includingFinishing */);
     }
 
-    boolean isStyleFloating() {
-        return mStyleFloating;
+    boolean isNoDisplay() {
+        return mNoDisplay;
+    }
+
+    /**
+     * Exposed only for testing and should not be used to modify value of {@link #mNoDisplay}.
+     */
+    @VisibleForTesting
+    void setIsNoDisplay(boolean isNoDisplay) {
+        mNoDisplay = isNoDisplay;
     }
 
     /** Returns true if this activity is not finishing, is opaque and fills the entire space of
@@ -3192,6 +3196,9 @@
         if (mWmService.mConstants.isPackageOptOutIgnoreActivityOrientationRequest(packageName)) {
             return false;
         }
+        if (mAppCompatController.mAllowRestrictedResizability.getAsBoolean()) {
+            return false;
+        }
         // If the user preference respects aspect ratio, then it becomes non-resizable.
         return !mAppCompatController.getAppCompatOverrides().getAppCompatAspectRatioOverrides()
                 .shouldApplyUserMinAspectRatioOverride();
@@ -6069,7 +6076,7 @@
     void notifyUnknownVisibilityLaunchedForKeyguardTransition() {
         // No display activities never add a window, so there is no point in waiting them for
         // relayout.
-        if (noDisplay || !isKeyguardLocked()) {
+        if (mNoDisplay || !isKeyguardLocked()) {
             return;
         }
 
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 05a96d9..2e2ca14 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1037,7 +1037,6 @@
         mLastStartReason = request.reason;
         mLastStartActivityTimeMs = System.currentTimeMillis();
 
-        final ActivityRecord previousStart = mLastStartActivityRecord;
         final IApplicationThread caller = request.caller;
         Intent intent = request.intent;
         NeededUriGrants intentGrants = request.intentGrants;
@@ -2350,7 +2349,8 @@
         // When there is a reused activity and the current result is a trampoline activity,
         // set the reused activity as the result.
         if (mLastStartActivityRecord != null
-                && (mLastStartActivityRecord.finishing || mLastStartActivityRecord.noDisplay)) {
+                && (mLastStartActivityRecord.finishing
+                    || mLastStartActivityRecord.isNoDisplay())) {
             mLastStartActivityRecord = targetTaskTop;
         }
 
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 30b53d1..4857b02e 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -1725,9 +1725,6 @@
             return;
         }
         Transition transit = task.mTransitionController.requestCloseTransitionIfNeeded(task);
-        if (transit == null) {
-            transit = task.mTransitionController.getCollectingTransition();
-        }
         if (transit != null) {
             transit.collectClose(task);
             if (!task.mTransitionController.useFullReadyTracking()) {
@@ -1739,7 +1736,15 @@
                 // before anything that may need it to wait (setReady(false)).
                 transit.setReady(task, true);
             }
+        } else {
+            // If we failed to create a transition, there might be already a currently collecting
+            // transition. Let's use it if possible.
+            transit = task.mTransitionController.getCollectingTransition();
+            if (transit != null) {
+                transit.collectClose(task);
+            }
         }
+
         // Consume the stopping activities immediately so activity manager won't skip killing
         // the process because it is still foreground state, i.e. RESUMED -> PAUSING set from
         // removeActivities -> finishIfPossible.
@@ -2478,7 +2483,7 @@
     /** Notifies that the top activity of the task is forced to be resizeable. */
     private void handleForcedResizableTaskIfNeeded(Task task, int reason) {
         final ActivityRecord topActivity = task.getTopNonFinishingActivity();
-        if (topActivity == null || topActivity.noDisplay
+        if (topActivity == null || topActivity.isNoDisplay()
                 || !topActivity.canForceResizeNonResizable(task.getWindowingMode())) {
             return;
         }
@@ -2894,10 +2899,9 @@
         private boolean mIncludeInvisibleAndFinishing;
         private boolean mIgnoringKeyguard;
 
-        ActivityRecord getOpaqueActivity(
-                @NonNull WindowContainer<?> container, boolean ignoringKeyguard) {
+        ActivityRecord getOpaqueActivity(@NonNull WindowContainer<?> container) {
             mIncludeInvisibleAndFinishing = true;
-            mIgnoringKeyguard = ignoringKeyguard;
+            mIgnoringKeyguard = true;
             return container.getActivity(this,
                     true /* traverseTopToBottom */, null /* boundary */);
         }
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
index 548c0a3..fa2c716 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
@@ -128,10 +128,11 @@
         }
         if (!aspectRatioOverrides.shouldOverrideMinAspectRatio()
                 && !AppCompatCameraPolicy.shouldOverrideMinAspectRatioForCamera(mActivityRecord)) {
-            if (mActivityRecord.isUniversalResizeable()) {
+            final float minAspectRatio = info.getMinAspectRatio();
+            if (minAspectRatio == 0 || mActivityRecord.isUniversalResizeable()) {
                 return 0;
             }
-            return info.getMinAspectRatio();
+            return minAspectRatio;
         }
 
         if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY)
@@ -173,10 +174,11 @@
         if (mTransparentPolicy.isRunning()) {
             return mTransparentPolicy.getInheritedMaxAspectRatio();
         }
-        if (mActivityRecord.isUniversalResizeable()) {
+        final float maxAspectRatio = mActivityRecord.info.getMaxAspectRatio();
+        if (maxAspectRatio == 0 || mActivityRecord.isUniversalResizeable()) {
             return 0;
         }
-        return mActivityRecord.info.getMaxAspectRatio();
+        return maxAspectRatio;
     }
 
     @Nullable
diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java
index 6c344c6..145a376 100644
--- a/services/core/java/com/android/server/wm/AppCompatController.java
+++ b/services/core/java/com/android/server/wm/AppCompatController.java
@@ -15,12 +15,15 @@
  */
 package com.android.server.wm;
 
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY;
+
 import android.annotation.NonNull;
 import android.content.pm.PackageManager;
 
 import com.android.server.wm.utils.OptPropFactory;
 
 import java.io.PrintWriter;
+import java.util.function.BooleanSupplier;
 
 /**
  * Allows the interaction with all the app compat policies and configurations
@@ -47,6 +50,8 @@
     private final AppCompatLetterboxPolicy mAppCompatLetterboxPolicy;
     @NonNull
     private final AppCompatSizeCompatModePolicy mAppCompatSizeCompatModePolicy;
+    @NonNull
+    final BooleanSupplier mAllowRestrictedResizability;
 
     AppCompatController(@NonNull WindowManagerService wmService,
                         @NonNull ActivityRecord activityRecord) {
@@ -70,6 +75,17 @@
                 mAppCompatOverrides, mTransparentPolicy, wmService.mAppCompatConfiguration);
         mAppCompatSizeCompatModePolicy = new AppCompatSizeCompatModePolicy(mActivityRecord,
                 mAppCompatOverrides);
+        mAllowRestrictedResizability = AppCompatUtils.asLazy(() -> {
+            try {
+                return packageManager.getPropertyAsUser(
+                        PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY,
+                        mActivityRecord.mActivityComponent.getPackageName(),
+                        mActivityRecord.mActivityComponent.getClassName(),
+                        mActivityRecord.mUserId).getBoolean();
+            } catch (PackageManager.NameNotFoundException e) {
+                return false;
+            }
+        });
     }
 
     @NonNull
diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java
index db76eb9..ebb50db 100644
--- a/services/core/java/com/android/server/wm/AppCompatUtils.java
+++ b/services/core/java/com/android/server/wm/AppCompatUtils.java
@@ -164,41 +164,46 @@
 
         appCompatTaskInfo.setIsFromLetterboxDoubleTap(reachabilityOverrides.isFromDoubleTap());
 
-        final Rect bounds = top.getBounds();
-        final Rect appBounds = getAppBounds(top);
-        appCompatTaskInfo.topActivityLetterboxWidth = bounds.width();
-        appCompatTaskInfo.topActivityLetterboxHeight = bounds.height();
-        appCompatTaskInfo.topActivityLetterboxAppWidth = appBounds.width();
-        appCompatTaskInfo.topActivityLetterboxAppHeight = appBounds.height();
+        final boolean isTopActivityLetterboxed = top.areBoundsLetterboxed();
+        appCompatTaskInfo.setTopActivityLetterboxed(isTopActivityLetterboxed);
+        if (isTopActivityLetterboxed) {
+            final Rect bounds = top.getBounds();
+            final Rect appBounds = getAppBounds(top);
+            appCompatTaskInfo.topActivityLetterboxWidth = bounds.width();
+            appCompatTaskInfo.topActivityLetterboxHeight = bounds.height();
+            appCompatTaskInfo.topActivityLetterboxAppWidth = appBounds.width();
+            appCompatTaskInfo.topActivityLetterboxAppHeight = appBounds.height();
 
-        // We need to consider if letterboxed or pillarboxed.
-        // TODO(b/336807329) Encapsulate reachability logic
-        appCompatTaskInfo.setLetterboxDoubleTapEnabled(reachabilityOverrides
-                .isLetterboxDoubleTapEducationEnabled());
-        if (appCompatTaskInfo.isLetterboxDoubleTapEnabled()) {
-            if (appCompatTaskInfo.isTopActivityPillarboxed()) {
-                if (reachabilityOverrides.allowHorizontalReachabilityForThinLetterbox()) {
-                    // Pillarboxed.
-                    appCompatTaskInfo.topActivityLetterboxHorizontalPosition =
-                            reachabilityOverrides.getLetterboxPositionForHorizontalReachability();
+            // We need to consider if letterboxed or pillarboxed.
+            // TODO(b/336807329) Encapsulate reachability logic
+            appCompatTaskInfo.setLetterboxDoubleTapEnabled(reachabilityOverrides
+                    .isLetterboxDoubleTapEducationEnabled());
+            if (appCompatTaskInfo.isLetterboxDoubleTapEnabled()) {
+                if (appCompatTaskInfo.isTopActivityPillarboxShaped()) {
+                    if (reachabilityOverrides.allowHorizontalReachabilityForThinLetterbox()) {
+                        // Pillarboxed.
+                        appCompatTaskInfo.topActivityLetterboxHorizontalPosition =
+                                reachabilityOverrides
+                                        .getLetterboxPositionForHorizontalReachability();
+                    } else {
+                        appCompatTaskInfo.setLetterboxDoubleTapEnabled(false);
+                    }
                 } else {
-                    appCompatTaskInfo.setLetterboxDoubleTapEnabled(false);
-                }
-            } else {
-                if (reachabilityOverrides.allowVerticalReachabilityForThinLetterbox()) {
-                    // Letterboxed.
-                    appCompatTaskInfo.topActivityLetterboxVerticalPosition =
-                            reachabilityOverrides.getLetterboxPositionForVerticalReachability();
-                } else {
-                    appCompatTaskInfo.setLetterboxDoubleTapEnabled(false);
+                    if (reachabilityOverrides.allowVerticalReachabilityForThinLetterbox()) {
+                        // Letterboxed.
+                        appCompatTaskInfo.topActivityLetterboxVerticalPosition =
+                                reachabilityOverrides.getLetterboxPositionForVerticalReachability();
+                    } else {
+                        appCompatTaskInfo.setLetterboxDoubleTapEnabled(false);
+                    }
                 }
             }
         }
+
         final boolean eligibleForAspectRatioButton =
                 !info.isTopActivityTransparent && !appCompatTaskInfo.isTopActivityInSizeCompat()
                         && aspectRatioOverrides.shouldEnableUserAspectRatioSettings();
         appCompatTaskInfo.setEligibleForUserAspectRatioButton(eligibleForAspectRatioButton);
-        appCompatTaskInfo.setTopActivityLetterboxed(top.areBoundsLetterboxed());
         appCompatTaskInfo.cameraCompatTaskInfo.freeformCameraCompatMode =
                 AppCompatCameraPolicy.getCameraCompatFreeformMode(top);
         appCompatTaskInfo.setHasMinAspectRatioOverride(top.mAppCompatController
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e827f44..e9e550e 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -159,7 +159,6 @@
 import static com.android.server.wm.utils.DisplayInfoOverrides.copyDisplayInfoFields;
 import static com.android.server.wm.utils.RegionUtils.forEachRectReverse;
 import static com.android.server.wm.utils.RegionUtils.rectListToRegion;
-import static com.android.window.flags.Flags.explicitRefreshRateHints;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -3426,14 +3425,6 @@
         if (!mWmService.mSupportsHighPerfTransitions) {
             return;
         }
-        if (!explicitRefreshRateHints()) {
-            if (enable) {
-                getPendingTransaction().setEarlyWakeupStart();
-            } else {
-                getPendingTransaction().setEarlyWakeupEnd();
-            }
-            return;
-        }
         if (enable) {
             if (mTransitionPrefSession == null) {
                 mTransitionPrefSession = mWmService.mSystemPerformanceHinter.createSession(
@@ -3446,10 +3437,6 @@
     }
 
     void enableHighFrameRate(boolean enable) {
-        if (!explicitRefreshRateHints()) {
-            // Done by RefreshRatePolicy.
-            return;
-        }
         if (enable) {
             if (mHighFrameRateSession == null) {
                 mHighFrameRateSession = mWmService.mSystemPerformanceHinter.createSession(
@@ -7072,7 +7059,7 @@
         @Override
         public void setImeInputTargetRequestedVisibility(boolean visible) {
             if (android.view.inputmethod.Flags.refactorInsetsController()) {
-                // TODO(b/329229469) we won't have the statsToken in all cases, but should still log
+                // TODO(b/353463205) we won't have the statsToken in all cases, but should still log
                 try {
                     mRemoteInsetsController.setImeInputTargetRequestedVisibility(visible);
                 } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index c7d57fe..76e8a70 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1735,9 +1735,9 @@
         }
 
         // Show IME over the keyguard if the target allows it.
-        final boolean showImeOverKeyguard = imeTarget != null && imeTarget.isVisible()
-                && win.mIsImWindow && (imeTarget.canShowWhenLocked()
-                        || !imeTarget.canBeHiddenByKeyguard());
+        final boolean showImeOverKeyguard =
+                imeTarget != null && win.mIsImWindow && imeTarget.isDisplayed() && (
+                        imeTarget.canShowWhenLocked() || !imeTarget.canBeHiddenByKeyguard());
         if (showImeOverKeyguard) {
             return false;
         }
@@ -3053,7 +3053,7 @@
             @InsetsType int insetsType) {
         for (int i = insetsState.sourceSize() - 1; i >= 0; i--) {
             final InsetsSource source = insetsState.sourceAt(i);
-            if ((source.getType() & insetsType) == 0 || !source.isVisible()) {
+            if ((source.getType() & insetsType) == 0) {
                 continue;
             }
             if (Rect.intersects(bounds, source.getFrame())) {
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index e9c6e93..5ed9612 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -100,13 +100,13 @@
             // isLeashReadyForDispatching (used to dispatch the leash of the control) is
             // depending on mGivenInsetsReady. Therefore, triggering notifyControlChanged here
             // again, so that the control with leash can be eventually dispatched
-            if (!mGivenInsetsReady && mServerVisible && !givenInsetsPending) {
+            if (!mGivenInsetsReady && isServerVisible() && !givenInsetsPending) {
                 mGivenInsetsReady = true;
                 ImeTracker.forLogging().onProgress(mStatsToken,
                         ImeTracker.PHASE_WM_POST_LAYOUT_NOTIFY_CONTROLS_CHANGED);
                 mStateController.notifyControlChanged(mControlTarget, this);
                 setImeShowing(true);
-            } else if (wasServerVisible && mServerVisible && mGivenInsetsReady
+            } else if (wasServerVisible && isServerVisible() && mGivenInsetsReady
                     && givenInsetsPending) {
                 // If the server visibility didn't change (still visible), and mGivenInsetsReady
                 // is set, we won't call into notifyControlChanged. Therefore, we can reset the
@@ -114,7 +114,7 @@
                 ImeTracker.forLogging().onCancelled(mStatsToken,
                         ImeTracker.PHASE_WM_POST_LAYOUT_NOTIFY_CONTROLS_CHANGED);
                 mStatsToken = null;
-            } else if (wasServerVisible && !mServerVisible) {
+            } else if (wasServerVisible && !isServerVisible()) {
                 setImeShowing(false);
             }
         }
@@ -134,11 +134,15 @@
     @Override
     protected boolean isLeashReadyForDispatching() {
         if (android.view.inputmethod.Flags.refactorInsetsController()) {
+            // We should only dispatch the leash, if the following conditions are fulfilled:
+            // 1. parent isLeashReadyForDispatching, 2. mGivenInsetsReady (means there are no
+            // givenInsetsPending), 3. the IME surface is drawn, 4. either the IME is
+            // serverVisible (the unfrozen state)
             final WindowState ws =
                     mWindowContainer != null ? mWindowContainer.asWindowState() : null;
             final boolean isDrawn = ws != null && ws.isDrawn();
             return super.isLeashReadyForDispatching()
-                    && mServerVisible && isDrawn && mGivenInsetsReady;
+                    && isServerVisible() && isDrawn && mGivenInsetsReady;
         } else {
             return super.isLeashReadyForDispatching();
         }
@@ -254,7 +258,7 @@
             // Refer WindowState#getImeControlTarget().
             target = target.getWindow().getImeControlTarget();
         }
-        // TODO(b/329229469) make sure that the statsToken of all callers is non-null (currently
+        // TODO(b/353463205) make sure that the statsToken of all callers is non-null (currently
         //  not the case)
         super.updateControlForTarget(target, force, statsToken);
         if (Flags.refactorInsetsController()) {
@@ -290,12 +294,14 @@
         changed |= mDisplayContent.onImeInsetsClientVisibilityUpdate();
         if (Flags.refactorInsetsController()) {
             if (changed) {
+                ImeTracker.forLogging().onProgress(statsToken,
+                        ImeTracker.PHASE_SERVER_UPDATE_CLIENT_VISIBILITY);
                 invokeOnImeRequestedChangedListener(mDisplayContent.getImeInputTarget(),
                         statsToken);
             } else {
-                // TODO(b/329229469) change phase and check cancelled / failed
+                // TODO(b/353463205) check cancelled / failed
                 ImeTracker.forLogging().onCancelled(statsToken,
-                        ImeTracker.PHASE_CLIENT_REPORT_REQUESTED_VISIBLE_TYPES);
+                        ImeTracker.PHASE_SERVER_UPDATE_CLIENT_VISIBILITY);
             }
         }
         return changed;
@@ -460,7 +466,7 @@
             // This can later become ready, so we don't want to cancel the pending request here.
             return;
         }
-        // TODO(b/329229469) check if this is still triggered, as we don't go into STATE_SHOW_IME
+        // TODO(b/353463205) check if this is still triggered, as we don't go into STATE_SHOW_IME
         //  (DefaultImeVisibilityApplier)
         if (android.view.inputmethod.Flags.refactorInsetsController()) {
             // The IME is drawn, so call into {@link WindowState#notifyInsetsControlChanged}
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 1d4d6eb..7276007 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -730,6 +730,10 @@
         return mFakeControlTarget;
     }
 
+    boolean isServerVisible() {
+        return mServerVisible;
+    }
+
     boolean isClientVisible() {
         return mClientVisible;
     }
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 5dddf36..4b2d454 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -317,9 +317,9 @@
             // aborted.
             provider.updateFakeControlTarget(target);
         } else {
-            // TODO(b/329229469) if the IME controlTarget changes, any pending requests should fail
+            // TODO(b/353463205) if the IME controlTarget changes, any pending requests should fail
             provider.updateControlForTarget(target, false /* force */,
-                    null /* TODO(b/329229469) check if needed here */);
+                    null /* TODO(b/353463205) check if needed here */);
 
             // Get control target again in case the provider didn't accept the one we passed to it.
             target = provider.getControlTarget();
diff --git a/services/core/java/com/android/server/wm/RefreshRatePolicy.java b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
index 8cab7d9..e4c34ed 100644
--- a/services/core/java/com/android/server/wm/RefreshRatePolicy.java
+++ b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
@@ -19,8 +19,6 @@
 import static android.hardware.display.DisplayManager.SWITCHING_TYPE_NONE;
 import static android.hardware.display.DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY;
 
-import static com.android.window.flags.Flags.explicitRefreshRateHints;
-
 import android.hardware.display.DisplayManager;
 import android.view.Display;
 import android.view.Display.Mode;
@@ -60,7 +58,6 @@
     }
 
     private final DisplayInfo mDisplayInfo;
-    private final Mode mDefaultMode;
     private final Mode mLowRefreshRateMode;
     private final PackageRefreshRate mNonHighRefreshRatePackages = new PackageRefreshRate();
     private final HighRefreshRateDenylist mHighRefreshRateDenylist;
@@ -92,8 +89,7 @@
     RefreshRatePolicy(WindowManagerService wmService, DisplayInfo displayInfo,
             HighRefreshRateDenylist denylist) {
         mDisplayInfo = displayInfo;
-        mDefaultMode = displayInfo.getDefaultMode();
-        mLowRefreshRateMode = findLowRefreshRateMode(displayInfo, mDefaultMode);
+        mLowRefreshRateMode = findLowRefreshRateMode(displayInfo);
         mHighRefreshRateDenylist = denylist;
         mWmService = wmService;
     }
@@ -102,7 +98,8 @@
      * Finds the mode id with the lowest refresh rate which is >= 60hz and same resolution as the
      * default mode.
      */
-    private Mode findLowRefreshRateMode(DisplayInfo displayInfo, Mode defaultMode) {
+    private Mode findLowRefreshRateMode(DisplayInfo displayInfo) {
+        final Mode defaultMode = displayInfo.getDefaultMode();
         float[] refreshRates = displayInfo.getDefaultRefreshRates();
         float bestRefreshRate = defaultMode.getRefreshRate();
         mMinSupportedRefreshRate = bestRefreshRate;
@@ -135,33 +132,6 @@
             // Unspecified, use default mode.
             return 0;
         }
-
-        // If app is animating, it's not able to control refresh rate because we want the animation
-        // to run in default refresh rate. But if the display size of default mode is different
-        // from the using preferred mode, then still keep the preferred mode to avoid disturbing
-        // the animation.
-        if (!explicitRefreshRateHints() && w.isAnimationRunningSelfOrParent()) {
-            Display.Mode preferredMode = null;
-            for (Display.Mode mode : mDisplayInfo.supportedModes) {
-                if (preferredDisplayModeId == mode.getModeId()) {
-                    preferredMode = mode;
-                    break;
-                }
-            }
-            if (preferredMode != null) {
-                final int pW = preferredMode.getPhysicalWidth();
-                final int pH = preferredMode.getPhysicalHeight();
-                if ((pW != mDefaultMode.getPhysicalWidth()
-                        || pH != mDefaultMode.getPhysicalHeight())
-                        && pW == mDisplayInfo.getNaturalWidth()
-                        && pH == mDisplayInfo.getNaturalHeight()) {
-                    // Prefer not to change display size when animating.
-                    return preferredDisplayModeId;
-                }
-            }
-            return 0;
-        }
-
         return preferredDisplayModeId;
     }
 
@@ -264,12 +234,6 @@
             return w.mFrameRateVote.reset();
         }
 
-        // If app is animating, it's not able to control refresh rate because we want the animation
-        // to run in default refresh rate.
-        if (!explicitRefreshRateHints() && w.isAnimationRunningSelfOrParent()) {
-            return w.mFrameRateVote.reset();
-        }
-
         // If the app set a preferredDisplayModeId, the preferred refresh rate is the refresh rate
         // of that mode id.
         if (refreshRateSwitchingType != SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY) {
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 077127c..1bb4c41 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -715,7 +715,7 @@
                 if (embeddedWindow != null) {
                     // If there is no WindowState for the IWindow, it could be still an
                     // EmbeddedWindow. Therefore, check the EmbeddedWindowController as well
-                    // TODO(b/329229469) Use different phase here
+                    // TODO(b/353463205) Use different phase here
                     ImeTracker.forLogging().onProgress(imeStatsToken,
                             ImeTracker.PHASE_WM_UPDATE_REQUESTED_VISIBLE_TYPES);
                     embeddedWindow.setRequestedVisibleTypes(
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 7473acc..352dc52 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3432,9 +3432,9 @@
         info.isFocused = isFocused();
         info.isVisible = hasVisibleChildren();
         info.isVisibleRequested = isVisibleRequested();
+        info.isTopActivityNoDisplay = top != null && top.isNoDisplay();
         info.isSleeping = shouldSleepActivities();
         info.isTopActivityTransparent = top != null && !top.fillsParent();
-        info.isTopActivityStyleFloating = top != null && top.isStyleFloating();
         info.lastNonFullscreenBounds = topTask.mLastNonFullscreenBounds;
         final WindowState windowState = top != null ? top.findMainWindow() : null;
         info.requestedVisibleTypes = (windowState != null && Flags.enableFullyImmersiveInDesktop())
@@ -4724,7 +4724,7 @@
             }
         }
         if (likelyResolvedMode != WINDOWING_MODE_FULLSCREEN
-                && topActivity != null && !topActivity.noDisplay
+                && topActivity != null && !topActivity.isNoDisplay()
                 && topActivity.canForceResizeNonResizable(likelyResolvedMode)) {
             // Inform the user that they are starting an app that may not work correctly in
             // multi-window mode.
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 606d51d..e090b19 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1090,8 +1090,7 @@
             return true;
         }
         // Including finishing Activity if the TaskFragment is becoming invisible in the transition.
-        return mTaskSupervisor.mOpaqueActivityHelper.getOpaqueActivity(this,
-                true /* ignoringKeyguard */) == null;
+        return mTaskSupervisor.mOpaqueActivityHelper.getOpaqueActivity(this) == null;
     }
 
     /**
@@ -1734,6 +1733,12 @@
         if (!hasDirectChildActivities()) {
             return false;
         }
+        if (mResumedActivity != null && mTransitionController.isTransientLaunch(mResumedActivity)) {
+            // Even if the transient activity is occluded, defer pausing (addToStopping will still
+            // be called) it until the transient transition is done. So the current resuming
+            // activity won't need to wait for additional pause complete.
+            return false;
+        }
 
         ProtoLog.d(WM_DEBUG_STATES, "startPausing: taskFrag =%s " + "mResumedActivity=%s", this,
                 mResumedActivity);
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index 5c9a84d..c39671d 100644
--- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
@@ -449,7 +449,7 @@
 
         // If the source activity is a no-display activity, pass on the launch display area token
         // from source activity as currently preferred.
-        if (taskDisplayArea == null && source != null && source.noDisplay) {
+        if (taskDisplayArea == null && source != null && source.isNoDisplay()) {
             taskDisplayArea = source.mHandoverTaskDisplayArea;
             if (taskDisplayArea != null) {
                 if (DEBUG) appendLog("display-area-from-no-display-source=" + taskDisplayArea);
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 454e431..a603466 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -477,20 +477,17 @@
             if (transientRoot == null) continue;
             final WindowContainer<?> rootParent = transientRoot.getParent();
             if (rootParent == null || rootParent.getTopChild() == transientRoot) continue;
-            final ActivityRecord topOpaque = mController.mAtm.mTaskSupervisor.mOpaqueActivityHelper
-                    .getOpaqueActivity(rootParent, true /* ignoringKeyguard */);
-            if (transientRoot.compareTo(topOpaque.getRootTask()) < 0) {
-                occludedCount++;
+            for (int j = rootParent.getChildCount() - 1; j >= 0; --j) {
+                final WindowContainer<?> sibling = rootParent.getChildAt(j);
+                if (sibling == transientRoot) break;
+                if (!sibling.getWindowConfiguration().isAlwaysOnTop() && mController.mAtm
+                        .mTaskSupervisor.mOpaqueActivityHelper.getOpaqueActivity(sibling) != null) {
+                    occludedCount++;
+                    break;
+                }
             }
         }
         if (occludedCount == numTransient) {
-            for (int i = mTransientLaunches.size() - 1; i >= 0; --i) {
-                if (mTransientLaunches.keyAt(i).isDescendantOf(task)) {
-                    // Keep transient activity visible until transition finished, so it won't pause
-                    // with transient-hide tasks that may delay resuming the next top.
-                    return true;
-                }
-            }
             // Let transient-hide activities pause before transition is finished.
             return false;
         }
@@ -3589,6 +3586,10 @@
             if (wc.mWmService.mAtmService.mBackNavigationController.isMonitorTransitionTarget(wc)) {
                 flags |= TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
             }
+            final TaskDisplayArea tda = wc.asTaskDisplayArea();
+            if (tda != null) {
+                flags |= TransitionInfo.FLAG_IS_TASK_DISPLAY_AREA;
+            }
             final Task task = wc.asTask();
             if (task != null) {
                 final ActivityRecord topActivity = task.getTopNonFinishingActivity();
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index d01e29b..079170a 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -157,6 +157,7 @@
 import static com.android.server.wm.WindowStateProto.ANIMATOR;
 import static com.android.server.wm.WindowStateProto.ATTRIBUTES;
 import static com.android.server.wm.WindowStateProto.DESTROYING;
+import static com.android.server.wm.WindowStateProto.DIM_BOUNDS;
 import static com.android.server.wm.WindowStateProto.DISPLAY_ID;
 import static com.android.server.wm.WindowStateProto.FORCE_SEAMLESS_ROTATION;
 import static com.android.server.wm.WindowStateProto.GIVEN_CONTENT_INSETS;
@@ -181,7 +182,6 @@
 import static com.android.server.wm.WindowStateProto.VIEW_VISIBILITY;
 import static com.android.server.wm.WindowStateProto.WINDOW_CONTAINER;
 import static com.android.server.wm.WindowStateProto.WINDOW_FRAMES;
-import static com.android.window.flags.Flags.explicitRefreshRateHints;
 import static com.android.window.flags.Flags.secureWindowState;
 import static com.android.window.flags.Flags.surfaceTrustedOverlay;
 
@@ -4118,6 +4118,12 @@
                 mMergedLocalInsetsSources.valueAt(i).dumpDebug(proto, MERGED_LOCAL_INSETS_SOURCES);
             }
         }
+        if (getDimController() != null) {
+            final Rect dimBounds = getDimController().getDimBounds();
+            if (dimBounds != null) {
+                dimBounds.dumpDebug(proto, DIM_BOUNDS);
+            }
+        }
         proto.end(token);
     }
 
@@ -5297,12 +5303,9 @@
         if (voteChanged) {
             getPendingTransaction()
                     .setFrameRate(mSurfaceControl, mFrameRateVote.mRefreshRate,
-                        mFrameRateVote.mCompatibility, Surface.CHANGE_FRAME_RATE_ALWAYS);
-            if (explicitRefreshRateHints()) {
-                getPendingTransaction().setFrameRateSelectionStrategy(mSurfaceControl,
-                        mFrameRateVote.mSelectionStrategy);
-            }
-
+                            mFrameRateVote.mCompatibility, Surface.CHANGE_FRAME_RATE_ALWAYS)
+                    .setFrameRateSelectionStrategy(mSurfaceControl,
+                            mFrameRateVote.mSelectionStrategy);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 5bde8b5..44e237a 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -614,6 +614,12 @@
         final int rotation = getRelativeDisplayRotation();
         if (rotation == Surface.ROTATION_0) return mFixedRotationTransformLeash;
         if (mFixedRotationTransformLeash != null) return mFixedRotationTransformLeash;
+        if (ActivityTaskManagerService.isPip2ExperimentEnabled() && asActivityRecord() != null
+                && mTransitionController.getWindowingModeAtStart(
+                asActivityRecord()) == WINDOWING_MODE_PINNED) {
+            // PiP handles fixed rotation animation in Shell, so do not create the rotation leash.
+            return null;
+        }
 
         final SurfaceControl leash = makeSurface().setContainerLayer()
                 .setParent(getParentSurfaceControl())
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index ea0b02c..4dc3ca5 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -55,7 +55,6 @@
         "com_android_server_powerstats_PowerStatsService.cpp",
         "com_android_server_power_stats_CpuPowerStatsCollector.cpp",
         "com_android_server_hint_HintManagerService.cpp",
-        "com_android_server_SerialService.cpp",
         "com_android_server_soundtrigger_middleware_AudioSessionProviderImpl.cpp",
         "com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker.cpp",
         "com_android_server_stats_pull_StatsPullAtomService.cpp",
diff --git a/services/core/jni/com_android_server_SerialService.cpp b/services/core/jni/com_android_server_SerialService.cpp
deleted file mode 100644
index 6600c98..0000000
--- a/services/core/jni/com_android_server_SerialService.cpp
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2011 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.
- */
-
-#define LOG_TAG "SerialServiceJNI"
-#include "utils/Log.h"
-
-#include "jni.h"
-#include <nativehelper/JNIPlatformHelp.h>
-#include "android_runtime/AndroidRuntime.h"
-
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-
-namespace android
-{
-
-static struct parcel_file_descriptor_offsets_t
-{
-    jclass mClass;
-    jmethodID mConstructor;
-} gParcelFileDescriptorOffsets;
-
-static jobject android_server_SerialService_open(JNIEnv *env, jobject /* thiz */, jstring path)
-{
-    const char *pathStr = env->GetStringUTFChars(path, NULL);
-
-    int fd = open(pathStr, O_RDWR | O_NOCTTY);
-    if (fd < 0) {
-        ALOGE("could not open %s", pathStr);
-        env->ReleaseStringUTFChars(path, pathStr);
-        return NULL;
-    }
-    env->ReleaseStringUTFChars(path, pathStr);
-
-    jobject fileDescriptor = jniCreateFileDescriptor(env, fd);
-    if (fileDescriptor == NULL) {
-        close(fd);
-        return NULL;
-    }
-    return env->NewObject(gParcelFileDescriptorOffsets.mClass,
-        gParcelFileDescriptorOffsets.mConstructor, fileDescriptor);
-}
-
-
-static const JNINativeMethod method_table[] = {
-    { "native_open",                "(Ljava/lang/String;)Landroid/os/ParcelFileDescriptor;",
-                                    (void*)android_server_SerialService_open },
-};
-
-int register_android_server_SerialService(JNIEnv *env)
-{
-    jclass clazz = env->FindClass("com/android/server/SerialService");
-    if (clazz == NULL) {
-        ALOGE("Can't find com/android/server/SerialService");
-        return -1;
-    }
-
-    clazz = env->FindClass("android/os/ParcelFileDescriptor");
-    LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.ParcelFileDescriptor");
-    gParcelFileDescriptorOffsets.mClass = (jclass) env->NewGlobalRef(clazz);
-    gParcelFileDescriptorOffsets.mConstructor = env->GetMethodID(clazz, "<init>", "(Ljava/io/FileDescriptor;)V");
-    LOG_FATAL_IF(gParcelFileDescriptorOffsets.mConstructor == NULL,
-                 "Unable to find constructor for android.os.ParcelFileDescriptor");
-
-    return jniRegisterNativeMethods(env, "com/android/server/SerialService",
-            method_table, NELEM(method_table));
-}
-
-};
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 416e60f..e4ac826 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -110,6 +110,7 @@
     jmethodID notifyInputDevicesChanged;
     jmethodID notifyTouchpadHardwareState;
     jmethodID notifyTouchpadGestureInfo;
+    jmethodID notifyTouchpadThreeFingerTap;
     jmethodID notifySwitch;
     jmethodID notifyInputChannelBroken;
     jmethodID notifyNoFocusedWindowAnr;
@@ -345,6 +346,7 @@
     void setTouchpadTapDraggingEnabled(bool enabled);
     void setShouldNotifyTouchpadHardwareState(bool enabled);
     void setTouchpadRightClickZoneEnabled(bool enabled);
+    void setTouchpadThreeFingerTapShortcutEnabled(bool enabled);
     void setInputDeviceEnabled(uint32_t deviceId, bool enabled);
     void setShowTouches(bool enabled);
     void setNonInteractiveDisplays(const std::set<ui::LogicalDisplayId>& displayIds);
@@ -370,6 +372,7 @@
     void notifyTouchpadHardwareState(const SelfContainedHardwareState& schs,
                                      int32_t deviceId) override;
     void notifyTouchpadGestureInfo(enum GestureType type, int32_t deviceId) override;
+    void notifyTouchpadThreeFingerTap() override;
     std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay(
             const InputDeviceIdentifier& identifier,
             const std::optional<KeyboardLayoutInfo> keyboardLayoutInfo) override;
@@ -510,6 +513,10 @@
         // into context (a.k.a. "right") clicks.
         bool touchpadRightClickZoneEnabled{false};
 
+        // True to use three-finger tap as a customizable shortcut; false to use it as a
+        // middle-click.
+        bool touchpadThreeFingerTapShortcutEnabled{false};
+
         // True if a pointer icon should be shown for stylus pointers.
         bool stylusPointerIconEnabled{false};
 
@@ -780,6 +787,8 @@
         outConfig->touchpadTapDraggingEnabled = mLocked.touchpadTapDraggingEnabled;
         outConfig->shouldNotifyTouchpadHardwareState = mLocked.shouldNotifyTouchpadHardwareState;
         outConfig->touchpadRightClickZoneEnabled = mLocked.touchpadRightClickZoneEnabled;
+        outConfig->touchpadThreeFingerTapShortcutEnabled =
+                mLocked.touchpadThreeFingerTapShortcutEnabled;
 
         outConfig->disabledDevices = mLocked.disabledInputDevices;
 
@@ -1034,6 +1043,13 @@
     checkAndClearExceptionFromCallback(env, "notifyTouchpadGestureInfo");
 }
 
+void NativeInputManager::notifyTouchpadThreeFingerTap() {
+    ATRACE_CALL();
+    JNIEnv* env = jniEnv();
+    env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyTouchpadThreeFingerTap);
+    checkAndClearExceptionFromCallback(env, "notifyTouchpadThreeFingerTap");
+}
+
 std::shared_ptr<KeyCharacterMap> NativeInputManager::getKeyboardLayoutOverlay(
         const InputDeviceIdentifier& identifier,
         const std::optional<KeyboardLayoutInfo> keyboardLayoutInfo) {
@@ -1495,6 +1511,22 @@
             InputReaderConfiguration::Change::TOUCHPAD_SETTINGS);
 }
 
+void NativeInputManager::setTouchpadThreeFingerTapShortcutEnabled(bool enabled) {
+    { // acquire lock
+        std::scoped_lock _l(mLock);
+
+        if (mLocked.touchpadThreeFingerTapShortcutEnabled == enabled) {
+            return;
+        }
+
+        ALOGI("Setting touchpad three finger tap shortcut to %s.", toString(enabled));
+        mLocked.touchpadThreeFingerTapShortcutEnabled = enabled;
+    } // release lock
+
+    mInputManager->getReader().requestRefreshConfiguration(
+            InputReaderConfiguration::Change::TOUCHPAD_SETTINGS);
+}
+
 void NativeInputManager::setInputDeviceEnabled(uint32_t deviceId, bool enabled) {
     bool refresh = false;
 
@@ -2437,6 +2469,11 @@
     im->setTouchpadRightClickZoneEnabled(enabled);
 }
 
+static void nativeSetTouchpadThreeFingerTapShortcutEnabled(JNIEnv* env, jobject nativeImplObj,
+                                                           jboolean enabled) {
+    getNativeInputManager(env, nativeImplObj)->setTouchpadThreeFingerTapShortcutEnabled(enabled);
+}
+
 static void nativeSetShowTouches(JNIEnv* env, jobject nativeImplObj, jboolean enabled) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
@@ -3119,6 +3156,8 @@
         {"setShouldNotifyTouchpadHardwareState", "(Z)V",
          (void*)nativeSetShouldNotifyTouchpadHardwareState},
         {"setTouchpadRightClickZoneEnabled", "(Z)V", (void*)nativeSetTouchpadRightClickZoneEnabled},
+        {"setTouchpadThreeFingerTapShortcutEnabled", "(Z)V",
+         (void*)nativeSetTouchpadThreeFingerTapShortcutEnabled},
         {"setShowTouches", "(Z)V", (void*)nativeSetShowTouches},
         {"setNonInteractiveDisplays", "([I)V", (void*)nativeSetNonInteractiveDisplays},
         {"reloadCalibration", "()V", (void*)nativeReloadCalibration},
@@ -3229,6 +3268,8 @@
     GET_METHOD_ID(gServiceClassInfo.notifyTouchpadGestureInfo, clazz, "notifyTouchpadGestureInfo",
                   "(II)V")
 
+    GET_METHOD_ID(gServiceClassInfo.notifyTouchpadThreeFingerTap, clazz,
+                  "notifyTouchpadThreeFingerTap", "()V")
     GET_METHOD_ID(gServiceClassInfo.notifySwitch, clazz,
             "notifySwitch", "(JII)V");
 
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 3c55d18..59d7365 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -33,7 +33,6 @@
 int register_android_server_power_stats_CpuPowerStatsCollector(JNIEnv* env);
 int register_android_server_HintManagerService(JNIEnv* env);
 int register_android_server_storage_AppFuse(JNIEnv* env);
-int register_android_server_SerialService(JNIEnv* env);
 int register_android_server_SystemServer(JNIEnv* env);
 int register_android_server_UsbAlsaJackDetector(JNIEnv* env);
 int register_android_server_UsbAlsaMidiDevice(JNIEnv* env);
@@ -94,7 +93,6 @@
     register_android_server_PowerStatsService(env);
     register_android_server_power_stats_CpuPowerStatsCollector(env);
     register_android_server_HintManagerService(env);
-    register_android_server_SerialService(env);
     register_android_server_InputManager(env);
     register_android_server_LightsService(env);
     register_android_server_UsbDeviceManager(vm, env);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index a58da81..9841058 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -90,6 +90,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.CompletableFuture;
 
 /**
  * Class responsible for setting, resolving, and enforcing policies set by multiple management
@@ -1030,11 +1031,11 @@
         }
     }
 
-    private <V> void enforcePolicy(PolicyDefinition<V> policyDefinition,
+    private <V> CompletableFuture<Boolean> enforcePolicy(PolicyDefinition<V> policyDefinition,
             @Nullable PolicyValue<V> policyValue, int userId) {
         // null policyValue means remove any enforced policies, ensure callbacks handle this
         // properly
-        policyDefinition.enforcePolicy(
+        return policyDefinition.enforcePolicy(
                 policyValue == null ? null : policyValue.getValue(), mContext, userId);
     }
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index aca6f72..c653038 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -255,7 +255,6 @@
 import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_BLOCKING;
 import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK;
 import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
-import static android.provider.DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER;
 import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER;
 import static android.provider.Settings.Secure.MANAGED_PROVISIONING_DPC_DOWNLOADED;
 import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
@@ -462,7 +461,6 @@
 import android.provider.CalendarContract;
 import android.provider.ContactsContract.QuickContact;
 import android.provider.ContactsInternal;
-import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.provider.Settings.Global;
 import android.provider.Telephony;
@@ -908,10 +906,6 @@
                     + "management app's authentication policy";
     private static final String NOT_SYSTEM_CALLER_MSG = "Only the system can %s";
 
-    private static final String PERMISSION_BASED_ACCESS_EXPERIMENT_FLAG =
-            "enable_permission_based_access";
-    private static final boolean DEFAULT_VALUE_PERMISSION_BASED_ACCESS_FLAG = false;
-
     private static final int RETRY_COPY_ACCOUNT_ATTEMPTS = 3;
 
     /**
@@ -3486,7 +3480,7 @@
     @GuardedBy("getLockObject()")
     private boolean maybeMigrateSuspendedPackagesLocked(String backupId) {
         Slog.i(LOG_TAG, "Migrating suspended packages to policy engine");
-        if (!Flags.unmanagedModeMigration()) {
+        if (!Flags.suspendPackagesCoexistence()) {
             return false;
         }
         if (mOwners.isSuspendedPackagesMigrated()) {
@@ -3557,6 +3551,46 @@
         return true;
     }
 
+
+
+    @GuardedBy("getLockObject()")
+    private boolean maybeMigrateMemoryTaggingLocked(String backupId) {
+        if (!Flags.setMtePolicyCoexistence()) {
+            Slog.i(LOG_TAG, "Memory Tagging not migrated because coexistence "
+                    + "support is disabled.");
+            return false;
+        }
+        if (mOwners.isMemoryTaggingMigrated()) {
+            // TODO: Remove log after Flags.setMtePolicyCoexistence full rollout.
+            Slog.v(LOG_TAG, "Memory Tagging was previously migrated to policy engine.");
+            return false;
+        }
+
+        Slog.i(LOG_TAG, "Migrating Memory Tagging to policy engine");
+
+        // Create backup if none exists
+        mDevicePolicyEngine.createBackup(backupId);
+        try {
+            iterateThroughDpcAdminsLocked((admin, enforcingAdmin) -> {
+                if (admin.mtePolicy != 0) {
+                    Slog.i(LOG_TAG, "Setting Memory Tagging policy");
+                    mDevicePolicyEngine.setGlobalPolicy(
+                            PolicyDefinition.MEMORY_TAGGING,
+                            enforcingAdmin,
+                            new IntegerPolicyValue(admin.mtePolicy),
+                            true /* No need to re-set system properties */);
+                }
+            });
+        } catch (Exception e) {
+            Slog.wtf(LOG_TAG,
+                    "Failed to migrate Memory Tagging to policy engine", e);
+        }
+
+        Slog.i(LOG_TAG, "Marking Memory Tagging migration complete");
+        mOwners.markMemoryTaggingMigrated();
+        return true;
+    }
+
     /** Register callbacks for statsd pulled atoms. */
     private void registerStatsCallbacks() {
         final StatsManager statsManager = mContext.getSystemService(StatsManager.class);
@@ -4646,22 +4680,13 @@
     @GuardedBy("getLockObject()")
     private List<ActiveAdmin> getActiveAdminsForLockscreenPoliciesLocked(int userHandle) {
         if (isSeparateProfileChallengeEnabled(userHandle)) {
-
-            if (isPermissionCheckFlagEnabled()) {
-                return getActiveAdminsForAffectedUserInclPermissionBasedAdminLocked(userHandle);
-            }
             // If this user has a separate challenge, only return its restrictions.
             return getUserDataUnchecked(userHandle).mAdminList;
         }
         // If isSeparateProfileChallengeEnabled is false and userHandle points to a managed profile
         // we need to query the parent user who owns the credential.
-        if (isPermissionCheckFlagEnabled()) {
-            return getActiveAdminsForUserAndItsManagedProfilesInclPermissionBasedAdminLocked(getProfileParentId(userHandle),
-                    (user) -> !mLockPatternUtils.isSeparateProfileChallengeEnabled(user.id));
-        } else {
-            return getActiveAdminsForUserAndItsManagedProfilesLocked(getProfileParentId(userHandle),
-                    (user) -> !mLockPatternUtils.isSeparateProfileChallengeEnabled(user.id));
-        }
+        return getActiveAdminsForUserAndItsManagedProfilesLocked(getProfileParentId(userHandle),
+                (user) -> !mLockPatternUtils.isSeparateProfileChallengeEnabled(user.id));
 
     }
 
@@ -4684,33 +4709,6 @@
                 (user) -> mLockPatternUtils.isProfileWithUnifiedChallenge(user.id));
 
     }
-    /**
-     * Get the list of active admins for an affected user:
-     * <ul>
-     * <li>The active admins associated with the userHandle itself</li>
-     * <li>The parent active admins for each managed profile associated with the userHandle</li>
-     * <li>The permission based admin associated with the userHandle itself</li>
-     * </ul>
-     *
-     * @param userHandle the affected user for whom to get the active admins
-     * @return the list of active admins for the affected user
-     */
-    @GuardedBy("getLockObject()")
-    private List<ActiveAdmin> getActiveAdminsForAffectedUserInclPermissionBasedAdminLocked(
-            int userHandle) {
-        List<ActiveAdmin> list;
-
-        if (isManagedProfile(userHandle)) {
-            list = getUserDataUnchecked(userHandle).mAdminList;
-        }
-        list = getActiveAdminsForUserAndItsManagedProfilesInclPermissionBasedAdminLocked(userHandle,
-                /* shouldIncludeProfileAdmins */ (user) -> false);
-
-        if (getUserData(userHandle).mPermissionBasedAdmin != null) {
-            list.add(getUserData(userHandle).mPermissionBasedAdmin);
-        }
-        return list;
-    }
 
     /**
      * Returns the list of admins on the given user, as well as parent admins for each managed
@@ -4763,44 +4761,6 @@
         return mDevicePolicyEngine.getResolvedPolicyAcrossUsers(policyDefinition, users);
     }
 
-    /**
-     * Returns the list of admins on the given user, as well as parent admins for each managed
-     * profile associated with the given user. Optionally also include the admin of each managed
-     * profile.
-     * <p> Should not be called on a profile user.
-     */
-    @GuardedBy("getLockObject()")
-    private List<ActiveAdmin> getActiveAdminsForUserAndItsManagedProfilesInclPermissionBasedAdminLocked(int userHandle,
-            Predicate<UserInfo> shouldIncludeProfileAdmins) {
-        ArrayList<ActiveAdmin> admins = new ArrayList<>();
-        mInjector.binderWithCleanCallingIdentity(() -> {
-            for (UserInfo userInfo : mUserManager.getProfiles(userHandle)) {
-                DevicePolicyData policy = getUserDataUnchecked(userInfo.id);
-                if (userInfo.id == userHandle) {
-                    admins.addAll(policy.mAdminList);
-                    if (policy.mPermissionBasedAdmin != null) {
-                        admins.add(policy.mPermissionBasedAdmin);
-                    }
-                } else if (userInfo.isManagedProfile()) {
-                    for (int i = 0; i < policy.mAdminList.size(); i++) {
-                        ActiveAdmin admin = policy.mAdminList.get(i);
-                        if (admin.hasParentActiveAdmin()) {
-                            admins.add(admin.getParentActiveAdmin());
-                        }
-                        if (shouldIncludeProfileAdmins.test(userInfo)) {
-                            admins.add(admin);
-                        }
-                    }
-                    if (policy.mPermissionBasedAdmin != null
-                            && shouldIncludeProfileAdmins.test(userInfo)) {
-                        admins.add(policy.mPermissionBasedAdmin);
-                    }
-                }
-            }
-        });
-        return admins;
-    }
-
     private boolean isSeparateProfileChallengeEnabled(int userHandle) {
         return mInjector.binderWithCleanCallingIdentity(() ->
                 mLockPatternUtils.isSeparateProfileChallengeEnabled(userHandle));
@@ -4893,25 +4853,15 @@
         if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
             return;
         }
-        if (!isPermissionCheckFlagEnabled()) {
-            Objects.requireNonNull(who, "ComponentName is null");
-        }
+        Objects.requireNonNull(who, "ComponentName is null");
 
         Preconditions.checkArgumentNonnegative(timeout, "Timeout must be >= 0 ms");
         int userHandle = mInjector.userHandleGetCallingUserId();
         int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle;
         synchronized (getLockObject()) {
             ActiveAdmin ap;
-            if (isPermissionCheckFlagEnabled()) {
-                CallerIdentity caller = getCallerIdentity(who, callerPackageName);
-                ap = enforcePermissionAndGetEnforcingAdmin(
-                        who, MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
-                        caller.getPackageName(), affectedUserId)
-                        .getActiveAdmin();
-            } else {
-                ap = getActiveAdminForCallerLocked(
-                        who, DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD, parent);
-            }
+            ap = getActiveAdminForCallerLocked(
+                    who, DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD, parent);
             // Calling this API automatically bumps the expiration date
             final long expiration = timeout > 0L ? (timeout + System.currentTimeMillis()) : 0L;
             ap.passwordExpirationDate = expiration;
@@ -4972,28 +4922,14 @@
     @Override
     public boolean addCrossProfileWidgetProvider(ComponentName admin, String callerPackageName,
             String packageName) {
-        CallerIdentity caller;
+        CallerIdentity caller = getCallerIdentity(admin);
 
-        if (isPermissionCheckFlagEnabled()) {
-            caller = getCallerIdentity(admin, callerPackageName);
-        } else {
-            caller = getCallerIdentity(admin);
-        }
+        Objects.requireNonNull(admin, "ComponentName is null");
+        Preconditions.checkCallAuthorization(isProfileOwner(caller));
+
         ActiveAdmin activeAdmin;
-
-        if (isPermissionCheckFlagEnabled()) {
-            EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
-                    admin,
-                    MANAGE_DEVICE_POLICY_PROFILE_INTERACTION,
-                    caller.getPackageName(),
-                    caller.getUserId());
-            activeAdmin = enforcingAdmin.getActiveAdmin();
-        } else {
-            Objects.requireNonNull(admin, "ComponentName is null");
-            Preconditions.checkCallAuthorization(isProfileOwner(caller));
-            synchronized (getLockObject()) {
-                activeAdmin = getProfileOwnerLocked(caller.getUserId());
-            }
+        synchronized (getLockObject()) {
+            activeAdmin = getProfileOwnerLocked(caller.getUserId());
         }
         List<String> changedProviders = null;
 
@@ -5026,28 +4962,14 @@
     @Override
     public boolean removeCrossProfileWidgetProvider(ComponentName admin, String callerPackageName,
             String packageName) {
-        CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
-            caller = getCallerIdentity(admin, callerPackageName);
-        } else {
-            caller = getCallerIdentity(admin);
-        }
+        CallerIdentity caller = getCallerIdentity(admin);
+
+        Objects.requireNonNull(admin, "ComponentName is null");
+        Preconditions.checkCallAuthorization(isProfileOwner(caller));
 
         ActiveAdmin activeAdmin;
-
-        if (isPermissionCheckFlagEnabled()) {
-            EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
-                    admin,
-                    MANAGE_DEVICE_POLICY_PROFILE_INTERACTION,
-                    caller.getPackageName(),
-                    caller.getUserId());
-            activeAdmin = enforcingAdmin.getActiveAdmin();
-        } else {
-            Objects.requireNonNull(admin, "ComponentName is null");
-            Preconditions.checkCallAuthorization(isProfileOwner(caller));
-            synchronized (getLockObject()) {
-                activeAdmin = getProfileOwnerLocked(caller.getUserId());
-            }
+        synchronized (getLockObject()) {
+            activeAdmin = getProfileOwnerLocked(caller.getUserId());
         }
         List<String> changedProviders = null;
 
@@ -5080,27 +5002,14 @@
     @Override
     public List<String> getCrossProfileWidgetProviders(ComponentName admin,
             String callerPackageName) {
-        CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
-            caller = getCallerIdentity(admin, callerPackageName);
-        } else {
-            caller = getCallerIdentity(admin);
-        }
-        ActiveAdmin activeAdmin;
+        CallerIdentity caller = getCallerIdentity(admin);
 
-        if (isPermissionCheckFlagEnabled()) {
-            EnforcingAdmin enforcingAdmin = enforceCanQueryAndGetEnforcingAdmin(
-                    admin,
-                    MANAGE_DEVICE_POLICY_PROFILE_INTERACTION,
-                    caller.getPackageName(),
-                    caller.getUserId());
-            activeAdmin = enforcingAdmin.getActiveAdmin();
-        } else {
-            Objects.requireNonNull(admin, "ComponentName is null");
-            Preconditions.checkCallAuthorization(isProfileOwner(caller));
-            synchronized (getLockObject()) {
-                activeAdmin = getProfileOwnerLocked(caller.getUserId());
-            }
+        Objects.requireNonNull(admin, "ComponentName is null");
+        Preconditions.checkCallAuthorization(isProfileOwner(caller));
+
+        ActiveAdmin activeAdmin;
+        synchronized (getLockObject()) {
+            activeAdmin = getProfileOwnerLocked(caller.getUserId());
         }
 
         synchronized (getLockObject()) {
@@ -5449,24 +5358,17 @@
         enforceUserUnlocked(userHandle, parent);
 
         synchronized (getLockObject()) {
-            if (isPermissionCheckFlagEnabled()) {
-                int affectedUser = parent ? getProfileParentId(userHandle) : userHandle;
-                enforcePermission(MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
-                        callerPackageName, affectedUser);
-            } else {
-                // This API can only be called by an active device admin,
-                // so try to retrieve it to check that the caller is one.
-                getActiveAdminForCallerLocked(
-                        null, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
-            }
+            // This API can only be called by an active device admin,
+            // so try to retrieve it to check that the caller is one.
+            getActiveAdminForCallerLocked(
+                    null, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
 
             int credentialOwner = getCredentialOwner(userHandle, parent);
             DevicePolicyData policy = getUserDataUnchecked(credentialOwner);
             PasswordMetrics metrics = mLockSettingsInternal.getUserPasswordMetrics(credentialOwner);
             final int userToCheck = getProfileParentUserIfRequested(userHandle, parent);
-            boolean activePasswordSufficientForUserLocked = isActivePasswordSufficientForUserLocked(
+            return isActivePasswordSufficientForUserLocked(
                     policy.mPasswordValidAtLastCheckpoint, metrics, userToCheck);
-            return activePasswordSufficientForUserLocked;
         }
     }
 
@@ -5622,21 +5524,11 @@
                     isDefaultDeviceOwner(caller) || isProfileOwner(caller) || isSystemUid(caller),
                     "Only profile owner, device owner and system may call this method on parent.");
         } else {
-            if (isPermissionCheckFlagEnabled()) {
-                Preconditions.checkCallAuthorization(
-                        hasCallingOrSelfPermission(REQUEST_PASSWORD_COMPLEXITY)
-                                || hasCallingOrSelfPermission(MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS)
-                                || isDefaultDeviceOwner(caller) || isProfileOwner(caller),
-                        "Must have " + REQUEST_PASSWORD_COMPLEXITY + " or " +
-                                MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS
-                                + " permissions, or be a profile owner or device owner.");
-            } else {
-                Preconditions.checkCallAuthorization(
-                        hasCallingOrSelfPermission(REQUEST_PASSWORD_COMPLEXITY)
-                                || isDefaultDeviceOwner(caller) || isProfileOwner(caller),
-                        "Must have " + REQUEST_PASSWORD_COMPLEXITY
-                                + " permission, or be a profile owner or device owner.");
-            }
+            Preconditions.checkCallAuthorization(
+                    hasCallingOrSelfPermission(REQUEST_PASSWORD_COMPLEXITY)
+                            || isDefaultDeviceOwner(caller) || isProfileOwner(caller),
+                    "Must have " + REQUEST_PASSWORD_COMPLEXITY
+                            + " permission, or be a profile owner or device owner.");
         }
 
         synchronized (getLockObject()) {
@@ -5728,26 +5620,15 @@
     private void setRequiredPasswordComplexityPreCoexistence(
             String callerPackageName, int passwordComplexity, boolean calledOnParent) {
         CallerIdentity caller = getCallerIdentity(callerPackageName);
-        if (!isPermissionCheckFlagEnabled()) {
-            Preconditions.checkCallAuthorization(
-                    isDefaultDeviceOwner(caller) || isProfileOwner(caller));
-            Preconditions.checkArgument(!calledOnParent || isProfileOwner(caller));
-        }
+
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller));
+        Preconditions.checkArgument(!calledOnParent || isProfileOwner(caller));
 
         synchronized (getLockObject()) {
             ActiveAdmin admin;
-            if (isPermissionCheckFlagEnabled()) {
-                // TODO: Make sure this returns the parent of the fake admin
-                // TODO: Deal with null componentname
-                int affectedUser = calledOnParent
-                        ? getProfileParentId(caller.getUserId()) : caller.getUserId();
-                admin = enforcePermissionAndGetEnforcingAdmin(
-                        null, MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
-                        caller.getPackageName(), affectedUser).getActiveAdmin();
-            } else {
-                admin = getParentOfAdminIfRequired(
-                        getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()), calledOnParent);
-            }
+            admin = getParentOfAdminIfRequired(
+                    getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()), calledOnParent);
 
             if (admin.mPasswordComplexity != passwordComplexity) {
                 // We require the caller to explicitly clear any password quality requirements set
@@ -5907,14 +5788,8 @@
             if (!isSystemUid(caller)) {
                 // This API can be called by an active device admin or by keyguard code.
                 if (!hasCallingPermission(permission.ACCESS_KEYGUARD_SECURE_STORAGE)) {
-                    if (isPermissionCheckFlagEnabled()) {
-                        int affectedUser = parent ? getProfileParentId(userHandle) : userHandle;
-                        enforcePermission(MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
-                                callerPackageName, affectedUser);
-                    } else {
-                        getActiveAdminForCallerLocked(
-                                null, DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, parent);
-                    }
+                    getActiveAdminForCallerLocked(
+                            null, DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, parent);
                 }
             }
 
@@ -5931,31 +5806,18 @@
             return;
         }
 
-        if (!isPermissionCheckFlagEnabled()) {
-            Objects.requireNonNull(who, "ComponentName is null");
-        }
-
+        Objects.requireNonNull(who, "ComponentName is null");
 
         int userId = mInjector.userHandleGetCallingUserId();
         int affectedUserId = parent ? getProfileParentId(userId) : userId;
 
         synchronized (getLockObject()) {
-            ActiveAdmin ap;
-            if (isPermissionCheckFlagEnabled()) {
-                CallerIdentity caller = getCallerIdentity(who, callerPackageName);
-                ap = enforcePermissionAndGetEnforcingAdmin(
-                        who,
-                        /*permission=*/ MANAGE_DEVICE_POLICY_WIPE_DATA,
-                        /* adminPolicy=*/ DeviceAdminInfo.USES_POLICY_WIPE_DATA,
-                        caller.getPackageName(), affectedUserId).getActiveAdmin();
-            } else {
-                // This API can only be called by an active device admin,
-                // so try to retrieve it to check that the caller is one.
-                getActiveAdminForCallerLocked(
-                        who, DeviceAdminInfo.USES_POLICY_WIPE_DATA, parent);
-                ap = getActiveAdminForCallerLocked(
-                        who, DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, parent);
-            }
+            // This API can only be called by an active device admin,
+            // so try to retrieve it to check that the caller is one.
+            getActiveAdminForCallerLocked(
+                    who, DeviceAdminInfo.USES_POLICY_WIPE_DATA, parent);
+            ActiveAdmin ap = getActiveAdminForCallerLocked(
+                    who, DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, parent);
 
             if (ap.maximumFailedPasswordsForWipe != num) {
                 ap.maximumFailedPasswordsForWipe = num;
@@ -6210,25 +6072,14 @@
         if (!mHasFeature) {
             return;
         }
-        if (!isPermissionCheckFlagEnabled()) {
-            Objects.requireNonNull(who, "ComponentName is null");
-        }
+
+        Objects.requireNonNull(who, "ComponentName is null");
+
         int userHandle = mInjector.userHandleGetCallingUserId();
         int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle;
         synchronized (getLockObject()) {
-            ActiveAdmin ap;
-            if (isPermissionCheckFlagEnabled()) {
-                CallerIdentity caller = getCallerIdentity(who, callerPackageName);
-                ap = enforcePermissionAndGetEnforcingAdmin(
-                        who,
-                        /*permission=*/ MANAGE_DEVICE_POLICY_LOCK,
-                        /*AdminPolicy=*/DeviceAdminInfo.USES_POLICY_FORCE_LOCK,
-                        caller.getPackageName(),
-                        affectedUserId).getActiveAdmin();
-            } else {
-                ap = getActiveAdminForCallerLocked(
-                        who, DeviceAdminInfo.USES_POLICY_FORCE_LOCK, parent);
-            }
+            ActiveAdmin ap = getActiveAdminForCallerLocked(
+                    who, DeviceAdminInfo.USES_POLICY_FORCE_LOCK, parent);
 
             if (ap.maximumTimeToUnlock != timeMs) {
                 ap.maximumTimeToUnlock = timeMs;
@@ -6334,16 +6185,13 @@
         if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
             return;
         }
+
         Preconditions.checkArgument(timeoutMs >= 0, "Timeout must not be a negative number.");
-        CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
-            caller = getCallerIdentity(who, callerPackageName);
-        } else {
-            caller = getCallerIdentity(who);
-            Objects.requireNonNull(who, "ComponentName is null");
-            Preconditions.checkCallAuthorization(
-                    isDefaultDeviceOwner(caller) || isProfileOwner(caller));
-        }
+        CallerIdentity caller = getCallerIdentity(who);
+        Objects.requireNonNull(who, "ComponentName is null");
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller));
+
         // timeoutMs with value 0 means that the admin doesn't participate
         // timeoutMs is clamped to the interval in case the internal constants change in the future
         final long minimumStrongAuthTimeout = getMinimumStrongAuthTimeoutMs();
@@ -6357,17 +6205,8 @@
         final int userHandle = caller.getUserId();
         boolean changed = false;
         synchronized (getLockObject()) {
-            ActiveAdmin ap;
-            if (isPermissionCheckFlagEnabled()) {
-                int affectedUser = parent
-                        ? getProfileParentId(caller.getUserId()) : caller.getUserId();
-                ap = enforcePermissionAndGetEnforcingAdmin(
-                        who, MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
-                        caller.getPackageName(), affectedUser).getActiveAdmin();
-            } else {
-                ap = getParentOfAdminIfRequired(
-                        getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()), parent);
-            }
+            ActiveAdmin ap = getParentOfAdminIfRequired(
+                    getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()), parent);
             if (ap.strongAuthUnlockTimeout != timeoutMs) {
                 ap.strongAuthUnlockTimeout = timeoutMs;
                 saveSettingsLocked(userHandle);
@@ -6664,16 +6503,9 @@
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
         final boolean isCallerDelegate = isCallerDelegate(caller, DELEGATION_CERT_INSTALL);
         final boolean isCredentialManagementApp = isCredentialManagementApp(caller);
-        if (isPermissionCheckFlagEnabled()) {
-            Preconditions.checkCallAuthorization(
-                    hasPermission(MANAGE_DEVICE_POLICY_CERTIFICATES,
-                            caller.getPackageName(), caller.getUserId())
-                            || isCredentialManagementApp);
-        }  else {
-            Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                    && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
-                    || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp)));
-        }
+        Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+                && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
+                || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp)));
         if (isCredentialManagementApp) {
             Preconditions.checkCallAuthorization(!isUserSelectable, "The credential "
                     + "management app is not allowed to install a user selectable key pair");
@@ -6733,16 +6565,9 @@
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
         final boolean isCallerDelegate = isCallerDelegate(caller, DELEGATION_CERT_INSTALL);
         final boolean isCredentialManagementApp = isCredentialManagementApp(caller);
-        if (isPermissionCheckFlagEnabled()) {
-            Preconditions.checkCallAuthorization(
-                    hasPermission(MANAGE_DEVICE_POLICY_CERTIFICATES,
-                            caller.getPackageName(), caller.getUserId())
-                            || isCredentialManagementApp);
-        }  else {
-            Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                    && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
-                    || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp)));
-        }
+        Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+                && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
+                || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp)));
         if (isCredentialManagementApp) {
             Preconditions.checkCallAuthorization(
                     isAliasInCredentialManagementAppPolicy(caller, alias),
@@ -6802,13 +6627,8 @@
     }
 
     private boolean canInstallCertificates(CallerIdentity caller) {
-        if (isPermissionCheckFlagEnabled()) {
-            return hasPermission(MANAGE_DEVICE_POLICY_CERTIFICATES,
-                    caller.getPackageName(), caller.getUserId());
-        }  else {
-            return isProfileOwner(caller) || isDefaultDeviceOwner(caller)
-                    || isCallerDelegate(caller, DELEGATION_CERT_INSTALL);
-        }
+        return isProfileOwner(caller) || isDefaultDeviceOwner(caller)
+                || isCallerDelegate(caller, DELEGATION_CERT_INSTALL);
     }
 
     private boolean canChooseCertificates(CallerIdentity caller) {
@@ -7001,16 +6821,9 @@
                     caller.getPackageName(), caller.getUid()));
             enforceIndividualAttestationSupportedIfRequested(attestationUtilsFlags);
         } else {
-            if (isPermissionCheckFlagEnabled()) {
-                Preconditions.checkCallAuthorization(
-                        hasPermission(MANAGE_DEVICE_POLICY_CERTIFICATES,
-                                caller.getPackageName(), caller.getUserId())
-                                || isCredentialManagementApp);
-            }  else {
-                Preconditions.checkCallAuthorization((caller.hasAdminComponent() && (isProfileOwner(
-                        caller) || isDefaultDeviceOwner(caller))) || (caller.hasPackage() && (
-                        isCallerDelegate || isCredentialManagementApp)));
-            }
+            Preconditions.checkCallAuthorization((caller.hasAdminComponent() && (isProfileOwner(
+                    caller) || isDefaultDeviceOwner(caller))) || (caller.hasPackage() && (
+                    isCallerDelegate || isCredentialManagementApp)));
             if (isCredentialManagementApp) {
                 Preconditions.checkCallAuthorization(
                         isAliasInCredentialManagementAppPolicy(caller, alias),
@@ -7143,16 +6956,9 @@
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
         final boolean isCallerDelegate = isCallerDelegate(caller, DELEGATION_CERT_INSTALL);
         final boolean isCredentialManagementApp = isCredentialManagementApp(caller);
-        if (isPermissionCheckFlagEnabled()) {
-            Preconditions.checkCallAuthorization(
-                    hasPermission(MANAGE_DEVICE_POLICY_CERTIFICATES,
-                            caller.getPackageName(), caller.getUserId())
-                            || isCredentialManagementApp);
-        } else {
-            Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                    && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
-                    || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp)));
-        }
+        Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+                && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
+                || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp)));
         if (isCredentialManagementApp) {
             Preconditions.checkCallAuthorization(
                     isAliasInCredentialManagementAppPolicy(caller, alias),
@@ -8285,29 +8091,21 @@
         if (!mHasFeature) {
             return;
         }
-        if (!isPermissionCheckFlagEnabled()) {
-            Preconditions.checkNotNull(who, "ComponentName is null");
-        }
+
+        Preconditions.checkNotNull(who, "ComponentName is null");
+
         CallerIdentity caller = getCallerIdentity(who, callerPackageName);
-        if (!isPermissionCheckFlagEnabled()) {
-            Preconditions.checkCallAuthorization(
-                    isDefaultDeviceOwner(caller)
-                            || isProfileOwnerOfOrganizationOwnedDevice(caller));
-        }
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller)
+                        || isProfileOwnerOfOrganizationOwnedDevice(caller));
+
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager
                 .OPERATION_SET_FACTORY_RESET_PROTECTION_POLICY);
 
         final int frpManagementAgentUid = getFrpManagementAgentUidOrThrow();
         synchronized (getLockObject()) {
             ActiveAdmin admin;
-            if (isPermissionCheckFlagEnabled()) {
-                admin = enforcePermissionAndGetEnforcingAdmin(
-                        who, MANAGE_DEVICE_POLICY_FACTORY_RESET, caller.getPackageName(),
-                        UserHandle.USER_ALL)
-                        .getActiveAdmin();
-            } else {
-                admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
-            }
+            admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
             admin.mFactoryResetProtectionPolicy = policy;
             saveSettingsLocked(caller.getUserId());
         }
@@ -8347,7 +8145,7 @@
                                 || hasCallingPermission(permission.MASTER_CLEAR)
                                 || hasCallingPermission(MANAGE_DEVICE_POLICY_FACTORY_RESET),
                         "Must be called by the FRP management agent on device");
-                admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceOrSystemPermissionBasedAdminLocked();
+                admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
             } else {
                 Preconditions.checkCallAuthorization(
                         isDefaultDeviceOwner(caller)
@@ -10247,15 +10045,6 @@
         return admin;
     }
 
-    ActiveAdmin getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceOrSystemPermissionBasedAdminLocked() {
-        ensureLocked();
-        ActiveAdmin doOrPo = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
-        if (isPermissionCheckFlagEnabled() && doOrPo == null) {
-            return getUserData(0).mPermissionBasedAdmin;
-        }
-        return doOrPo;
-    }
-
     @Override
     public void clearDeviceOwner(String packageName) {
         Objects.requireNonNull(packageName, "packageName is null");
@@ -10998,8 +10787,6 @@
      *     (2.1.1) The caller is the profile owner.
      *     (2.1.2) The caller is from another app in the same user as the profile owner, AND
      *             the caller is the delegated cert installer.
-     * (3) The caller holds the
-     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CERTIFICATES} permission.
      *
      *  For the device owner case, simply check that the caller is the device owner or the
      *  delegated certificate installer.
@@ -11013,24 +10800,18 @@
     @VisibleForTesting
     boolean hasDeviceIdAccessUnchecked(String packageName, int uid) {
         final int userId = UserHandle.getUserId(uid);
-        // TODO(b/280048070): Introduce a permission to handle device ID access
-        if (isPermissionCheckFlagEnabled()
-                && !(isUidProfileOwnerLocked(uid) || isUidDeviceOwnerLocked(uid))) {
-            return hasPermission(MANAGE_DEVICE_POLICY_CERTIFICATES, packageName, userId);
-        } else {
-            ComponentName deviceOwner = getDeviceOwnerComponent(true);
-            if (deviceOwner != null && (deviceOwner.getPackageName().equals(packageName)
-                    || isCallerDelegate(packageName, uid, DELEGATION_CERT_INSTALL))) {
-                return true;
-            }
-            ComponentName profileOwner = getProfileOwnerAsUser(userId);
-            final boolean isCallerProfileOwnerOrDelegate = profileOwner != null
-                    && (profileOwner.getPackageName().equals(packageName)
-                    || isCallerDelegate(packageName, uid, DELEGATION_CERT_INSTALL));
-            if (isCallerProfileOwnerOrDelegate && (isProfileOwnerOfOrganizationOwnedDevice(userId)
-                    || isUserAffiliatedWithDevice(userId))) {
-                return true;
-            }
+        ComponentName deviceOwner = getDeviceOwnerComponent(true);
+        if (deviceOwner != null && (deviceOwner.getPackageName().equals(packageName)
+                || isCallerDelegate(packageName, uid, DELEGATION_CERT_INSTALL))) {
+            return true;
+        }
+        ComponentName profileOwner = getProfileOwnerAsUser(userId);
+        final boolean isCallerProfileOwnerOrDelegate = profileOwner != null
+                && (profileOwner.getPackageName().equals(packageName)
+                || isCallerDelegate(packageName, uid, DELEGATION_CERT_INSTALL));
+        if (isCallerProfileOwnerOrDelegate && (isProfileOwnerOfOrganizationOwnedDevice(userId)
+                || isUserAffiliatedWithDevice(userId))) {
+            return true;
         }
         return false;
     }
@@ -11731,25 +11512,12 @@
     @Override
     public void setDefaultSmsApplication(ComponentName admin, String callerPackageName,
             String packageName, boolean parent) {
-        CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
-            caller = getCallerIdentity(admin, callerPackageName);
-        } else {
-            caller = getCallerIdentity(admin);
-        }
+        CallerIdentity caller = getCallerIdentity(admin);
 
-        final int userId;
 
-        if (isPermissionCheckFlagEnabled()) {
-            enforcePermission(
-                    MANAGE_DEVICE_POLICY_DEFAULT_SMS,
-                    caller.getPackageName(),
-                    getAffectedUser(parent));
-        } else {
-            Objects.requireNonNull(admin, "ComponentName is null");
-            Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
-                    || isProfileOwnerOfOrganizationOwnedDevice(caller));
-        }
+        Objects.requireNonNull(admin, "ComponentName is null");
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
+                || isProfileOwnerOfOrganizationOwnedDevice(caller));
 
         if (!parent && isManagedProfile(caller.getUserId())
                 && getManagedSubscriptionsPolicy().getPolicyType()
@@ -11759,6 +11527,7 @@
                             + "ManagedSubscriptions policy is set");
         }
 
+        final int userId;
         if (parent) {
             userId = getProfileParentId(mInjector.userHandleGetCallingUserId());
             mInjector.binderWithCleanCallingIdentity(() -> enforcePackageIsSystemPackage(
@@ -11957,10 +11726,7 @@
             return;
         }
 
-        if (!isPermissionCheckFlagEnabled()) {
-            Objects.requireNonNull(admin, "admin is null");
-        }
-
+        Objects.requireNonNull(admin, "admin is null");
         Objects.requireNonNull(agent, "agent is null");
 
         PolicySizeVerifier.enforceMaxPackageNameLength(agent.getPackageName());
@@ -11972,19 +11738,8 @@
 
         int userHandle = mInjector.userHandleGetCallingUserId();
         synchronized (getLockObject()) {
-            ActiveAdmin ap;
-            if (isPermissionCheckFlagEnabled()) {
-                CallerIdentity caller = getCallerIdentity(admin, callerPackageName);
-                int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle;
-                ap = enforcePermissionAndGetEnforcingAdmin(
-                        admin,
-                        /*permission=*/MANAGE_DEVICE_POLICY_KEYGUARD,
-                        /*adminPolicy=*/DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES,
-                        caller.getPackageName(), affectedUserId).getActiveAdmin();
-            } else {
-                ap = getActiveAdminForCallerLocked(admin,
-                        DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES, parent);
-            }
+            ActiveAdmin ap = getActiveAdminForCallerLocked(admin,
+                    DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES, parent);
             checkCanExecuteOrThrowUnsafe(
                     DevicePolicyManager.OPERATION_SET_TRUST_AGENT_CONFIGURATION);
 
@@ -12080,27 +11835,16 @@
     @Override
     public void addCrossProfileIntentFilter(ComponentName who, String callerPackageName,
             IntentFilter filter, int flags) {
-        CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
-            caller = getCallerIdentity(who, callerPackageName);
-        } else {
-            caller = getCallerIdentity(who);
-        }
-        int callingUserId = caller.getUserId();
+        CallerIdentity caller = getCallerIdentity(who);
 
-        if (isPermissionCheckFlagEnabled()) {
-            enforcePermission(
-                    MANAGE_DEVICE_POLICY_PROFILE_INTERACTION,
-                    caller.getPackageName(),
-                    callingUserId);
-        } else {
-            Objects.requireNonNull(who, "ComponentName is null");
-            Preconditions.checkCallAuthorization(
-                    isProfileOwner(caller) || isDefaultDeviceOwner(caller));
-        }
+        Objects.requireNonNull(who, "ComponentName is null");
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
+
         synchronized (getLockObject()) {
             long id = mInjector.binderClearCallingIdentity();
             try {
+                int callingUserId = caller.getUserId();
                 UserInfo parent = mUserManager.getProfileParent(callingUserId);
                 if (parent == null) {
                     Slogf.e(LOG_TAG, "Cannot call addCrossProfileIntentFilter if there is no "
@@ -12144,28 +11888,16 @@
 
     @Override
     public void clearCrossProfileIntentFilters(ComponentName who, String callerPackageName) {
-        CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
-            caller = getCallerIdentity(who, callerPackageName);
-        } else {
-            caller = getCallerIdentity(who);
-        }
-        int callingUserId = caller.getUserId();
+        CallerIdentity caller = getCallerIdentity(who);
 
-        if (isPermissionCheckFlagEnabled()) {
-            enforcePermission(
-                    MANAGE_DEVICE_POLICY_PROFILE_INTERACTION,
-                    caller.getPackageName(),
-                    callingUserId);
-        } else {
-            Objects.requireNonNull(who, "ComponentName is null");
-            Preconditions.checkCallAuthorization(
-                    isProfileOwner(caller) || isDefaultDeviceOwner(caller));
-        }
+        Objects.requireNonNull(who, "ComponentName is null");
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
 
         synchronized (getLockObject()) {
             long id = mInjector.binderClearCallingIdentity();
             try {
+                int callingUserId = caller.getUserId();
                 UserInfo parent = mUserManager.getProfileParent(callingUserId);
                 if (parent == null) {
                     Slogf.e(LOG_TAG, "Cannot call clearCrossProfileIntentFilter if there is no "
@@ -13360,7 +13092,7 @@
     @Override
     public String[] setPackagesSuspended(ComponentName who, String callerPackage,
             String[] packageNames, boolean suspended) {
-        if (!Flags.unmanagedModeMigration()) {
+        if (!Flags.suspendPackagesCoexistence()) {
             return setPackagesSuspendedPreCoexistence(who, callerPackage, packageNames, suspended);
         }
 
@@ -13450,7 +13182,7 @@
     public boolean isPackageSuspended(ComponentName who, String callerPackage, String packageName) {
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
 
-        if (Flags.unmanagedModeMigration()) {
+        if (Flags.suspendPackagesCoexistence()) {
             enforcePermission(
                     MANAGE_DEVICE_POLICY_PACKAGE_STATE,
                     caller.getPackageName(),
@@ -15166,19 +14898,12 @@
         if (!mHasFeature) {
             return;
         }
-        CallerIdentity caller;
 
-        if (isPermissionCheckFlagEnabled()) {
-            caller = getCallerIdentity(who, callerPackageName);
-            enforcePermission(MANAGE_DEVICE_POLICY_WIFI, caller.getPackageName(),
-                    UserHandle.USER_ALL);
-        } else {
-            caller = getCallerIdentity(who);
-            Preconditions.checkNotNull(who, "ComponentName is null");
-            Preconditions.checkCallAuthorization(
-                    isDefaultDeviceOwner(caller)
-                            || isProfileOwnerOfOrganizationOwnedDevice(caller));
-        }
+        CallerIdentity caller = getCallerIdentity(who);
+        Preconditions.checkNotNull(who, "ComponentName is null");
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller)
+                        || isProfileOwnerOfOrganizationOwnedDevice(caller));
 
         mInjector.binderWithCleanCallingIdentity(() ->
                 mInjector.settingsGlobalPutInt(Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN,
@@ -15197,16 +14922,10 @@
             return false;
         }
         CallerIdentity caller = getCallerIdentity(who);
-        if (isPermissionCheckFlagEnabled()) {
-            enforcePermission(MANAGE_DEVICE_POLICY_WIFI, who.getPackageName(),
-                    UserHandle.USER_ALL);
-        } else {
-            Preconditions.checkNotNull(who, "ComponentName is null");
-
-            Preconditions.checkCallAuthorization(
-                    isDefaultDeviceOwner(caller)
-                            || isProfileOwnerOfOrganizationOwnedDevice(caller));
-        }
+        Preconditions.checkNotNull(who, "ComponentName is null");
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller)
+                        || isProfileOwnerOfOrganizationOwnedDevice(caller));
 
         return mInjector.binderWithCleanCallingIdentity(() ->
                 mInjector.settingsGlobalGetInt(Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) > 0);
@@ -15294,18 +15013,11 @@
 
     @Override
     public boolean setTime(@Nullable ComponentName who, String callerPackageName, long millis) {
-        CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
-            caller = getCallerIdentity(who, callerPackageName);
-            // This is a global action.
-            enforcePermission(SET_TIME, caller.getPackageName(), UserHandle.USER_ALL);
-        } else {
-            caller = getCallerIdentity(who);
-            Objects.requireNonNull(who, "ComponentName is null");
-            Preconditions.checkCallAuthorization(
-                    isDefaultDeviceOwner(caller)
-                            || isProfileOwnerOfOrganizationOwnedDevice(caller));
-        }
+        CallerIdentity caller = getCallerIdentity(who);
+        Objects.requireNonNull(who, "ComponentName is null");
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller)
+                        || isProfileOwnerOfOrganizationOwnedDevice(caller));
 
         // Don't allow set time when auto time is on.
         if (mInjector.settingsGlobalGetInt(Global.AUTO_TIME, 0) == 1) {
@@ -15322,18 +15034,11 @@
     @Override
     public boolean setTimeZone(@Nullable ComponentName who, String callerPackageName,
             String timeZone) {
-        CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
-            caller = getCallerIdentity(who, callerPackageName);
-            // This is a global action.
-            enforcePermission(SET_TIME_ZONE, caller.getPackageName(), UserHandle.USER_ALL);
-        } else {
-            caller = getCallerIdentity(who);
-            Objects.requireNonNull(who, "ComponentName is null");
-            Preconditions.checkCallAuthorization(
-                    isDefaultDeviceOwner(caller)
-                            || isProfileOwnerOfOrganizationOwnedDevice(caller));
-        }
+        CallerIdentity caller = getCallerIdentity(who);
+        Objects.requireNonNull(who, "ComponentName is null");
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller)
+                        || isProfileOwnerOfOrganizationOwnedDevice(caller));
 
         // Don't allow set timezone when auto timezone is on.
         if (mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) == 1) {
@@ -16537,22 +16242,15 @@
             policy.validateAgainstPreviousFreezePeriod(record.first, record.second,
                     LocalDate.now());
         }
-        CallerIdentity caller;
+
+        CallerIdentity caller = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(
+                isProfileOwnerOfOrganizationOwnedDevice(caller)
+                        || isDefaultDeviceOwner(caller));
+
+        checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_SYSTEM_UPDATE_POLICY);
 
         synchronized (getLockObject()) {
-            if (isPermissionCheckFlagEnabled()) {
-                caller = getCallerIdentity(who, callerPackageName);
-                enforcePermission(MANAGE_DEVICE_POLICY_SYSTEM_UPDATES, caller.getPackageName(),
-                        UserHandle.USER_ALL);
-            } else {
-                caller = getCallerIdentity(who);
-                Preconditions.checkCallAuthorization(
-                        isProfileOwnerOfOrganizationOwnedDevice(caller)
-                        || isDefaultDeviceOwner(caller));
-            }
-
-            checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_SYSTEM_UPDATE_POLICY);
-
             if (policy == null) {
                 mOwners.clearSystemUpdatePolicy();
             } else {
@@ -16699,7 +16397,6 @@
             if (!mUserManager.getUserInfo(UserHandle.getCallingUserId()).isMain()) {
                 Slogf.w(LOG_TAG, "Only the system update service in the main user can broadcast "
                         + "update information.");
-                return;
             }
         });
 
@@ -16723,7 +16420,7 @@
                 }
             }
             // Get running users.
-            final int runningUserIds[];
+            final int[] runningUserIds;
             try {
                 runningUserIds = mInjector.getIActivityManager().getRunningUserIds();
             } catch (RemoteException e) {
@@ -16966,10 +16663,7 @@
                 return false;
             }
         }
-        if (!isRuntimePermission(permission)) {
-            return false;
-        }
-        return true;
+        return isRuntimePermission(permission);
     }
 
     private void enforcePermissionGrantStateOnFinancedDevice(
@@ -17384,18 +17078,12 @@
 
     @Override
     public String getWifiMacAddress(ComponentName admin, String callerPackageName) {
-//        if (!isPermissionCheckFlagEnabled()) {
-            Objects.requireNonNull(admin, "ComponentName is null");
-//        }
+        Objects.requireNonNull(admin, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(admin, callerPackageName);
-//        if (isPermissionCheckFlagEnabled()) {
-//            enforcePermission(MANAGE_DEVICE_POLICY_WIFI, UserHandle.USER_ALL);
-//        } else {
-            Preconditions.checkCallAuthorization(
-                    isDefaultDeviceOwner(caller)
-                            || isProfileOwnerOfOrganizationOwnedDevice(caller));
-//        }
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller)
+                        || isProfileOwnerOfOrganizationOwnedDevice(caller));
 
         return mInjector.binderWithCleanCallingIdentity(() -> {
             String[] macAddresses = mInjector.getWifiManager().getFactoryMacAddresses();
@@ -17462,25 +17150,15 @@
         if (!mHasFeature) {
             return;
         }
-        CallerIdentity caller;
-        ActiveAdmin admin;
 
         message = PolicySizeVerifier.truncateIfLonger(message, MAX_SHORT_SUPPORT_MESSAGE_LENGTH);
 
-        if (isPermissionCheckFlagEnabled()) {
-            caller = getCallerIdentity(who, callerPackageName);
-            EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
-                    who,
-                    MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE,
-                    caller.getPackageName(),
-                    caller.getUserId());
-            admin = enforcingAdmin.getActiveAdmin();
-        } else {
-            caller = getCallerIdentity(who);
-            Objects.requireNonNull(who, "ComponentName is null");
-            synchronized (getLockObject()) {
-                admin = getActiveAdminForUidLocked(who, caller.getUid());
-            }
+        CallerIdentity caller = getCallerIdentity(who);
+        Objects.requireNonNull(who, "ComponentName is null");
+
+        ActiveAdmin admin;
+        synchronized (getLockObject()) {
+            admin = getActiveAdminForUidLocked(who, caller.getUid());
         }
 
         synchronized (getLockObject()) {
@@ -17501,23 +17179,13 @@
         if (!mHasFeature) {
             return null;
         }
-        CallerIdentity caller;
-        ActiveAdmin admin;
 
-        if (isPermissionCheckFlagEnabled()) {
-            caller = getCallerIdentity(who, callerPackageName);
-            EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
-                    who,
-                    MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE,
-                    caller.getPackageName(),
-                    caller.getUserId());
-            admin = enforcingAdmin.getActiveAdmin();
-        } else {
-            caller = getCallerIdentity(who);
-            Objects.requireNonNull(who, "ComponentName is null");
-            synchronized (getLockObject()) {
-                admin = getActiveAdminForUidLocked(who, caller.getUid());
-            }
+        CallerIdentity caller = getCallerIdentity(who);
+        Objects.requireNonNull(who, "ComponentName is null");
+
+        ActiveAdmin admin;
+        synchronized (getLockObject()) {
+            admin = getActiveAdminForUidLocked(who, caller.getUid());
         }
         return admin.shortSupportMessage;
     }
@@ -17680,26 +17348,14 @@
             return;
         }
         CallerIdentity caller = getCallerIdentity(who);
-        ActiveAdmin admin = null;
 
-        if (isPermissionCheckFlagEnabled()) {
-            EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
-                    who,
-                    MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
-                    caller.getPackageName(),
-                    caller.getUserId());
-            admin = enforcingAdmin.getActiveAdmin();
-        } else {
-            Objects.requireNonNull(who, "ComponentName is null");
-            Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
-        }
+        Objects.requireNonNull(who, "ComponentName is null");
+        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
 
         text = PolicySizeVerifier.truncateIfLonger(text, MAX_ORG_NAME_LENGTH);
 
         synchronized (getLockObject()) {
-            if (!isPermissionCheckFlagEnabled()) {
-                admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
-            }
+            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
             if (!TextUtils.equals(admin.organizationName, text)) {
                 admin.organizationName = (text == null || text.length() == 0)
                         ? null : text.toString();
@@ -17714,23 +17370,14 @@
             return null;
         }
         CallerIdentity caller = getCallerIdentity(who);
+
+        Objects.requireNonNull(who, "ComponentName is null");
+        Preconditions.checkCallingUser(isManagedProfile(caller.getUserId()));
+        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+
         ActiveAdmin admin;
-
-        if (isPermissionCheckFlagEnabled()) {
-            EnforcingAdmin enforcingAdmin = enforceCanQueryAndGetEnforcingAdmin(
-                    who,
-                    MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
-                    caller.getPackageName(),
-                    caller.getUserId());
-            admin = enforcingAdmin.getActiveAdmin();
-        } else {
-            Objects.requireNonNull(who, "ComponentName is null");
-            Preconditions.checkCallingUser(isManagedProfile(caller.getUserId()));
-            Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
-
-            synchronized (getLockObject()) {
-                admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
-            }
+        synchronized (getLockObject()) {
+            admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
         }
 
         return admin.organizationName;
@@ -18214,28 +17861,19 @@
         }
 
         final CallerIdentity caller = getCallerIdentity(admin, packageName);
-        if (isPermissionCheckFlagEnabled()) {
-            synchronized (getLockObject()) {
-                Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile()
-                        || areAllUsersAffiliatedWithDeviceLocked());
-                enforcePermission(MANAGE_DEVICE_POLICY_SECURITY_LOGGING, caller.getPackageName(),
-                        UserHandle.USER_ALL);
-            }
+        if (admin != null) {
+            Preconditions.checkCallAuthorization(
+                    isProfileOwnerOfOrganizationOwnedDevice(caller)
+                            || isDefaultDeviceOwner(caller));
         } else {
-            if (admin != null) {
-                Preconditions.checkCallAuthorization(
-                        isProfileOwnerOfOrganizationOwnedDevice(caller)
-                                || isDefaultDeviceOwner(caller));
-            } else {
-                // A delegate app passes a null admin component, which is expected
-                Preconditions.checkCallAuthorization(
-                        isCallerDelegate(caller, DELEGATION_SECURITY_LOGGING));
-            }
+            // A delegate app passes a null admin component, which is expected
+            Preconditions.checkCallAuthorization(
+                    isCallerDelegate(caller, DELEGATION_SECURITY_LOGGING));
+        }
 
-            synchronized (getLockObject()) {
-                Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile()
-                        || areAllUsersAffiliatedWithDeviceLocked());
-            }
+        synchronized (getLockObject()) {
+            Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile()
+                    || areAllUsersAffiliatedWithDeviceLocked());
         }
 
         DevicePolicyEventLogger
@@ -18259,7 +17897,7 @@
             return new ParceledListSlice<SecurityEvent>(output);
         } catch (IOException e) {
             Slogf.w(LOG_TAG, "Fail to read previous events" , e);
-            return new ParceledListSlice<SecurityEvent>(Collections.<SecurityEvent>emptyList());
+            return new ParceledListSlice<SecurityEvent>(Collections.emptyList());
         }
     }
 
@@ -18752,8 +18390,8 @@
     }
 
     private boolean hasIncompatibleAccounts(int userId) {
-        return mHasIncompatibleAccounts == null ? true
-                : mHasIncompatibleAccounts.getOrDefault(userId, /* default= */ false);
+        return mHasIncompatibleAccounts == null || mHasIncompatibleAccounts.getOrDefault(
+                userId, /* default= */ false);
     }
 
     /**
@@ -18870,7 +18508,7 @@
                 return false;
             }
         }
-    };
+    }
 
     private boolean isAdb(CallerIdentity caller) {
         return isShellUid(caller) || isRootUid(caller);
@@ -20168,21 +19806,12 @@
     @Override
     public void installUpdateFromFile(ComponentName admin, String callerPackageName,
             ParcelFileDescriptor updateFileDescriptor, StartInstallingUpdateCallback callback) {
-        if (!isPermissionCheckFlagEnabled()) {
-            Objects.requireNonNull(admin, "ComponentName is null");
-        }
+        Objects.requireNonNull(admin, "ComponentName is null");
 
-        CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
-            caller = getCallerIdentity(admin, callerPackageName);
-            enforcePermission(MANAGE_DEVICE_POLICY_SYSTEM_UPDATES, caller.getPackageName(),
-                    UserHandle.USER_ALL);
-        } else {
-            caller = getCallerIdentity(admin);
-            Preconditions.checkCallAuthorization(
-                    isDefaultDeviceOwner(caller)
-                            || isProfileOwnerOfOrganizationOwnedDevice(caller));
-        }
+        CallerIdentity caller = getCallerIdentity(admin);
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller)
+                        || isProfileOwnerOfOrganizationOwnedDevice(caller));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_INSTALL_SYSTEM_UPDATE);
 
         DevicePolicyEventLogger
@@ -20752,32 +20381,15 @@
     @Override
     public void setCommonCriteriaModeEnabled(ComponentName who, String callerPackageName,
             boolean enabled) {
-        CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
-            caller = getCallerIdentity(who, callerPackageName);
-        } else {
-            caller = getCallerIdentity(who);
-        }
-        final ActiveAdmin admin;
+        CallerIdentity caller = getCallerIdentity(who);
 
-        if (isPermissionCheckFlagEnabled()) {
-            EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
-                    who,
-                    MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE,
-                    caller.getPackageName(),
-                    caller.getUserId());
-            admin = enforcingAdmin.getActiveAdmin();
-        } else {
-            Objects.requireNonNull(who, "ComponentName is null");
-            Preconditions.checkCallAuthorization(
-                    isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
-                    "Common Criteria mode can only be controlled by a device owner or "
-                            + "a profile owner on an organization-owned device.");
-            synchronized (getLockObject()) {
-                admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
-            }
-        }
+        Objects.requireNonNull(who, "ComponentName is null");
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+                "Common Criteria mode can only be controlled by a device owner or "
+                        + "a profile owner on an organization-owned device.");
         synchronized (getLockObject()) {
+            final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
             admin.mCommonCriteriaMode = enabled;
             saveSettingsLocked(caller.getUserId());
         }
@@ -20809,7 +20421,7 @@
             // their ActiveAdmin, instead of iterating through all admins.
             ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
 
-            return admin != null ? admin.mCommonCriteriaMode : false;
+            return admin != null && admin.mCommonCriteriaMode;
         }
     }
 
@@ -22209,7 +21821,7 @@
             } else {
                 owner = getDeviceOrProfileOwnerAdminLocked(userId);
             }
-            boolean canGrant = owner != null ? owner.mAdminCanGrantSensorsPermissions : false;
+            boolean canGrant = owner != null && owner.mAdminCanGrantSensorsPermissions;
             mPolicyCache.setAdminCanGrantSensorsPermissions(canGrant);
         }
     }
@@ -22408,27 +22020,15 @@
 
     @Override
     public void setMinimumRequiredWifiSecurityLevel(String callerPackageName, int level) {
-        CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
-            caller = getCallerIdentity(callerPackageName);
-        } else {
-            caller = getCallerIdentity();
-            Preconditions.checkCallAuthorization(
-                    isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
-                    "Wi-Fi minimum security level can only be controlled by a device owner or "
-                            + "a profile owner on an organization-owned device.");
-        }
+        CallerIdentity caller = getCallerIdentity();
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+                "Wi-Fi minimum security level can only be controlled by a device owner or "
+                        + "a profile owner on an organization-owned device.");
 
         boolean valueChanged = false;
         synchronized (getLockObject()) {
-            ActiveAdmin admin;
-            if (isPermissionCheckFlagEnabled()) {
-                admin = enforcePermissionAndGetEnforcingAdmin(/* admin= */ null,
-                        MANAGE_DEVICE_POLICY_WIFI, caller.getPackageName(), caller.getUserId())
-                        .getActiveAdmin();
-            } else {
-                admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
-            }
+            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
             if (admin.mWifiMinimumSecurityLevel != level) {
                 admin.mWifiMinimumSecurityLevel = level;
                 saveSettingsLocked(caller.getUserId());
@@ -22450,21 +22050,16 @@
     @Override
     public WifiSsidPolicy getWifiSsidPolicy(String callerPackageName) {
         final CallerIdentity caller = getCallerIdentity();
-        if (isPermissionCheckFlagEnabled()) {
-            enforcePermission(MANAGE_DEVICE_POLICY_WIFI, callerPackageName,
-                    caller.getUserId());
-        } else {
-            Preconditions.checkCallAuthorization(
-                    isDefaultDeviceOwner(caller)
-                            || isProfileOwnerOfOrganizationOwnedDevice(caller)
-                            || canQueryAdminPolicy(caller),
-                    "SSID policy can only be retrieved by a device owner or "
-                            + "a profile owner on an organization-owned device or "
-                            + "an app with the QUERY_ADMIN_POLICY permission.");
-        }
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller)
+                        || isProfileOwnerOfOrganizationOwnedDevice(caller)
+                        || canQueryAdminPolicy(caller),
+                "SSID policy can only be retrieved by a device owner or "
+                        + "a profile owner on an organization-owned device or "
+                        + "an app with the QUERY_ADMIN_POLICY permission.");
         synchronized (getLockObject()) {
             ActiveAdmin admin;
-            admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceOrSystemPermissionBasedAdminLocked();
+            admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
             return admin != null ? admin.mWifiSsidPolicy : null;
         }
     }
@@ -22485,29 +22080,15 @@
 
     @Override
     public void setWifiSsidPolicy(String callerPackageName, WifiSsidPolicy policy) {
-        CallerIdentity caller;
-
-        if (isPermissionCheckFlagEnabled()) {
-            caller = getCallerIdentity(callerPackageName);
-        } else {
-            caller = getCallerIdentity();
-            Preconditions.checkCallAuthorization(
-                    isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
-                    "SSID denylist can only be controlled by a device owner or "
-                            + "a profile owner on an organization-owned device.");
-        }
+        CallerIdentity caller = getCallerIdentity();
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+                "SSID denylist can only be controlled by a device owner or "
+                        + "a profile owner on an organization-owned device.");
 
         boolean changed = false;
         synchronized (getLockObject()) {
-            ActiveAdmin admin;
-            if (isPermissionCheckFlagEnabled()) {
-                admin = enforcePermissionAndGetEnforcingAdmin(
-                        /* admin= */ null, MANAGE_DEVICE_POLICY_WIFI,
-                        caller.getPackageName(),
-                        caller.getUserId()).getActiveAdmin();
-            } else {
-                admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
-            }
+            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
             if (!Objects.equals(policy, admin.mWifiSsidPolicy)) {
                 admin.mWifiSsidPolicy = policy;
                 changed = true;
@@ -22715,7 +22296,7 @@
     }
 
     private final class DevicePolicyManagementRoleObserver implements OnRoleHoldersChangedListener {
-        private RoleManager mRm;
+        private final RoleManager mRm;
         private final Executor mExecutor;
         private final Context mContext;
 
@@ -22732,13 +22313,11 @@
         @Override
         public void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) {
             mDevicePolicyEngine.handleRoleChanged(roleName, user.getIdentifier());
-            if (RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT.equals(roleName)) {
-                handleDevicePolicyManagementRoleChange(user);
-                return;
-            }
-            if (RoleManager.ROLE_FINANCED_DEVICE_KIOSK.equals(roleName)) {
-                handleFinancedDeviceKioskRoleChange();
-                return;
+            switch (roleName) {
+                case RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT ->
+                        handleDevicePolicyManagementRoleChange(user);
+                case RoleManager.ROLE_FINANCED_DEVICE_KIOSK ->
+                        handleFinancedDeviceKioskRoleChange();
             }
         }
 
@@ -23390,26 +22969,6 @@
 
     /**
      * Checks if the calling process has been granted permission to apply a device policy on a
-     * specific user.
-     * The given permission will be checked along with its associated cross-user permission if it
-     * exists and the target user is different to the calling user.
-     * Returns an {@link EnforcingAdmin} for the caller.
-     *
-     * @param admin the component name of the admin.
-     * @param callerPackageName The package name  of the calling application.
-     * @param permission The name of the permission being checked.
-     * @param deviceAdminPolicy The userId of the user which the caller needs permission to act on.
-     * @throws SecurityException if the caller has not been granted the given permission,
-     * the associated cross-user permission if the caller's user is different to the target user.
-     */
-    private EnforcingAdmin enforcePermissionAndGetEnforcingAdmin(@Nullable ComponentName admin,
-            String permission, int deviceAdminPolicy, String callerPackageName, int targetUserId) {
-        enforcePermission(permission, deviceAdminPolicy, callerPackageName, targetUserId);
-        return getEnforcingAdminForCaller(admin, callerPackageName);
-    }
-
-    /**
-     * Checks if the calling process has been granted permission to apply a device policy on a
      * specific user.  Only one permission provided in the list needs to be granted to pass this
      * check.
      * The given permissions will be checked along with their associated cross-user permissions if
@@ -23431,23 +22990,6 @@
     }
 
     /**
-     * Checks whether the calling process has been granted permission to query a device policy on
-     * a specific user.
-     * The given permission will be checked along with its associated cross-user permission if it
-     * exists and the target user is different to the calling user.
-     *
-     * @param permission The name of the permission being checked.
-     * @param targetUserId The userId of the user which the caller needs permission to act on.
-     * @throws SecurityException if the caller has not been granted the given permission,
-     * the associated cross-user permission if the caller's user is different to the target user.
-     */
-    private EnforcingAdmin enforceCanQueryAndGetEnforcingAdmin(@Nullable ComponentName admin,
-            String permission, String callerPackageName, int targetUserId) {
-        enforceCanQuery(permission, callerPackageName, targetUserId);
-        return getEnforcingAdminForCaller(admin, callerPackageName);
-    }
-
-    /**
      * Checks if the calling process has been granted permission to apply a device policy.
      *
      * @param callerPackageName The package name  of the calling application.
@@ -23754,13 +23296,6 @@
         return NOT_A_DPC;
     }
 
-    private boolean isPermissionCheckFlagEnabled() {
-        return DeviceConfig.getBoolean(
-                NAMESPACE_DEVICE_POLICY_MANAGER,
-                PERMISSION_BASED_ACCESS_EXPERIMENT_FLAG,
-                DEFAULT_VALUE_PERMISSION_BASED_ACCESS_FLAG);
-    }
-
     private static boolean isSetStatusBarDisabledCoexistenceEnabled() {
         return false;
     }
@@ -23837,58 +23372,83 @@
             Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
         }
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (Flags.setMtePolicyCoexistence()) {
             enforcePermission(MANAGE_DEVICE_POLICY_MTE, caller.getPackageName(),
                     UserHandle.USER_ALL);
         } else {
             Preconditions.checkCallAuthorization(
                     isDefaultDeviceOwner(caller)
-                            || isProfileOwnerOfOrganizationOwnedDevice(caller));
+                    || isProfileOwnerOfOrganizationOwnedDevice(caller));
         }
+
         synchronized (getLockObject()) {
-            ActiveAdmin admin =
-                        getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
-
-            if (admin != null) {
-                final String memtagProperty = "arm64.memtag.bootctl";
-                if (flags == DevicePolicyManager.MTE_ENABLED) {
-                    mInjector.systemPropertiesSet(memtagProperty, "memtag");
-                } else if (flags == DevicePolicyManager.MTE_DISABLED) {
-                    mInjector.systemPropertiesSet(memtagProperty, "memtag-off");
-                } else if (flags == DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
-                    if (admin.mtePolicy != DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
-                        mInjector.systemPropertiesSet(memtagProperty, "default");
-                    }
+            if (Flags.setMtePolicyCoexistence()) {
+                final EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(null,
+                        MANAGE_DEVICE_POLICY_MTE, callerPackageName, caller.getUserId());
+                if (flags != DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
+                    mDevicePolicyEngine.setGlobalPolicy(
+                            PolicyDefinition.MEMORY_TAGGING,
+                            admin,
+                            new IntegerPolicyValue(flags));
+                } else {
+                    mDevicePolicyEngine.removeGlobalPolicy(
+                            PolicyDefinition.MEMORY_TAGGING,
+                            admin);
                 }
-                admin.mtePolicy = flags;
-                saveSettingsLocked(caller.getUserId());
-
-                DevicePolicyEventLogger.createEvent(DevicePolicyEnums.SET_MTE_POLICY)
-                        .setInt(flags)
-                        .setAdmin(caller.getPackageName())
-                        .write();
+            } else {
+                ActiveAdmin admin =
+                        getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
+                if (admin != null) {
+                    final String memtagProperty = "arm64.memtag.bootctl";
+                    if (flags == DevicePolicyManager.MTE_ENABLED) {
+                        mInjector.systemPropertiesSet(memtagProperty, "memtag");
+                    } else if (flags == DevicePolicyManager.MTE_DISABLED) {
+                        mInjector.systemPropertiesSet(memtagProperty, "memtag-off");
+                    } else if (flags == DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
+                        if (admin.mtePolicy != DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
+                            mInjector.systemPropertiesSet(memtagProperty, "default");
+                        }
+                    }
+                    admin.mtePolicy = flags;
+                    saveSettingsLocked(caller.getUserId());
+                }
             }
+
+            DevicePolicyEventLogger.createEvent(DevicePolicyEnums.SET_MTE_POLICY)
+                    .setInt(flags)
+                    .setAdmin(caller.getPackageName())
+                    .write();
         }
     }
 
     @Override
     public int getMtePolicy(String callerPackageName) {
         final CallerIdentity caller = getCallerIdentity(callerPackageName);
-        if (isPermissionCheckFlagEnabled()) {
+        if (Flags.setMtePolicyCoexistence()) {
             enforcePermission(MANAGE_DEVICE_POLICY_MTE, caller.getPackageName(),
                     UserHandle.USER_ALL);
         } else {
             Preconditions.checkCallAuthorization(
                     isDefaultDeviceOwner(caller)
-                            || isProfileOwnerOfOrganizationOwnedDevice(caller)
-                            || isSystemUid(caller));
+                    || isProfileOwnerOfOrganizationOwnedDevice(caller)
+                    || isSystemUid(caller));
         }
+
         synchronized (getLockObject()) {
-            ActiveAdmin admin =
+            if (Flags.setMtePolicyCoexistence()) {
+                final EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(null,
+                        MANAGE_DEVICE_POLICY_MTE, callerPackageName, caller.getUserId());
+                final Integer policyFromAdmin = mDevicePolicyEngine.getGlobalPolicySetByAdmin(
+                        PolicyDefinition.MEMORY_TAGGING, admin);
+                return (policyFromAdmin != null ? policyFromAdmin
+                        : DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY);
+            } else {
+                ActiveAdmin admin =
                         getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
-            return admin != null
-                    ? admin.mtePolicy
-                    : DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY;
+                return admin != null
+                        ? admin.mtePolicy
+                        : DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY;
+            }
         }
     }
 
@@ -24235,21 +23795,24 @@
         maybeMigrateSecurityLoggingPolicyLocked();
         // ID format: <sdk-int>.<auto_increment_id>.<descriptions>'
         String unmanagedBackupId = "35.1.unmanaged-mode";
-        boolean unmanagedMigrated = false;
-        unmanagedMigrated =
-                unmanagedMigrated | maybeMigrateRequiredPasswordComplexityLocked(unmanagedBackupId);
-        unmanagedMigrated =
-                unmanagedMigrated | maybeMigrateSuspendedPackagesLocked(unmanagedBackupId);
+        boolean unmanagedMigrated = maybeMigrateRequiredPasswordComplexityLocked(unmanagedBackupId);
         if (unmanagedMigrated) {
             Slogf.i(LOG_TAG, "Backup made: " + unmanagedBackupId);
         }
 
         String supervisionBackupId = "36.2.supervision-support";
         boolean supervisionMigrated = maybeMigrateResetPasswordTokenLocked(supervisionBackupId);
+        supervisionMigrated |= maybeMigrateSuspendedPackagesLocked(supervisionBackupId);
         if (supervisionMigrated) {
             Slogf.i(LOG_TAG, "Backup made: " + supervisionBackupId);
         }
 
+        String memoryTaggingBackupId = "36.3.memory-tagging";
+        boolean memoryTaggingMigrated = maybeMigrateMemoryTaggingLocked(memoryTaggingBackupId);
+        if (memoryTaggingMigrated) {
+            Slogf.i(LOG_TAG, "Backup made: " + memoryTaggingBackupId);
+        }
+
         // Additional migration steps should repeat the pattern above with a new backupId.
     }
 
@@ -24666,7 +24229,7 @@
                 || isCallerDevicePolicyManagementRoleHolder(caller)
                 || isCallerSystemSupervisionRoleHolder(caller));
         return getFinancedDeviceKioskRoleHolderOnAnyUser() != null;
-    };
+    }
 
     @Override
     public String getFinancedDeviceKioskRoleHolder(String callerPackageName) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index b3c8408..be4eea4 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -682,6 +682,19 @@
         }
     }
 
+    void markMemoryTaggingMigrated() {
+        synchronized (mData) {
+            mData.mMemoryTaggingMigrated = true;
+            mData.writeDeviceOwner();
+        }
+    }
+
+    boolean isMemoryTaggingMigrated() {
+        synchronized (mData) {
+            return mData.mMemoryTaggingMigrated;
+        }
+    }
+
     @GuardedBy("mData")
     void pushToAppOpsLocked() {
         if (!mSystemReady) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
index 10e43d9..1cae924 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
@@ -93,6 +93,9 @@
     private static final String ATTR_SUSPENDED_PACKAGES_MIGRATED = "suspendedPackagesMigrated";
     private static final String ATTR_RESET_PASSWORD_WITH_TOKEN_MIGRATED =
             "resetPasswordWithTokenMigrated";
+    private static final String ATTR_MEMORY_TAGGING_MIGRATED =
+            "memoryTaggingMigrated";
+
     private static final String ATTR_MIGRATED_POST_UPGRADE = "migratedPostUpgrade";
 
     // Internal state for the device owner package.
@@ -125,6 +128,7 @@
     boolean mRequiredPasswordComplexityMigrated = false;
     boolean mSuspendedPackagesMigrated = false;
     boolean mResetPasswordWithTokenMigrated = false;
+    boolean mMemoryTaggingMigrated = false;
 
     boolean mPoliciesMigratedPostUpdate = false;
 
@@ -416,6 +420,8 @@
             if (Flags.unmanagedModeMigration()) {
                 out.attributeBoolean(null, ATTR_REQUIRED_PASSWORD_COMPLEXITY_MIGRATED,
                         mRequiredPasswordComplexityMigrated);
+            }
+            if (Flags.suspendPackagesCoexistence()) {
                 out.attributeBoolean(null, ATTR_SUSPENDED_PACKAGES_MIGRATED,
                         mSuspendedPackagesMigrated);
 
@@ -424,6 +430,10 @@
                 out.attributeBoolean(null, ATTR_RESET_PASSWORD_WITH_TOKEN_MIGRATED,
                         mResetPasswordWithTokenMigrated);
             }
+            if (Flags.setMtePolicyCoexistence()) {
+                out.attributeBoolean(null, ATTR_MEMORY_TAGGING_MIGRATED,
+                        mMemoryTaggingMigrated);
+            }
             out.endTag(null, TAG_POLICY_ENGINE_MIGRATION);
 
         }
@@ -491,13 +501,15 @@
                     mRequiredPasswordComplexityMigrated = Flags.unmanagedModeMigration()
                             && parser.getAttributeBoolean(null,
                                     ATTR_REQUIRED_PASSWORD_COMPLEXITY_MIGRATED, false);
-                    mSuspendedPackagesMigrated = Flags.unmanagedModeMigration()
+                    mSuspendedPackagesMigrated = Flags.suspendPackagesCoexistence()
                             && parser.getAttributeBoolean(null,
                                     ATTR_SUSPENDED_PACKAGES_MIGRATED, false);
                     mResetPasswordWithTokenMigrated = Flags.resetPasswordWithTokenCoexistence()
                             && parser.getAttributeBoolean(null,
                             ATTR_RESET_PASSWORD_WITH_TOKEN_MIGRATED, false);
-
+                    mMemoryTaggingMigrated = Flags.setMtePolicyCoexistence()
+                            && parser.getAttributeBoolean(null,
+                            ATTR_MEMORY_TAGGING_MIGRATED, false);
                     break;
                 default:
                     Slog.e(TAG, "Unexpected tag: " + tag);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index f271162..f1711f5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -53,6 +53,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.CompletableFuture;
 
 final class PolicyDefinition<V> {
 
@@ -336,6 +337,13 @@
                     PolicyEnforcerCallbacks::noOp,
                     new PackageSetPolicySerializer());
 
+    static PolicyDefinition<Integer> MEMORY_TAGGING = new PolicyDefinition<>(
+                    new NoArgsPolicyKey(
+                            DevicePolicyIdentifiers.MEMORY_TAGGING_POLICY),
+                    new TopPriority<>(List.of(EnforcingAdmin.DPC_AUTHORITY)),
+                    PolicyEnforcerCallbacks::setMtePolicy,
+                    new IntegerPolicySerializer());
+
     private static final Map<String, PolicyDefinition<?>> POLICY_DEFINITIONS = new HashMap<>();
     private static Map<String, Integer> USER_RESTRICTION_FLAGS = new HashMap<>();
 
@@ -382,6 +390,8 @@
                 PASSWORD_COMPLEXITY);
         POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.PACKAGES_SUSPENDED_POLICY,
                 PACKAGES_SUSPENDED);
+        POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.MEMORY_TAGGING_POLICY,
+                MEMORY_TAGGING);
 
         // User Restriction Policies
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_MODIFY_ACCOUNTS, /* flags= */ 0);
@@ -504,7 +514,8 @@
     private final int mPolicyFlags;
     // A function that accepts  policy to apply, context, userId, callback arguments, and returns
     // true if the policy has been enforced successfully.
-    private final QuadFunction<V, Context, Integer, PolicyKey, Boolean> mPolicyEnforcerCallback;
+    private final QuadFunction<V, Context, Integer, PolicyKey, CompletableFuture<Boolean>>
+            mPolicyEnforcerCallback;
     private final PolicySerializer<V> mPolicySerializer;
 
     private PolicyDefinition<V> createPolicyDefinition(PolicyKey key) {
@@ -574,7 +585,7 @@
         return mResolutionMechanism.resolve(adminsPolicy);
     }
 
-    boolean enforcePolicy(@Nullable V value, Context context, int userId) {
+    CompletableFuture<Boolean> enforcePolicy(@Nullable V value, Context context, int userId) {
         return mPolicyEnforcerCallback.apply(value, context, userId, mPolicyKey);
     }
 
@@ -592,7 +603,6 @@
         POLICY_DEFINITIONS.put(key.getIdentifier(), definition);
     }
 
-
     /**
      * Callers must ensure that {@code policyType} have implemented an appropriate
      * {@link Object#equals} implementation.
@@ -600,7 +610,8 @@
     private PolicyDefinition(
             @NonNull  PolicyKey key,
             ResolutionMechanism<V> resolutionMechanism,
-            QuadFunction<V, Context, Integer, PolicyKey, Boolean> policyEnforcerCallback,
+            QuadFunction<V, Context, Integer, PolicyKey, CompletableFuture<Boolean>>
+                    policyEnforcerCallback,
             PolicySerializer<V> policySerializer) {
         this(key, resolutionMechanism, POLICY_FLAG_NONE, policyEnforcerCallback, policySerializer);
     }
@@ -610,10 +621,11 @@
      * {@link Object#equals} and {@link Object#hashCode()} implementation.
      */
     private PolicyDefinition(
-            @NonNull  PolicyKey policyKey,
+            @NonNull PolicyKey policyKey,
             ResolutionMechanism<V> resolutionMechanism,
             int policyFlags,
-            QuadFunction<V, Context, Integer, PolicyKey, Boolean> policyEnforcerCallback,
+            QuadFunction<V, Context, Integer, PolicyKey, CompletableFuture<Boolean>>
+                    policyEnforcerCallback,
             PolicySerializer<V> policySerializer) {
         Objects.requireNonNull(policyKey);
         mPolicyKey = policyKey;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index 4d9abf1..fdc0ec1 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -47,6 +47,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.permission.AdminPermissionControlParams;
 import android.permission.PermissionControllerManager;
@@ -55,6 +56,7 @@
 import android.util.Slog;
 import android.view.IWindowManager;
 
+import com.android.internal.infra.AndroidFuture;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.LocalServices;
@@ -65,6 +67,7 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
@@ -73,33 +76,36 @@
 
     private static final String LOG_TAG = "PolicyEnforcerCallbacks";
 
-    static <T> boolean noOp(T value, Context context, Integer userId, PolicyKey policyKey) {
-        return true;
+    static <T> CompletableFuture<Boolean> noOp(T value, Context context, Integer userId,
+            PolicyKey policyKey) {
+        return AndroidFuture.completedFuture(true);
     }
 
-    static boolean setAutoTimezoneEnabled(@Nullable Boolean enabled, @NonNull Context context) {
+    static CompletableFuture<Boolean> setAutoTimezoneEnabled(@Nullable Boolean enabled,
+            @NonNull Context context) {
         if (!Flags.setAutoTimeZoneEnabledCoexistence()) {
             Slogf.w(LOG_TAG, "Trying to enforce setAutoTimezoneEnabled while flag is off.");
-            return true;
+            return AndroidFuture.completedFuture(true);
         }
         return Binder.withCleanCallingIdentity(() -> {
             Objects.requireNonNull(context);
 
             int value = enabled != null && enabled ? 1 : 0;
-            return Settings.Global.putInt(
-                    context.getContentResolver(), Settings.Global.AUTO_TIME_ZONE,
-                    value);
+            return AndroidFuture.completedFuture(
+                    Settings.Global.putInt(
+                            context.getContentResolver(), Settings.Global.AUTO_TIME_ZONE,
+                    value));
         });
     }
 
-    static boolean setPermissionGrantState(
+    static CompletableFuture<Boolean> setPermissionGrantState(
             @Nullable Integer grantState, @NonNull Context context, int userId,
             @NonNull PolicyKey policyKey) {
         if (!Flags.setPermissionGrantStateCoexistence()) {
             Slogf.w(LOG_TAG, "Trying to enforce setPermissionGrantState while flag is off.");
-            return true;
+            return AndroidFuture.completedFuture(true);
         }
-        return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> {
+        return Binder.withCleanCallingIdentity(() -> {
             if (!(policyKey instanceof PackagePermissionPolicyKey)) {
                 throw new IllegalArgumentException("policyKey is not of type "
                         + "PermissionGrantStatePolicyKey, passed in policyKey is: " + policyKey);
@@ -125,12 +131,13 @@
                     .setRuntimePermissionGrantStateByDeviceAdmin(context.getPackageName(),
                             permissionParams, context.getMainExecutor(), callback::trigger);
             try {
-                return callback.await(20_000, TimeUnit.MILLISECONDS);
+                return AndroidFuture.completedFuture(
+                        callback.await(20_000, TimeUnit.MILLISECONDS));
             } catch (Exception e) {
                 // TODO: add logging
-                return false;
+                return AndroidFuture.completedFuture(false);
             }
-        }));
+        });
     }
 
     @NonNull
@@ -149,23 +156,23 @@
         }
     }
 
-    static boolean enforceSecurityLogging(
+    static CompletableFuture<Boolean> enforceSecurityLogging(
             @Nullable Boolean value, @NonNull Context context, int userId,
             @NonNull PolicyKey policyKey) {
         final var dpmi = LocalServices.getService(DevicePolicyManagerInternal.class);
         dpmi.enforceSecurityLoggingPolicy(Boolean.TRUE.equals(value));
-        return true;
+        return AndroidFuture.completedFuture(true);
     }
 
-    static boolean enforceAuditLogging(
+    static CompletableFuture<Boolean> enforceAuditLogging(
             @Nullable Boolean value, @NonNull Context context, int userId,
             @NonNull PolicyKey policyKey) {
         final var dpmi = LocalServices.getService(DevicePolicyManagerInternal.class);
         dpmi.enforceAuditLoggingPolicy(Boolean.TRUE.equals(value));
-        return true;
+        return AndroidFuture.completedFuture(true);
     }
 
-    static boolean setLockTask(
+    static CompletableFuture<Boolean> setLockTask(
             @Nullable LockTaskPolicy policy, @NonNull Context context, int userId) {
         List<String> packages = Collections.emptyList();
         int flags = LockTaskPolicy.DEFAULT_LOCK_TASK_FLAG;
@@ -175,7 +182,7 @@
         }
         DevicePolicyManagerService.updateLockTaskPackagesLocked(context, packages, userId);
         DevicePolicyManagerService.updateLockTaskFeaturesLocked(flags, userId);
-        return true;
+        return AndroidFuture.completedFuture(true);
     }
 
 
@@ -187,8 +194,8 @@
      * rely on the POLICY_FLAG_SKIP_ENFORCEMENT_IF_UNCHANGED flag so DPE only invokes this callback
      * when the policy is set, and not during system boot or other situations.
      */
-    static boolean setApplicationRestrictions(Bundle bundle, Context context, Integer userId,
-            PolicyKey policyKey) {
+    static CompletableFuture<Boolean> setApplicationRestrictions(Bundle bundle, Context context,
+            Integer userId, PolicyKey policyKey) {
         Binder.withCleanCallingIdentity(() -> {
             PackagePolicyKey key = (PackagePolicyKey) policyKey;
             String packageName = key.getPackageName();
@@ -198,12 +205,13 @@
             changeIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
             context.sendBroadcastAsUser(changeIntent, UserHandle.of(userId));
         });
-        return true;
+        return AndroidFuture.completedFuture(true);
     }
 
     private static class BlockingCallback {
         private final CountDownLatch mLatch = new CountDownLatch(1);
         private final AtomicReference<Boolean> mValue = new AtomicReference<>();
+
         public void trigger(Boolean value) {
             mValue.set(value);
             mLatch.countDown();
@@ -220,7 +228,7 @@
     // TODO: when a local policy exists for a user, this callback will be invoked for this user
     // individually as well as for USER_ALL. This can be optimized by separating local and global
     // enforcement in the policy engine.
-    static boolean setUserControlDisabledPackages(
+    static CompletableFuture<Boolean> setUserControlDisabledPackages(
             @Nullable Set<String> packages, Context context, int userId, PolicyKey policyKey) {
         Binder.withCleanCallingIdentity(() -> {
             PackageManagerInternal pmi =
@@ -246,7 +254,7 @@
                 }
             }
         });
-        return true;
+        return AndroidFuture.completedFuture(true);
     }
 
     /** Handles USER_ALL expanding it into the list of all intact users. */
@@ -271,7 +279,7 @@
         }
     }
 
-    static boolean addPersistentPreferredActivity(
+    static CompletableFuture<Boolean> addPersistentPreferredActivity(
             @Nullable ComponentName preferredActivity, @NonNull Context context, int userId,
             @NonNull PolicyKey policyKey) {
         Binder.withCleanCallingIdentity(() -> {
@@ -297,13 +305,13 @@
                 Slog.wtf(LOG_TAG, "Error adding/removing persistent preferred activity", re);
             }
         });
-        return true;
+        return AndroidFuture.completedFuture(true);
     }
 
-    static boolean setUninstallBlocked(
+    static CompletableFuture<Boolean> setUninstallBlocked(
             @Nullable Boolean uninstallBlocked, @NonNull Context context, int userId,
             @NonNull PolicyKey policyKey) {
-        return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> {
+        return Binder.withCleanCallingIdentity(() -> {
             if (!(policyKey instanceof PackagePolicyKey)) {
                 throw new IllegalArgumentException("policyKey is not of type "
                         + "PackagePolicyKey, passed in policyKey is: " + policyKey);
@@ -314,14 +322,14 @@
                     packageName,
                     uninstallBlocked != null && uninstallBlocked,
                     userId);
-            return true;
-        }));
+            return AndroidFuture.completedFuture(true);
+        });
     }
 
-    static boolean setUserRestriction(
+    static CompletableFuture<Boolean> setUserRestriction(
             @Nullable Boolean enabled, @NonNull Context context, int userId,
             @NonNull PolicyKey policyKey) {
-        return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> {
+        return Binder.withCleanCallingIdentity(() -> {
             if (!(policyKey instanceof UserRestrictionPolicyKey)) {
                 throw new IllegalArgumentException("policyKey is not of type "
                         + "UserRestrictionPolicyKey, passed in policyKey is: " + policyKey);
@@ -331,14 +339,14 @@
             UserManagerInternal userManager = LocalServices.getService(UserManagerInternal.class);
             userManager.setUserRestriction(
                     userId, parsedKey.getRestriction(), enabled != null && enabled);
-            return true;
-        }));
+            return AndroidFuture.completedFuture(true);
+        });
     }
 
-    static boolean setApplicationHidden(
+    static CompletableFuture<Boolean> setApplicationHidden(
             @Nullable Boolean hide, @NonNull Context context, int userId,
             @NonNull PolicyKey policyKey) {
-        return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> {
+        return Binder.withCleanCallingIdentity(() -> {
             if (!(policyKey instanceof PackagePolicyKey)) {
                 throw new IllegalArgumentException("policyKey is not of type "
                         + "PackagePolicyKey, passed in policyKey is: " + policyKey);
@@ -346,12 +354,13 @@
             PackagePolicyKey parsedKey = (PackagePolicyKey) policyKey;
             String packageName = Objects.requireNonNull(parsedKey.getPackageName());
             IPackageManager packageManager = AppGlobals.getPackageManager();
-            return packageManager.setApplicationHiddenSettingAsUser(
-                    packageName, hide != null && hide, userId);
-        }));
+            return AndroidFuture.completedFuture(
+                    packageManager.setApplicationHiddenSettingAsUser(
+                            packageName, hide != null && hide, userId));
+        });
     }
 
-    static boolean setScreenCaptureDisabled(
+    static CompletableFuture<Boolean> setScreenCaptureDisabled(
             @Nullable Boolean disabled, @NonNull Context context, int userId,
             @NonNull PolicyKey policyKey) {
         Binder.withCleanCallingIdentity(() -> {
@@ -363,10 +372,10 @@
                 updateScreenCaptureDisabled();
             }
         });
-        return true;
+        return AndroidFuture.completedFuture(true);
     }
 
-    static boolean setContentProtectionPolicy(
+    static CompletableFuture<Boolean> setContentProtectionPolicy(
             @Nullable Integer value,
             @NonNull Context context,
             @UserIdInt Integer userId,
@@ -378,7 +387,7 @@
                         cacheImpl.setContentProtectionPolicy(userId, value);
                     }
                 });
-        return true;
+        return AndroidFuture.completedFuture(true);
     }
 
     private static void updateScreenCaptureDisabled() {
@@ -393,7 +402,7 @@
         });
     }
 
-    static boolean setPersonalAppsSuspended(
+    static CompletableFuture<Boolean> setPersonalAppsSuspended(
             @Nullable Boolean suspended, @NonNull Context context, int userId,
             @NonNull PolicyKey policyKey) {
         Binder.withCleanCallingIdentity(() -> {
@@ -404,7 +413,7 @@
                         .unsuspendAdminSuspendedPackages(userId);
             }
         });
-        return true;
+        return AndroidFuture.completedFuture(true);
     }
 
     private static void suspendPersonalAppsInPackageManager(Context context, int userId) {
@@ -418,13 +427,53 @@
         }
     }
 
-    static boolean setUsbDataSignalingEnabled(@Nullable Boolean value, @NonNull Context context) {
+    static CompletableFuture<Boolean> setUsbDataSignalingEnabled(@Nullable Boolean value,
+            @NonNull Context context) {
         return Binder.withCleanCallingIdentity(() -> {
             Objects.requireNonNull(context);
 
             boolean enabled = value == null || value;
             DevicePolicyManagerService.updateUsbDataSignal(context, enabled);
-            return true;
+            return AndroidFuture.completedFuture(true);
         });
     }
+
+    static CompletableFuture<Boolean> setMtePolicy(
+            @Nullable Integer mtePolicy, @NonNull Context context, int userId,
+            @NonNull PolicyKey policyKey) {
+        if (mtePolicy == null) {
+            mtePolicy = DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY;
+        }
+        final Set<Integer> allowedModes =
+                Set.of(
+                        DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY,
+                        DevicePolicyManager.MTE_DISABLED,
+                        DevicePolicyManager.MTE_ENABLED);
+        if (!allowedModes.contains(mtePolicy)) {
+            Slog.wtf(LOG_TAG, "MTE policy is not a known one: " + mtePolicy);
+            return AndroidFuture.completedFuture(false);
+        }
+
+        final String mteDpmSystemProperty =
+                "ro.arm64.memtag.bootctl_device_policy_manager";
+        final String mteSettingsSystemProperty =
+                "ro.arm64.memtag.bootctl_settings_toggle";
+        final String mteControlProperty = "arm64.memtag.bootctl";
+
+        final boolean isAvailable = SystemProperties.getBoolean(mteDpmSystemProperty,
+                SystemProperties.getBoolean(mteSettingsSystemProperty, false));
+        if (!isAvailable) {
+            return AndroidFuture.completedFuture(false);
+        }
+
+        if (mtePolicy == DevicePolicyManager.MTE_ENABLED) {
+            SystemProperties.set(mteControlProperty, "memtag");
+        } else if (mtePolicy == DevicePolicyManager.MTE_DISABLED) {
+            SystemProperties.set(mteControlProperty, "memtag-off");
+        } else if (mtePolicy == DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
+            SystemProperties.set(mteControlProperty, "default");
+        }
+
+        return AndroidFuture.completedFuture(true);
+    }
 }
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
index a804f24..c30ab73 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
@@ -101,13 +101,13 @@
                 TEST_SETTING_ACTIVITY_NAME, subtypes, TEST_IS_DEFAULT_RES_ID,
                 TEST_FORCE_DEFAULT, supportsSwitchingToNextInputMethod, TEST_IS_VR_IME);
         if (subtypes == null) {
-            items.add(new ImeSubtypeListItem(imeName, null /* variableName */, imi,
-                    NOT_A_SUBTYPE_INDEX, null, SYSTEM_LOCALE));
+            items.add(new ImeSubtypeListItem(imeName, null /* subtypeName */, null /* layoutName */,
+                    imi, NOT_A_SUBTYPE_INDEX, null, SYSTEM_LOCALE));
         } else {
             for (int i = 0; i < subtypes.size(); ++i) {
                 final String subtypeLocale = subtypeLocales.get(i);
-                items.add(new ImeSubtypeListItem(imeName, subtypeLocale, imi, i, subtypeLocale,
-                        SYSTEM_LOCALE));
+                items.add(new ImeSubtypeListItem(imeName, subtypeLocale, null /* layoutName */,
+                        imi, i, subtypeLocale, SYSTEM_LOCALE));
             }
         }
     }
@@ -138,8 +138,8 @@
         final InputMethodInfo imi = new InputMethodInfo(ri, TEST_IS_AUX_IME,
                 TEST_SETTING_ACTIVITY_NAME, subtypes, TEST_IS_DEFAULT_RES_ID,
                 TEST_FORCE_DEFAULT, true /* supportsSwitchingToNextInputMethod */, TEST_IS_VR_IME);
-        return new ImeSubtypeListItem(imeName, subtypeName, imi, subtypeIndex, subtypeLocale,
-                SYSTEM_LOCALE);
+        return new ImeSubtypeListItem(imeName, subtypeName, null /* layoutName */, imi,
+                subtypeIndex, subtypeLocale, SYSTEM_LOCALE);
     }
 
     @NonNull
diff --git a/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java
index 3ac7fb0..e0b0fec 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java
@@ -335,6 +335,10 @@
             @Override
             public void onStopped() {
             }
+
+            @Override
+            public void onRequestedBrightnessChanged(float brightness) {
+            }
         };
     }
 }
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index c81d6be..9a300fb 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 java_defaults {
-    name: "FrameworkMockingServicesTests-jni-defaults",
+    name: "FrameworksMockingServicesTests-jni-defaults",
     jni_libs: [
         "libmockingservicestestjni",
     ],
@@ -30,7 +30,7 @@
 android_test {
     name: "FrameworksMockingServicesTests",
     defaults: [
-        "FrameworkMockingServicesTests-jni-defaults",
+        "FrameworksMockingServicesTests-jni-defaults",
         "modules-utils-testable-device-config-defaults",
     ],
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
index cbc8538..37d1c30 100644
--- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
@@ -15,7 +15,12 @@
  */
 package com.android.server;
 
+import static android.os.PowerExemptionManager.REASON_OTHER;
+import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.NULL_DEFAULT;
+
 import static androidx.test.InstrumentationRegistry.getContext;
+
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -31,6 +36,7 @@
 import static com.android.server.DeviceIdleController.LIGHT_STATE_OVERRIDE;
 import static com.android.server.DeviceIdleController.LIGHT_STATE_WAITING_FOR_NETWORK;
 import static com.android.server.DeviceIdleController.MSG_REPORT_STATIONARY_STATUS;
+import static com.android.server.DeviceIdleController.MSG_TEMP_APP_WHITELIST_TIMEOUT;
 import static com.android.server.DeviceIdleController.STATE_ACTIVE;
 import static com.android.server.DeviceIdleController.STATE_IDLE;
 import static com.android.server.DeviceIdleController.STATE_IDLE_MAINTENANCE;
@@ -41,6 +47,7 @@
 import static com.android.server.DeviceIdleController.STATE_SENSING;
 import static com.android.server.DeviceIdleController.lightStateToString;
 import static com.android.server.DeviceIdleController.stateToString;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -83,6 +90,8 @@
 import android.os.PowerSaveState;
 import android.os.SystemClock;
 import android.os.WearModeManagerInternal;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.DeviceConfig;
 import android.telephony.TelephonyCallback;
 import android.telephony.TelephonyManager;
@@ -90,12 +99,16 @@
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.app.IBatteryStats;
+import com.android.server.am.BatteryStatsService;
 import com.android.server.deviceidle.ConstraintController;
+import com.android.server.deviceidle.Flags;
 import com.android.server.net.NetworkPolicyManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -115,6 +128,9 @@
 @SuppressWarnings("GuardedBy")
 @RunWith(AndroidJUnit4.class)
 public class DeviceIdleControllerTest {
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(NULL_DEFAULT);
+
     private DeviceIdleController mDeviceIdleController;
     private DeviceIdleController.MyHandler mHandler;
     private AnyMotionDetectorForTest mAnyMotionDetector;
@@ -157,7 +173,8 @@
         LocationManager locationManager;
         ConstraintController constraintController;
         // Freeze time for testing.
-        long nowElapsed;
+        volatile long nowElapsed;
+        volatile long nowUptime;
         boolean useMotionSensor = true;
         boolean isLocationPrefetchEnabled = true;
 
@@ -193,6 +210,11 @@
         }
 
         @Override
+        long getUptimeMillis() {
+            return nowUptime;
+        }
+
+        @Override
         LocationManager getLocationManager() {
             return locationManager;
         }
@@ -314,11 +336,13 @@
         mMockingSession = mockitoSession()
                 .initMocks(this)
                 .strictness(Strictness.LENIENT)
+                .mockStatic(BatteryStatsService.class)
                 .spyStatic(DeviceConfig.class)
                 .spyStatic(LocalServices.class)
                 .startMocking();
         spyOn(getContext());
         doReturn(null).when(getContext()).registerReceiver(any(), any());
+        doReturn(mock(IBatteryStats.class)).when(() -> BatteryStatsService.getService());
         doReturn(mock(ActivityManagerInternal.class))
                 .when(() -> LocalServices.getService(ActivityManagerInternal.class));
         doReturn(mock(ActivityTaskManagerInternal.class))
@@ -401,6 +425,46 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_USE_CPU_TIME_FOR_TEMP_ALLOWLIST)
+    public void testTempAllowlistCountsUptime() {
+        doNothing().when(getContext()).sendBroadcastAsUser(any(), any(), any(), any());
+        final int testUid = 12345;
+        final long durationMs = 4300;
+        final long startTime = 100; // Arbitrary starting point in time.
+        mInjector.nowUptime = mInjector.nowElapsed = startTime;
+
+        mDeviceIdleController.addPowerSaveTempWhitelistAppDirectInternal(0, testUid, durationMs,
+                TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED, true, REASON_OTHER, "test");
+
+        assertEquals(startTime + durationMs,
+                mDeviceIdleController.mTempWhitelistAppIdEndTimes.get(testUid).first.value);
+
+        final InOrder inorder = inOrder(mHandler);
+        // mHandler is already stubbed to do nothing on handleMessage.
+        inorder.verify(mHandler).sendMessageDelayed(
+                argThat(m -> m.what == MSG_TEMP_APP_WHITELIST_TIMEOUT && m.arg1 == testUid),
+                eq(durationMs));
+
+        mInjector.nowElapsed += durationMs;
+        mInjector.nowUptime += 2;
+        // Elapsed time moved past the expiration but not uptime. The check should be rescheduled.
+        mDeviceIdleController.checkTempAppWhitelistTimeout(testUid);
+        inorder.verify(mHandler).sendMessageDelayed(
+                argThat(m -> m.what == MSG_TEMP_APP_WHITELIST_TIMEOUT && m.arg1 == testUid),
+                eq(durationMs - 2));
+        assertEquals(startTime + durationMs,
+                mDeviceIdleController.mTempWhitelistAppIdEndTimes.get(testUid).first.value);
+
+        mInjector.nowUptime += durationMs;
+        // Uptime moved past the expiration time. Uid should be removed from the temp allowlist.
+        mDeviceIdleController.checkTempAppWhitelistTimeout(testUid);
+        inorder.verify(mHandler, never()).sendMessageDelayed(
+                argThat(m -> m.what == MSG_TEMP_APP_WHITELIST_TIMEOUT && m.arg1 == testUid),
+                anyLong());
+        assertFalse(mDeviceIdleController.mTempWhitelistAppIdEndTimes.contains(testUid));
+    }
+
+    @Test
     public void testUpdateInteractivityLocked() {
         doReturn(false).when(mPowerManager).isInteractive();
         mDeviceIdleController.updateInteractivityLocked();
diff --git a/services/tests/mockingservicestests/src/com/android/server/OWNERS b/services/tests/mockingservicestests/src/com/android/server/OWNERS
index dc5cb8d6..0c9f70c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/OWNERS
+++ b/services/tests/mockingservicestests/src/com/android/server/OWNERS
@@ -1,6 +1,6 @@
 per-file *Alarm* = file:/apex/jobscheduler/OWNERS
 per-file *AppStateTracker* = file:/apex/jobscheduler/OWNERS
-per-file *DeviceIdleController* = file:/apex/jobscheduler/OWNERS
+per-file *DeviceIdleController* = file:/apex/jobscheduler/DEVICE_IDLE_OWNERS
 per-file SensitiveContentProtectionManagerService* = file:/core/java/android/permission/OWNERS
 per-file RescuePartyTest.java = file:/packages/CrashRecovery/OWNERS
 per-file *Storage* = file:/core/java/android/os/storage/OWNERS
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index b005358..f82a860 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -60,6 +60,7 @@
 import static com.android.server.am.ProcessList.PERSISTENT_PROC_ADJ;
 import static com.android.server.am.ProcessList.PERSISTENT_SERVICE_ADJ;
 import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ;
+import static com.android.server.am.ProcessList.PREVIOUS_APP_MAX_ADJ;
 import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND;
 import static com.android.server.am.ProcessList.SCHED_GROUP_DEFAULT;
 import static com.android.server.am.ProcessList.SCHED_GROUP_FOREGROUND_WINDOW;
@@ -108,6 +109,7 @@
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.ArrayMap;
 import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 
 import com.android.server.LocalServices;
@@ -129,6 +131,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
 
 /**
  * Test class for {@link OomAdjuster}.
@@ -173,6 +176,7 @@
     private ActiveUids mActiveUids;
     private PackageManagerInternal mPackageManagerInternal;
     private ActivityManagerService mService;
+    private TestCachedAppOptimizer mTestCachedAppOptimizer;
     private OomAdjusterInjector mInjector = new OomAdjusterInjector();
 
     private int mUiTierSize;
@@ -240,9 +244,11 @@
         doNothing().when(pr).enqueueProcessChangeItemLocked(anyInt(), anyInt(), anyInt(),
                 anyBoolean());
         mActiveUids = new ActiveUids(mService, false);
+        mTestCachedAppOptimizer = new TestCachedAppOptimizer(mService);
         mProcessStateController = new ProcessStateController.Builder(mService,
                 mService.mProcessList, mActiveUids)
                 .useModernOomAdjuster(mService.mConstants.ENABLE_NEW_OOMADJ)
+                .setCachedAppOptimizer(mTestCachedAppOptimizer)
                 .setOomAdjusterInjector(mInjector)
                 .build();
         mService.mProcessStateController = mProcessStateController;
@@ -899,8 +905,25 @@
 
     @SuppressWarnings("GuardedBy")
     @Test
+    public void testUpdateOomAdj_DoPending_PreviousApp() {
+        testUpdateOomAdj_PreviousApp(apps -> {
+            for (ProcessRecord app : apps) {
+                mProcessStateController.enqueueUpdateTarget(app);
+            }
+            mProcessStateController.runPendingUpdate(OOM_ADJ_REASON_NONE);
+        });
+    }
+
+    @SuppressWarnings("GuardedBy")
+    @Test
     public void testUpdateOomAdj_DoAll_PreviousApp() {
-        final int numberOfApps = 15;
+        testUpdateOomAdj_PreviousApp(apps -> {
+            mProcessStateController.runFullUpdate(OOM_ADJ_REASON_NONE);
+        });
+    }
+
+    private void testUpdateOomAdj_PreviousApp(Consumer<ProcessRecord[]> updater) {
+        final int numberOfApps = 105;
         final ProcessRecord[] apps = new ProcessRecord[numberOfApps];
         for (int i = 0; i < numberOfApps; i++) {
             apps[i] = spy(makeDefaultProcessRecord(MOCKAPP_PID + i, MOCKAPP_UID + i,
@@ -911,10 +934,11 @@
         }
         setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
         setProcessesToLru(apps);
-        mProcessStateController.runFullUpdate(OOM_ADJ_REASON_NONE);
-
+        updater.accept(apps);
         for (int i = 0; i < numberOfApps; i++) {
-            assertProcStates(apps[i], PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ,
+            final int mruIndex = numberOfApps - i - 1;
+            final int expectedAdj = Math.min(PREVIOUS_APP_ADJ + mruIndex, PREVIOUS_APP_MAX_ADJ);
+            assertProcStates(apps[i], PROCESS_STATE_LAST_ACTIVITY, expectedAdj,
                     SCHED_GROUP_BACKGROUND, "previous");
         }
 
@@ -3090,13 +3114,13 @@
         mProcessStateController.setUidTempAllowlistStateLSP(MOCKAPP_UID, true);
 
         assertEquals(true, app.getUidRecord().isSetAllowListed());
-        assertEquals(true, app.mOptRecord.shouldNotFreeze());
-        assertEquals(true, app2.mOptRecord.shouldNotFreeze());
+        assertFreezeState(app, false);
+        assertFreezeState(app2, false);
 
         mProcessStateController.setUidTempAllowlistStateLSP(MOCKAPP_UID, false);
         assertEquals(false, app.getUidRecord().isSetAllowListed());
-        assertEquals(false, app.mOptRecord.shouldNotFreeze());
-        assertEquals(false, app2.mOptRecord.shouldNotFreeze());
+        assertFreezeState(app, true);
+        assertFreezeState(app2, true);
     }
 
     @SuppressWarnings("GuardedBy")
@@ -3118,25 +3142,25 @@
 
         assertEquals(true, app.getUidRecord().isSetAllowListed());
         assertEquals(true, app2.getUidRecord().isSetAllowListed());
-        assertEquals(true, app.mOptRecord.shouldNotFreeze());
-        assertEquals(true, app2.mOptRecord.shouldNotFreeze());
-        assertEquals(true, app3.mOptRecord.shouldNotFreeze());
+        assertFreezeState(app, false);
+        assertFreezeState(app2, false);
+        assertFreezeState(app3, false);
 
         // Remove app1 from allowlist.
         mProcessStateController.setUidTempAllowlistStateLSP(MOCKAPP_UID, false);
         assertEquals(false, app.getUidRecord().isSetAllowListed());
         assertEquals(true, app2.getUidRecord().isSetAllowListed());
-        assertEquals(false, app.mOptRecord.shouldNotFreeze());
-        assertEquals(true, app2.mOptRecord.shouldNotFreeze());
-        assertEquals(true, app3.mOptRecord.shouldNotFreeze());
+        assertFreezeState(app, true);
+        assertFreezeState(app2, false);
+        assertFreezeState(app3, false);
 
         // Now remove app2 from allowlist.
         mProcessStateController.setUidTempAllowlistStateLSP(MOCKAPP2_UID, false);
         assertEquals(false, app.getUidRecord().isSetAllowListed());
         assertEquals(false, app2.getUidRecord().isSetAllowListed());
-        assertEquals(false, app.mOptRecord.shouldNotFreeze());
-        assertEquals(false, app2.mOptRecord.shouldNotFreeze());
-        assertEquals(false, app3.mOptRecord.shouldNotFreeze());
+        assertFreezeState(app, true);
+        assertFreezeState(app2, true);
+        assertFreezeState(app3, true);
     }
 
     @SuppressWarnings("GuardedBy")
@@ -3184,7 +3208,8 @@
         setProcessesToLru(app1, app2);
         mProcessStateController.runFullUpdate(OOM_ADJ_REASON_NONE);
 
-        assertProcStates(app1, PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ,
+        assertProcStates(app1, PROCESS_STATE_LAST_ACTIVITY,
+                PREVIOUS_APP_ADJ + (Flags.oomadjusterPrevLaddering() ? 1 : 0),
                 SCHED_GROUP_BACKGROUND, "recent-provider");
         assertProcStates(app2, PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ,
                 SCHED_GROUP_BACKGROUND, "recent-provider");
@@ -3349,6 +3374,14 @@
         assertEquals(expectedCached, state.isCached());
     }
 
+    @SuppressWarnings("GuardedBy")
+    private void assertFreezeState(ProcessRecord app, boolean expectedFreezeState) {
+        boolean actualFreezeState = mTestCachedAppOptimizer.mLastSetFreezeState.get(app.getPid(),
+                false);
+        assertEquals("Unexcepted freeze state for " + app.processName, expectedFreezeState,
+                actualFreezeState);
+    }
+
     private class ProcessRecordBuilder {
         @SuppressWarnings("UnusedVariable")
         int mPid;
@@ -3492,6 +3525,39 @@
             return app;
         }
     }
+    private static final class TestProcessDependencies
+            implements CachedAppOptimizer.ProcessDependencies {
+        @Override
+        public long[] getRss(int pid) {
+            return new long[]{/*totalRSS*/ 0, /*fileRSS*/ 0, /*anonRSS*/ 0, /*swap*/ 0};
+        }
+
+        @Override
+        public void performCompaction(CachedAppOptimizer.CompactProfile action, int pid) {}
+    }
+
+    private static class TestCachedAppOptimizer extends CachedAppOptimizer {
+        private SparseBooleanArray mLastSetFreezeState = new SparseBooleanArray();
+
+        TestCachedAppOptimizer(ActivityManagerService ams) {
+            super(ams, null, new TestProcessDependencies());
+        }
+
+        @Override
+        public boolean useFreezer() {
+            return true;
+        }
+
+        @Override
+        public void freezeAppAsyncLSP(ProcessRecord app) {
+            mLastSetFreezeState.put(app.getPid(), true);
+        }
+
+        @Override
+        public void unfreezeAppLSP(ProcessRecord app, @UnfreezeReason int reason) {
+            mLastSetFreezeState.put(app.getPid(), false);
+        }
+    }
 
     static class OomAdjusterInjector extends OomAdjuster.Injector {
         // Jump ahead in time by this offset amount.
@@ -3503,7 +3569,6 @@
             mLastSetOomAdj.clear();
         }
 
-
         void jumpUptimeAheadTo(long uptimeMillis) {
             final long jumpMs = uptimeMillis - getUptimeMillis();
             if (jumpMs <= 0) return;
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java
index 1973428..f7c2e8b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java
@@ -21,6 +21,7 @@
 
 import static org.junit.Assert.assertEquals;
 
+import android.app.PropertyInvalidatedCache;
 import android.content.Context;
 import android.os.Handler;
 
@@ -63,6 +64,7 @@
 
     @Before
     public void setUp() {
+        PropertyInvalidatedCache.disableForTestMode();
         mSession = ExtendedMockito.mockitoSession()
                 .initMocks(this)
                 .strictness(Strictness.LENIENT)
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 4e1f741..dd7ce21 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -2351,6 +2351,7 @@
 
     /** Tests that jobs are removed from the pending list if the user stops the app. */
     @Test
+    @RequiresFlagsDisabled(android.app.job.Flags.FLAG_GET_PENDING_JOB_REASONS_API)
     public void testUserStopRemovesPending() {
         spyOn(mService);
 
@@ -2402,6 +2403,60 @@
         assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReason(job2b));
     }
 
+    /** Tests that jobs are removed from the pending list if the user stops the app. */
+    @Test
+    @RequiresFlagsEnabled(android.app.job.Flags.FLAG_GET_PENDING_JOB_REASONS_API)
+    public void testUserStopRemovesPending_withPendingJobReasonsApi() {
+        spyOn(mService);
+
+        JobStatus job1a = createJobStatus("testUserStopRemovesPending",
+                createJobInfo(1), 1, "pkg1");
+        JobStatus job1b = createJobStatus("testUserStopRemovesPending",
+                createJobInfo(2), 1, "pkg1");
+        JobStatus job2a = createJobStatus("testUserStopRemovesPending",
+                createJobInfo(1), 2, "pkg2");
+        JobStatus job2b = createJobStatus("testUserStopRemovesPending",
+                createJobInfo(2), 2, "pkg2");
+        doReturn(1).when(mPackageManagerInternal).getPackageUid("pkg1", 0, 0);
+        doReturn(11).when(mPackageManagerInternal).getPackageUid("pkg1", 0, 1);
+        doReturn(2).when(mPackageManagerInternal).getPackageUid("pkg2", 0, 0);
+
+        mService.getPendingJobQueue().clear();
+        mService.getPendingJobQueue().add(job1a);
+        mService.getPendingJobQueue().add(job1b);
+        mService.getPendingJobQueue().add(job2a);
+        mService.getPendingJobQueue().add(job2b);
+        mService.getJobStore().add(job1a);
+        mService.getJobStore().add(job1b);
+        mService.getJobStore().add(job2a);
+        mService.getJobStore().add(job2b);
+
+        mService.notePendingUserRequestedAppStopInternal("pkg1", 1, "test");
+        assertEquals(4, mService.getPendingJobQueue().size());
+        assertTrue(mService.getPendingJobQueue().contains(job1a));
+        assertTrue(mService.getPendingJobQueue().contains(job1b));
+        assertTrue(mService.getPendingJobQueue().contains(job2a));
+        assertTrue(mService.getPendingJobQueue().contains(job2b));
+
+        mService.notePendingUserRequestedAppStopInternal("pkg1", 0, "test");
+        assertEquals(2, mService.getPendingJobQueue().size());
+        assertFalse(mService.getPendingJobQueue().contains(job1a));
+        assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReasons(job1a)[0]);
+        assertFalse(mService.getPendingJobQueue().contains(job1b));
+        assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReasons(job1b)[0]);
+        assertTrue(mService.getPendingJobQueue().contains(job2a));
+        assertTrue(mService.getPendingJobQueue().contains(job2b));
+
+        mService.notePendingUserRequestedAppStopInternal("pkg2", 0, "test");
+        assertEquals(0, mService.getPendingJobQueue().size());
+        assertFalse(mService.getPendingJobQueue().contains(job1a));
+        assertFalse(mService.getPendingJobQueue().contains(job1b));
+        assertFalse(mService.getPendingJobQueue().contains(job2a));
+        assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReasons(job2a)[0]);
+        assertFalse(mService.getPendingJobQueue().contains(job2b));
+        assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReasons(job2b)[0]);
+    }
+
     /**
      * Unit tests {@link JobSchedulerService#checkIfRestricted(JobStatus)} with single {@link
      * JobRestriction} registered.
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 1cba3c5..8a10040 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -195,12 +195,12 @@
         doNothing().when(mSpiedContext).sendBroadcastAsUser(any(), any(), any());
         mockIsLowRamDevice(false);
 
-        // Called when getting boot user. config_bootToHeadlessSystemUser is false by default.
+        // Called when getting boot user. config_bootToHeadlessSystemUser is 0 by default.
         mSpyResources = spy(mSpiedContext.getResources());
         when(mSpiedContext.getResources()).thenReturn(mSpyResources);
-        doReturn(false)
+        doReturn(0)
                 .when(mSpyResources)
-                .getBoolean(com.android.internal.R.bool.config_bootToHeadlessSystemUser);
+                .getInteger(com.android.internal.R.integer.config_hsumBootStrategy);
 
         // Must construct UserManagerService in the UiThread
         mTestDir = new File(mRealContext.getDataDir(), "umstest");
@@ -859,15 +859,50 @@
     }
 
     @Test
-    public void testGetBootUser_enableBootToHeadlessSystemUser() {
+    public void testGetBootUser_Headless_BootToSystemUserWhenDeviceIsProvisioned() {
         setSystemUserHeadless(true);
-        doReturn(true)
+        addUser(USER_ID);
+        addUser(OTHER_USER_ID);
+        mockProvisionedDevice(true);
+        doReturn(1)
                 .when(mSpyResources)
-                .getBoolean(com.android.internal.R.bool.config_bootToHeadlessSystemUser);
+                .getInteger(com.android.internal.R.integer.config_hsumBootStrategy);
 
         assertThat(mUms.getBootUser()).isEqualTo(UserHandle.USER_SYSTEM);
     }
 
+    @Test
+    public void testGetBootUser_Headless_BootToFirstSwitchableFullUserWhenDeviceNotProvisioned() {
+        setSystemUserHeadless(true);
+        addUser(USER_ID);
+        addUser(OTHER_USER_ID);
+        mockProvisionedDevice(false);
+        doReturn(1)
+                .when(mSpyResources)
+                .getInteger(com.android.internal.R.integer.config_hsumBootStrategy);
+        // Even if the headless system user switchable flag is true, the boot user should be the
+        // first switchable full user.
+        doReturn(true)
+                .when(mSpyResources)
+                .getBoolean(com.android.internal.R.bool.config_canSwitchToHeadlessSystemUser);
+
+        assertThat(mUms.getBootUser()).isEqualTo(USER_ID);
+    }
+
+    @Test
+    public void testGetBootUser_Headless_ThrowsIfBootFailsNoFullUserWhenDeviceNotProvisioned()
+                throws Exception {
+        setSystemUserHeadless(true);
+        removeNonSystemUsers();
+        mockProvisionedDevice(false);
+        doReturn(1)
+                .when(mSpyResources)
+                .getInteger(com.android.internal.R.integer.config_hsumBootStrategy);
+
+        assertThrows(ServiceSpecificException.class,
+                () -> mUms.getBootUser());
+    }
+
     /**
      * Returns true if the user's XML file has Default restrictions
      * @param userId Id of the user.
@@ -935,6 +970,11 @@
                 any(), eq(android.provider.Settings.Global.USER_SWITCHER_ENABLED), anyInt()));
     }
 
+    private void mockProvisionedDevice(boolean isProvisionedDevice) {
+        doReturn(isProvisionedDevice ? 1 : 0).when(() -> Settings.Global.getInt(
+                any(), eq(android.provider.Settings.Global.DEVICE_PROVISIONED), anyInt()));
+    }
+
     private void mockIsLowRamDevice(boolean isLowRamDevice) {
         doReturn(isLowRamDevice).when(ActivityManager::isLowRamDeviceStatic);
     }
diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java
index 648da65..4e29e74 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java
@@ -49,17 +49,23 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.content.Context;
 import android.hardware.display.DisplayManagerInternal;
 import android.os.PowerManager;
 import android.os.PowerSaveState;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.view.Display;
 
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.internal.util.LatencyTracker;
+import com.android.server.LocalServices;
+import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
 import com.android.server.power.feature.PowerManagerFlags;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
@@ -73,6 +79,9 @@
 public class PowerGroupTest {
 
     private static final int GROUP_ID = 0;
+    private static final int NON_DEFAULT_GROUP_ID = 1;
+    private static final int NON_DEFAULT_DISPLAY_ID = 2;
+    private static final int VIRTUAL_DEVICE_ID = 3;
     private static final int UID = 11;
     private static final long TIMESTAMP_CREATE = 1;
     private static final long TIMESTAMP1 = 999;
@@ -87,10 +96,16 @@
     private static final LatencyTracker LATENCY_TRACKER = LatencyTracker.getInstance(
             InstrumentationRegistry.getInstrumentation().getContext());
 
+    private static final long DEFAULT_TIMEOUT = 1234L;
+
+    @Rule
+    public SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     private PowerGroup mPowerGroup;
     @Mock private PowerGroup.PowerGroupListener mWakefulnessCallbackMock;
     @Mock private Notifier mNotifier;
     @Mock private DisplayManagerInternal mDisplayManagerInternal;
+    @Mock private VirtualDeviceManagerInternal mVirtualDeviceManagerInternal;
     @Mock private PowerManagerFlags mFeatureFlags;
 
 
@@ -740,4 +755,111 @@
         assertThat(displayPowerRequest.screenLowPowerBrightnessFactor).isWithin(PRECISION).of(
                 brightnessFactor);
     }
+
+    @Test
+    public void testTimeoutsOverride_defaultGroup_noOverride() {
+        assertThat(mPowerGroup.getScreenDimDurationOverrideLocked(DEFAULT_TIMEOUT))
+                .isEqualTo(DEFAULT_TIMEOUT);
+        assertThat(mPowerGroup.getScreenOffTimeoutOverrideLocked(DEFAULT_TIMEOUT))
+                .isEqualTo(DEFAULT_TIMEOUT);
+    }
+
+    @EnableFlags(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+    @Test
+    public void testTimeoutsOverride_noVdm_noOverride() {
+        LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class);
+        LocalServices.addService(VirtualDeviceManagerInternal.class, null);
+
+        mPowerGroup = new PowerGroup(NON_DEFAULT_GROUP_ID, mWakefulnessCallbackMock, mNotifier,
+                mDisplayManagerInternal, WAKEFULNESS_AWAKE, /* ready= */ true,
+                /* supportsSandman= */ true, TIMESTAMP_CREATE, mFeatureFlags);
+
+        assertThat(mPowerGroup.getScreenDimDurationOverrideLocked(DEFAULT_TIMEOUT))
+                .isEqualTo(DEFAULT_TIMEOUT);
+        assertThat(mPowerGroup.getScreenOffTimeoutOverrideLocked(DEFAULT_TIMEOUT))
+                .isEqualTo(DEFAULT_TIMEOUT);
+    }
+
+    @EnableFlags(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+    @Test
+    public void testTimeoutsOverride_notValidVirtualDeviceId_noOverride() {
+        LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class);
+        LocalServices.addService(VirtualDeviceManagerInternal.class, mVirtualDeviceManagerInternal);
+
+        when(mDisplayManagerInternal.getDisplayIdsForGroup(NON_DEFAULT_GROUP_ID))
+                .thenReturn(new int[] {NON_DEFAULT_DISPLAY_ID});
+        when(mVirtualDeviceManagerInternal.getDeviceIdForDisplayId(NON_DEFAULT_DISPLAY_ID))
+                .thenReturn(Context.DEVICE_ID_DEFAULT);
+        when(mVirtualDeviceManagerInternal.isValidVirtualDeviceId(Context.DEVICE_ID_DEFAULT))
+                .thenReturn(false);
+
+        mPowerGroup = new PowerGroup(NON_DEFAULT_GROUP_ID, mWakefulnessCallbackMock, mNotifier,
+                mDisplayManagerInternal, WAKEFULNESS_AWAKE, /* ready= */ true,
+                /* supportsSandman= */ true, TIMESTAMP_CREATE, mFeatureFlags);
+
+        assertThat(mPowerGroup.getScreenDimDurationOverrideLocked(DEFAULT_TIMEOUT))
+                .isEqualTo(DEFAULT_TIMEOUT);
+        assertThat(mPowerGroup.getScreenOffTimeoutOverrideLocked(DEFAULT_TIMEOUT))
+                .isEqualTo(DEFAULT_TIMEOUT);
+    }
+
+    @EnableFlags(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+    @Test
+    public void testTimeoutsOverride_validVirtualDeviceId_timeoutsAreOverridden() {
+        LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class);
+        LocalServices.addService(VirtualDeviceManagerInternal.class, mVirtualDeviceManagerInternal);
+
+        final long dimDurationOverride = DEFAULT_TIMEOUT * 3;
+        final long screenOffTimeoutOverride = DEFAULT_TIMEOUT * 5;
+
+        when(mDisplayManagerInternal.getDisplayIdsForGroup(NON_DEFAULT_GROUP_ID))
+                .thenReturn(new int[] {NON_DEFAULT_DISPLAY_ID});
+        when(mVirtualDeviceManagerInternal.getDeviceIdForDisplayId(NON_DEFAULT_DISPLAY_ID))
+                .thenReturn(VIRTUAL_DEVICE_ID);
+        when(mVirtualDeviceManagerInternal.isValidVirtualDeviceId(VIRTUAL_DEVICE_ID))
+                .thenReturn(true);
+        when(mVirtualDeviceManagerInternal.getDimDurationMillisForDeviceId(VIRTUAL_DEVICE_ID))
+                .thenReturn(dimDurationOverride);
+        when(mVirtualDeviceManagerInternal.getScreenOffTimeoutMillisForDeviceId(VIRTUAL_DEVICE_ID))
+                .thenReturn(screenOffTimeoutOverride);
+
+        mPowerGroup = new PowerGroup(NON_DEFAULT_GROUP_ID, mWakefulnessCallbackMock, mNotifier,
+                mDisplayManagerInternal, WAKEFULNESS_AWAKE, /* ready= */ true,
+                /* supportsSandman= */ true, TIMESTAMP_CREATE, mFeatureFlags);
+
+        assertThat(mPowerGroup.getScreenDimDurationOverrideLocked(DEFAULT_TIMEOUT))
+                .isEqualTo(dimDurationOverride);
+        assertThat(mPowerGroup.getScreenOffTimeoutOverrideLocked(DEFAULT_TIMEOUT))
+                .isEqualTo(screenOffTimeoutOverride);
+    }
+
+    @EnableFlags(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+    @Test
+    public void testTimeoutsOverrides_dimDurationIsCapped() {
+        LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class);
+        LocalServices.addService(VirtualDeviceManagerInternal.class, mVirtualDeviceManagerInternal);
+
+        final long dimDurationOverride = DEFAULT_TIMEOUT * 5;
+        final long screenOffTimeoutOverride = DEFAULT_TIMEOUT * 3;
+
+        when(mDisplayManagerInternal.getDisplayIdsForGroup(NON_DEFAULT_GROUP_ID))
+                .thenReturn(new int[] {NON_DEFAULT_DISPLAY_ID});
+        when(mVirtualDeviceManagerInternal.getDeviceIdForDisplayId(NON_DEFAULT_DISPLAY_ID))
+                .thenReturn(VIRTUAL_DEVICE_ID);
+        when(mVirtualDeviceManagerInternal.isValidVirtualDeviceId(VIRTUAL_DEVICE_ID))
+                .thenReturn(true);
+        when(mVirtualDeviceManagerInternal.getDimDurationMillisForDeviceId(VIRTUAL_DEVICE_ID))
+                .thenReturn(dimDurationOverride);
+        when(mVirtualDeviceManagerInternal.getScreenOffTimeoutMillisForDeviceId(VIRTUAL_DEVICE_ID))
+                .thenReturn(screenOffTimeoutOverride);
+
+        mPowerGroup = new PowerGroup(NON_DEFAULT_GROUP_ID, mWakefulnessCallbackMock, mNotifier,
+                mDisplayManagerInternal, WAKEFULNESS_AWAKE, /* ready= */ true,
+                /* supportsSandman= */ true, TIMESTAMP_CREATE, mFeatureFlags);
+
+        assertThat(mPowerGroup.getScreenDimDurationOverrideLocked(DEFAULT_TIMEOUT))
+                .isEqualTo(screenOffTimeoutOverride);
+        assertThat(mPowerGroup.getScreenOffTimeoutOverrideLocked(DEFAULT_TIMEOUT))
+                .isEqualTo(screenOffTimeoutOverride);
+    }
 }
diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
index b10200d..359cf63 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -85,6 +85,7 @@
 import android.os.PowerSaveState;
 import android.os.UserHandle;
 import android.os.test.TestLooper;
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
@@ -102,6 +103,7 @@
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
 import com.android.server.display.feature.DeviceConfigParameterProvider;
 import com.android.server.lights.LightsManager;
 import com.android.server.policy.WindowManagerPolicy;
@@ -167,6 +169,7 @@
     @Mock private BatteryManagerInternal mBatteryManagerInternalMock;
     @Mock private ActivityManagerInternal mActivityManagerInternalMock;
     @Mock private AttentionManagerInternal mAttentionManagerInternalMock;
+    @Mock private VirtualDeviceManagerInternal mVirtualDeviceManagerInternalMock;
     @Mock private DreamManagerInternal mDreamManagerInternalMock;
     @Mock private PowerManagerService.NativeWrapper mNativeWrapperMock;
     @Mock private FoldGracePeriodProvider mFoldGracePeriodProvider;
@@ -246,6 +249,7 @@
         addLocalServiceMock(ActivityManagerInternal.class, mActivityManagerInternalMock);
         addLocalServiceMock(AttentionManagerInternal.class, mAttentionManagerInternalMock);
         addLocalServiceMock(DreamManagerInternal.class, mDreamManagerInternalMock);
+        addLocalServiceMock(VirtualDeviceManagerInternal.class, mVirtualDeviceManagerInternalMock);
 
         mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
         mResourcesSpy = spy(mContextSpy.getResources());
@@ -1200,6 +1204,59 @@
         assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
     }
 
+    @EnableFlags(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+    @SuppressWarnings("GuardedBy")
+    @Test
+    public void testNonDefaultDisplayGroupWithCustomTimeout_afterTimeout_goesToDozing() {
+        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+        final int nonDefaultDisplayId = Display.DEFAULT_DISPLAY + 2;
+        final int virtualDeviceId = Context.DEVICE_ID_DEFAULT + 3;
+        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+                new AtomicReference<>();
+        doAnswer((Answer<Void>) invocation -> {
+            listener.set(invocation.getArgument(0));
+            return null;
+        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+        final DisplayInfo info = new DisplayInfo();
+        info.displayGroupId = nonDefaultDisplayGroupId;
+        when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplayId)).thenReturn(info);
+        when(mDisplayManagerInternalMock.getDisplayIdsForGroup(nonDefaultDisplayGroupId))
+                .thenReturn(new int[] {nonDefaultDisplayId});
+        when(mVirtualDeviceManagerInternalMock.getDeviceIdForDisplayId(nonDefaultDisplayId))
+                .thenReturn(virtualDeviceId);
+        when(mVirtualDeviceManagerInternalMock.isValidVirtualDeviceId(virtualDeviceId))
+                .thenReturn(true);
+        when(mVirtualDeviceManagerInternalMock
+                .getScreenOffTimeoutMillisForDeviceId(virtualDeviceId))
+                .thenReturn(20000L);
+
+        setMinimumScreenOffTimeoutConfig(10000);
+        createService();
+        startSystem();
+        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP))
+                .isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId))
+                .isEqualTo(WAKEFULNESS_AWAKE);
+
+        // The default timeout is 10s, the custom group timeout is 20s.
+
+        advanceTime(15000);
+
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP))
+                .isEqualTo(WAKEFULNESS_ASLEEP);
+        assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId))
+                .isEqualTo(WAKEFULNESS_AWAKE);
+
+        advanceTime(10000);
+
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP))
+                .isEqualTo(WAKEFULNESS_ASLEEP);
+        assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId))
+                .isEqualTo(WAKEFULNESS_DOZING);
+    }
+
     @SuppressWarnings("GuardedBy")
     @Test
     public void testAmbientSuppression_disablesDreamingAndWakesDevice() {
@@ -3348,7 +3405,8 @@
                         null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY,
                         null /* callback */);
             }
-            assertThat(mService.getScreenOffTimeoutOverrideLocked(screenTimeout, screenDimTimeout))
+            assertThat(mService.getDefaultGroupScreenOffTimeoutOverrideLocked(screenTimeout,
+                           screenDimTimeout))
                     .isEqualTo(expect[2]);
             if (acquireWakeLock) {
                 mService.getBinderServiceInstance().releaseWakeLock(token, 0);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java
index 0a1fc31..f02a389 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java
@@ -438,10 +438,7 @@
         doAnswer(invocation -> {
             LongArrayMultiStateCounter counter = invocation.getArgument(1);
             long timestampMs = invocation.getArgument(2);
-            LongArrayMultiStateCounter.LongArrayContainer container =
-                    new LongArrayMultiStateCounter.LongArrayContainer(NUM_CPU_FREQS);
-            container.setValues(cpuTimes);
-            counter.updateValues(container, timestampMs);
+            counter.updateValues(cpuTimes, timestampMs);
             return null;
         }).when(mKernelSingleUidTimeReader).addDelta(eq(testUid),
                 any(LongArrayMultiStateCounter.class), anyLong());
@@ -451,20 +448,13 @@
         doAnswer(invocation -> {
             LongArrayMultiStateCounter counter = invocation.getArgument(1);
             long timestampMs = invocation.getArgument(2);
-            LongArrayMultiStateCounter.LongArrayContainer deltaContainer =
-                    invocation.getArgument(3);
-
-            LongArrayMultiStateCounter.LongArrayContainer container =
-                    new LongArrayMultiStateCounter.LongArrayContainer(NUM_CPU_FREQS);
-            container.setValues(cpuTimes);
-            counter.updateValues(container, timestampMs);
-            if (deltaContainer != null) {
-                deltaContainer.setValues(delta);
-            }
+            long[] deltaOut = invocation.getArgument(3);
+            counter.updateValues(cpuTimes, timestampMs);
+            System.arraycopy(delta, 0, deltaOut, 0, delta.length);
             return null;
         }).when(mKernelSingleUidTimeReader).addDelta(eq(testUid),
                 any(LongArrayMultiStateCounter.class), anyLong(),
-                any(LongArrayMultiStateCounter.LongArrayContainer.class));
+                any(long[].class));
     }
 
     @Test
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java
index 71a65c8..4cea728 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java
@@ -394,10 +394,7 @@
         doAnswer(invocation -> {
             LongArrayMultiStateCounter counter = invocation.getArgument(1);
             long timestampMs = invocation.getArgument(2);
-            LongArrayMultiStateCounter.LongArrayContainer container =
-                    new LongArrayMultiStateCounter.LongArrayContainer(NUM_CPU_FREQS);
-            container.setValues(cpuTimes);
-            counter.updateValues(container, timestampMs);
+            counter.updateValues(cpuTimes, timestampMs);
             return null;
         }).when(mMockKernelSingleUidTimeReader).addDelta(eq(uid),
                 any(LongArrayMultiStateCounter.class), anyLong());
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsRecentAccessPersistenceTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsRecentAccessPersistenceTest.java
index c4b3c149..5d7ffe9 100644
--- a/services/tests/servicestests/src/com/android/server/appop/AppOpsRecentAccessPersistenceTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsRecentAccessPersistenceTest.java
@@ -24,6 +24,7 @@
 import static org.mockito.Mockito.when;
 
 import android.app.AppOpsManager;
+import android.app.PropertyInvalidatedCache;
 import android.companion.virtual.VirtualDeviceManager;
 import android.content.Context;
 import android.os.FileUtils;
@@ -69,6 +70,7 @@
 
     @Before
     public void setUp() {
+        PropertyInvalidatedCache.disableForTestMode();
         when(mAppOpCheckingService.addAppOpsModeChangedListener(any())).thenReturn(true);
         LocalServices.addService(AppOpsCheckingServiceInterface.class, mAppOpCheckingService);
 
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
index c9e9f00..c418151 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
@@ -16,6 +16,7 @@
 package com.android.server.audio;
 
 import static android.media.AudioDeviceInfo.TYPE_BUILTIN_MIC;
+import static android.media.AudioManager.GET_DEVICES_INPUTS;
 
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -34,7 +35,9 @@
 import android.os.Looper;
 import android.os.PermissionEnforcer;
 import android.os.UserHandle;
+import android.os.test.TestLooper;
 import android.platform.test.annotations.EnableFlags;
+import android.util.IntArray;
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
@@ -80,12 +83,15 @@
 
     private static boolean sLooperPrepared = false;
 
+    private TestLooper mTestLooper;
+
     @Before
     public void setUp() throws Exception {
         if (!sLooperPrepared) {
             Looper.prepare();
             sLooperPrepared = true;
         }
+        mTestLooper = new TestLooper();
         mContext = InstrumentationRegistry.getTargetContext();
         mSpyAudioSystem = spy(new NoOpAudioSystemAdapter());
         mSettingsAdapter = new NoOpSettingsAdapter();
@@ -93,8 +99,11 @@
         when(mMockAppOpsManager.noteOp(anyInt(), anyInt(), anyString(), anyString(), anyString()))
                 .thenReturn(AppOpsManager.MODE_ALLOWED);
         mAudioService = new AudioService(mContext, mSpyAudioSystem, mSpySystemServer,
-                mSettingsAdapter, mAudioVolumeGroupHelper, mMockAudioPolicy, null,
-                mMockAppOpsManager, mMockPermissionEnforcer, mMockPermissionProvider, r -> r.run());
+                mSettingsAdapter, mAudioVolumeGroupHelper, mMockAudioPolicy,
+                mTestLooper.getLooper(), mMockAppOpsManager, mMockPermissionEnforcer,
+                mMockPermissionProvider, r -> r.run());
+
+        mTestLooper.dispatchAll();
     }
 
     /**
@@ -216,7 +225,19 @@
     public void testInputGainIndex() throws Exception {
         Log.i(TAG, "running testInputGainIndex");
         Assert.assertNotNull(mAudioService);
-        Thread.sleep(MAX_MESSAGE_HANDLING_DELAY_MS); // wait for full AudioService initialization
+
+        IntArray internalDeviceTypes = new IntArray();
+        int status = AudioSystem.getSupportedDeviceTypes(GET_DEVICES_INPUTS, internalDeviceTypes);
+        if (status != AudioSystem.SUCCESS) {
+            Log.e(TAG, "AudioSystem.getSupportedDeviceTypes(GET_DEVICES_INPUTS) failed. status:"
+                    + status);
+        }
+
+        // Make sure TYPE_BUILTIN_MIC, aka DEVICE_IN_BUILTIN_MIC in terms of internal device type,
+        // is supported.
+        if (!internalDeviceTypes.contains(AudioSystem.DEVICE_IN_BUILTIN_MIC)) {
+            return;
+        }
 
         AudioDeviceAttributes ada =
                 new AudioDeviceAttributes(
@@ -229,6 +250,8 @@
 
         int inputGainIndex = 20;
         mAudioService.setInputGainIndex(ada, inputGainIndex);
+        mTestLooper.dispatchAll();
+
         Assert.assertEquals(
                 "input gain index reporting wrong value",
                 inputGainIndex,
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index e386808..727d1b5 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -64,6 +64,7 @@
 import android.companion.virtual.sensor.VirtualSensor;
 import android.companion.virtual.sensor.VirtualSensorCallback;
 import android.companion.virtual.sensor.VirtualSensorConfig;
+import android.companion.virtualdevice.flags.Flags;
 import android.content.AttributionSource;
 import android.content.ComponentName;
 import android.content.Context;
@@ -103,6 +104,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.WorkSource;
+import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.Presubmit;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.testing.AndroidTestingRunner;
@@ -999,6 +1001,7 @@
                 nullable(String.class), anyInt(), eq(null));
     }
 
+    @DisableFlags(Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
     @Test
     public void onVirtualDisplayCreatedLocked_wakeLockIsAcquired() throws RemoteException {
         verify(mIPowerManagerMock, never()).acquireWakeLock(any(Binder.class), anyInt(),
@@ -1010,6 +1013,7 @@
                 nullable(String.class), eq(DISPLAY_ID_1), eq(null));
     }
 
+    @DisableFlags(Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
     @Test
     public void onVirtualDisplayCreatedLocked_duplicateCalls_onlyOneWakeLockIsAcquired()
             throws RemoteException {
@@ -1022,6 +1026,7 @@
                 nullable(String.class), eq(DISPLAY_ID_1), eq(null));
     }
 
+    @DisableFlags(Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
     @Test
     public void onVirtualDisplayRemovedLocked_wakeLockIsReleased() throws RemoteException {
         addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED);
@@ -1037,6 +1042,7 @@
         verify(mIPowerManagerMock).releaseWakeLock(eq(wakeLock), anyInt());
     }
 
+    @DisableFlags(Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
     @Test
     public void addVirtualDisplay_displayNotReleased_wakeLockIsReleased() throws RemoteException {
         addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index c741c6c..077bb03 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -2562,7 +2562,13 @@
         mTestLooper.dispatchAll();
 
         // User interacted with the DUT, so the device will not go to standby.
-        skipActiveSourceLostUi(0, true, true);
+        mHdmiControlService.oneTouchPlay(new IHdmiControlCallback.Stub() {
+            @Override
+            public void onComplete(int result) {
+            }
+        });
+        mTestLooper.dispatchAll();
+
         assertThat(mIsOnActiveSourceLostPopupActive).isFalse();
         assertThat(mPowerManager.isInteractive()).isTrue();
         assertThat(mNativeWrapper.getResultMessages().contains(activeSourceFromPlayback)).isTrue();
diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java
deleted file mode 100644
index 9ed2e88..0000000
--- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java
+++ /dev/null
@@ -1,914 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.serializer;
-
-import static com.android.server.integrity.model.ComponentBitSize.ATOMIC_FORMULA_START;
-import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_END;
-import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_START;
-import static com.android.server.integrity.model.ComponentBitSize.CONNECTOR_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.DEFAULT_FORMAT_VERSION;
-import static com.android.server.integrity.model.ComponentBitSize.EFFECT_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.FORMAT_VERSION_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.KEY_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.SEPARATOR_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS;
-import static com.android.server.integrity.model.IndexingFileConstants.END_INDEXING_KEY;
-import static com.android.server.integrity.model.IndexingFileConstants.INDEXING_BLOCK_SIZE;
-import static com.android.server.integrity.model.IndexingFileConstants.START_INDEXING_KEY;
-import static com.android.server.integrity.serializer.RuleBinarySerializer.INDEXED_RULE_SIZE_LIMIT;
-import static com.android.server.integrity.serializer.RuleBinarySerializer.NONINDEXED_RULE_SIZE_LIMIT;
-import static com.android.server.integrity.utils.TestUtils.getBits;
-import static com.android.server.integrity.utils.TestUtils.getBytes;
-import static com.android.server.integrity.utils.TestUtils.getValueBits;
-import static com.android.server.testutils.TestUtils.assertExpectException;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.integrity.AppInstallMetadata;
-import android.content.integrity.AtomicFormula;
-import android.content.integrity.CompoundFormula;
-import android.content.integrity.IntegrityFormula;
-import android.content.integrity.IntegrityUtils;
-import android.content.integrity.Rule;
-
-import androidx.annotation.NonNull;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.io.ByteArrayOutputStream;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Optional;
-
-@RunWith(JUnit4.class)
-public class RuleBinarySerializerTest {
-
-    private static final String SAMPLE_INSTALLER_NAME = "com.test.installer";
-    private static final String SAMPLE_INSTALLER_CERT = "installer_cert";
-
-    private static final String COMPOUND_FORMULA_START_BITS =
-            getBits(COMPOUND_FORMULA_START, SEPARATOR_BITS);
-    private static final String COMPOUND_FORMULA_END_BITS =
-            getBits(COMPOUND_FORMULA_END, SEPARATOR_BITS);
-    private static final String ATOMIC_FORMULA_START_BITS =
-            getBits(ATOMIC_FORMULA_START, SEPARATOR_BITS);
-
-    private static final String NOT = getBits(CompoundFormula.NOT, CONNECTOR_BITS);
-    private static final String AND = getBits(CompoundFormula.AND, CONNECTOR_BITS);
-    private static final String OR = getBits(CompoundFormula.OR, CONNECTOR_BITS);
-
-    private static final String PACKAGE_NAME = getBits(AtomicFormula.PACKAGE_NAME, KEY_BITS);
-    private static final String APP_CERTIFICATE = getBits(AtomicFormula.APP_CERTIFICATE, KEY_BITS);
-    private static final String INSTALLER_NAME = getBits(AtomicFormula.INSTALLER_NAME, KEY_BITS);
-    private static final String INSTALLER_CERTIFICATE =
-            getBits(AtomicFormula.INSTALLER_CERTIFICATE, KEY_BITS);
-    private static final String VERSION_CODE = getBits(AtomicFormula.VERSION_CODE, KEY_BITS);
-    private static final String PRE_INSTALLED = getBits(AtomicFormula.PRE_INSTALLED, KEY_BITS);
-
-    private static final String EQ = getBits(AtomicFormula.EQ, OPERATOR_BITS);
-
-    private static final String IS_NOT_HASHED = "0";
-    private static final String IS_HASHED = "1";
-
-    private static final String DENY = getBits(Rule.DENY, EFFECT_BITS);
-
-    private static final String START_BIT = "1";
-    private static final String END_BIT = "1";
-
-    private static final byte[] DEFAULT_FORMAT_VERSION_BYTES =
-            getBytes(getBits(DEFAULT_FORMAT_VERSION, FORMAT_VERSION_BITS));
-
-    private static final String SERIALIZED_START_INDEXING_KEY =
-            IS_NOT_HASHED
-                    + getBits(START_INDEXING_KEY.length(), VALUE_SIZE_BITS)
-                    + getValueBits(START_INDEXING_KEY);
-    private static final String SERIALIZED_END_INDEXING_KEY =
-            IS_NOT_HASHED
-                    + getBits(END_INDEXING_KEY.length(), VALUE_SIZE_BITS)
-                    + getValueBits(END_INDEXING_KEY);
-
-    @Test
-    public void testBinaryString_serializeNullRules() {
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-
-        assertExpectException(
-                RuleSerializeException.class,
-                /* expectedExceptionMessageRegex= */ "Null rules cannot be serialized.",
-                () -> binarySerializer.serialize(null, /* formatVersion= */ Optional.empty()));
-    }
-
-    @Test
-    public void testBinaryString_emptyRules() throws Exception {
-        ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
-        ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-
-        binarySerializer.serialize(
-                Collections.emptyList(),
-                /* formatVersion= */ Optional.empty(),
-                ruleOutputStream,
-                indexingOutputStream);
-
-        ByteArrayOutputStream expectedRuleOutputStream = new ByteArrayOutputStream();
-        expectedRuleOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
-        assertThat(ruleOutputStream.toByteArray())
-                .isEqualTo(expectedRuleOutputStream.toByteArray());
-
-        ByteArrayOutputStream expectedIndexingOutputStream = new ByteArrayOutputStream();
-        String serializedIndexingBytes =
-                SERIALIZED_START_INDEXING_KEY
-                        + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32)
-                        + SERIALIZED_END_INDEXING_KEY
-                        + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32);
-        byte[] expectedIndexingBytes =
-                getBytes(
-                        serializedIndexingBytes
-                                + serializedIndexingBytes
-                                + serializedIndexingBytes);
-        expectedIndexingOutputStream.write(expectedIndexingBytes);
-        assertThat(indexingOutputStream.toByteArray())
-                .isEqualTo(expectedIndexingOutputStream.toByteArray());
-    }
-
-    @Test
-    public void testBinaryStream_serializeValidCompoundFormula() throws Exception {
-        String packageName = "com.test.app";
-        Rule rule =
-                new Rule(
-                        new CompoundFormula(
-                                CompoundFormula.NOT,
-                                Collections.singletonList(
-                                        new AtomicFormula.StringAtomicFormula(
-                                                AtomicFormula.PACKAGE_NAME,
-                                                packageName,
-                                                /* isHashedValue= */ false))),
-                        Rule.DENY);
-
-        ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
-        ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-        binarySerializer.serialize(
-                Collections.singletonList(rule),
-                /* formatVersion= */ Optional.empty(),
-                ruleOutputStream,
-                indexingOutputStream);
-
-        String expectedBits =
-                START_BIT
-                        + COMPOUND_FORMULA_START_BITS
-                        + NOT
-                        + ATOMIC_FORMULA_START_BITS
-                        + PACKAGE_NAME
-                        + EQ
-                        + IS_NOT_HASHED
-                        + getBits(packageName.length(), VALUE_SIZE_BITS)
-                        + getValueBits(packageName)
-                        + COMPOUND_FORMULA_END_BITS
-                        + DENY
-                        + END_BIT;
-        ByteArrayOutputStream expectedRuleOutputStream = new ByteArrayOutputStream();
-        expectedRuleOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
-        expectedRuleOutputStream.write(getBytes(expectedBits));
-        assertThat(ruleOutputStream.toByteArray())
-                .isEqualTo(expectedRuleOutputStream.toByteArray());
-
-        ByteArrayOutputStream expectedIndexingOutputStream = new ByteArrayOutputStream();
-        String expectedIndexingBitsForIndexed =
-                SERIALIZED_START_INDEXING_KEY
-                        + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32)
-                        + SERIALIZED_END_INDEXING_KEY
-                        + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32);
-        String expectedIndexingBitsForUnindexed =
-                SERIALIZED_START_INDEXING_KEY
-                        + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32)
-                        + SERIALIZED_END_INDEXING_KEY
-                        + getBits(
-                                DEFAULT_FORMAT_VERSION_BYTES.length + getBytes(expectedBits).length,
-                                /* numOfBits= */ 32);
-        expectedIndexingOutputStream.write(
-                getBytes(
-                        expectedIndexingBitsForIndexed
-                                + expectedIndexingBitsForIndexed
-                                + expectedIndexingBitsForUnindexed));
-
-        assertThat(indexingOutputStream.toByteArray())
-                .isEqualTo(expectedIndexingOutputStream.toByteArray());
-    }
-
-    @Test
-    public void testBinaryString_serializeValidCompoundFormula_notConnector() throws Exception {
-        String packageName = "com.test.app";
-        Rule rule =
-                new Rule(
-                        new CompoundFormula(
-                                CompoundFormula.NOT,
-                                Collections.singletonList(
-                                        new AtomicFormula.StringAtomicFormula(
-                                                AtomicFormula.PACKAGE_NAME,
-                                                packageName,
-                                                /* isHashedValue= */ false))),
-                        Rule.DENY);
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-        String expectedBits =
-                START_BIT
-                        + COMPOUND_FORMULA_START_BITS
-                        + NOT
-                        + ATOMIC_FORMULA_START_BITS
-                        + PACKAGE_NAME
-                        + EQ
-                        + IS_NOT_HASHED
-                        + getBits(packageName.length(), VALUE_SIZE_BITS)
-                        + getValueBits(packageName)
-                        + COMPOUND_FORMULA_END_BITS
-                        + DENY
-                        + END_BIT;
-        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
-        byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
-        byteArrayOutputStream.write(getBytes(expectedBits));
-        byte[] expectedRules = byteArrayOutputStream.toByteArray();
-
-        byte[] actualRules =
-                binarySerializer.serialize(
-                        Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
-
-        assertThat(actualRules).isEqualTo(expectedRules);
-    }
-
-    @Test
-    public void testBinaryString_serializeValidCompoundFormula_andConnector() throws Exception {
-        String packageName = "com.test.app";
-        String appCertificate = "test_cert";
-        Rule rule =
-                new Rule(
-                        new CompoundFormula(
-                                CompoundFormula.AND,
-                                Arrays.asList(
-                                        new AtomicFormula.StringAtomicFormula(
-                                                AtomicFormula.PACKAGE_NAME,
-                                                packageName,
-                                                /* isHashedValue= */ false),
-                                        new AtomicFormula.StringAtomicFormula(
-                                                AtomicFormula.APP_CERTIFICATE,
-                                                appCertificate,
-                                                /* isHashedValue= */ false))),
-                        Rule.DENY);
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-        String expectedBits =
-                START_BIT
-                        + COMPOUND_FORMULA_START_BITS
-                        + AND
-                        + ATOMIC_FORMULA_START_BITS
-                        + PACKAGE_NAME
-                        + EQ
-                        + IS_NOT_HASHED
-                        + getBits(packageName.length(), VALUE_SIZE_BITS)
-                        + getValueBits(packageName)
-                        + ATOMIC_FORMULA_START_BITS
-                        + APP_CERTIFICATE
-                        + EQ
-                        + IS_NOT_HASHED
-                        + getBits(appCertificate.length(), VALUE_SIZE_BITS)
-                        + getValueBits(appCertificate)
-                        + COMPOUND_FORMULA_END_BITS
-                        + DENY
-                        + END_BIT;
-        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
-        byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
-        byteArrayOutputStream.write(getBytes(expectedBits));
-        byte[] expectedRules = byteArrayOutputStream.toByteArray();
-
-        byte[] actualRules =
-                binarySerializer.serialize(
-                        Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
-
-        assertThat(actualRules).isEqualTo(expectedRules);
-    }
-
-    @Test
-    public void testBinaryString_serializeValidCompoundFormula_orConnector() throws Exception {
-        String packageName = "com.test.app";
-        String appCertificate = "test_cert";
-        Rule rule =
-                new Rule(
-                        new CompoundFormula(
-                                CompoundFormula.OR,
-                                Arrays.asList(
-                                        new AtomicFormula.StringAtomicFormula(
-                                                AtomicFormula.PACKAGE_NAME,
-                                                packageName,
-                                                /* isHashedValue= */ false),
-                                        new AtomicFormula.StringAtomicFormula(
-                                                AtomicFormula.APP_CERTIFICATE,
-                                                appCertificate,
-                                                /* isHashedValue= */ false))),
-                        Rule.DENY);
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-        String expectedBits =
-                START_BIT
-                        + COMPOUND_FORMULA_START_BITS
-                        + OR
-                        + ATOMIC_FORMULA_START_BITS
-                        + PACKAGE_NAME
-                        + EQ
-                        + IS_NOT_HASHED
-                        + getBits(packageName.length(), VALUE_SIZE_BITS)
-                        + getValueBits(packageName)
-                        + ATOMIC_FORMULA_START_BITS
-                        + APP_CERTIFICATE
-                        + EQ
-                        + IS_NOT_HASHED
-                        + getBits(appCertificate.length(), VALUE_SIZE_BITS)
-                        + getValueBits(appCertificate)
-                        + COMPOUND_FORMULA_END_BITS
-                        + DENY
-                        + END_BIT;
-        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
-        byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
-        byteArrayOutputStream.write(getBytes(expectedBits));
-        byte[] expectedRules = byteArrayOutputStream.toByteArray();
-
-        byte[] actualRules =
-                binarySerializer.serialize(
-                        Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
-
-        assertThat(actualRules).isEqualTo(expectedRules);
-    }
-
-    @Test
-    public void testBinaryString_serializeValidAtomicFormula_stringValue() throws Exception {
-        String packageName = "com.test.app";
-        Rule rule =
-                new Rule(
-                        new AtomicFormula.StringAtomicFormula(
-                                AtomicFormula.PACKAGE_NAME,
-                                packageName,
-                                /* isHashedValue= */ false),
-                        Rule.DENY);
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-        String expectedBits =
-                START_BIT
-                        + ATOMIC_FORMULA_START_BITS
-                        + PACKAGE_NAME
-                        + EQ
-                        + IS_NOT_HASHED
-                        + getBits(packageName.length(), VALUE_SIZE_BITS)
-                        + getValueBits(packageName)
-                        + DENY
-                        + END_BIT;
-        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
-        byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
-        byteArrayOutputStream.write(getBytes(expectedBits));
-        byte[] expectedRules = byteArrayOutputStream.toByteArray();
-
-        byte[] actualRules =
-                binarySerializer.serialize(
-                        Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
-
-        assertThat(actualRules).isEqualTo(expectedRules);
-    }
-
-    @Test
-    public void testBinaryString_serializeValidAtomicFormula_hashedValue() throws Exception {
-        String appCertificate = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
-        Rule rule =
-                new Rule(
-                        new AtomicFormula.StringAtomicFormula(
-                                AtomicFormula.APP_CERTIFICATE,
-                                IntegrityUtils.getHexDigest(
-                                        appCertificate.getBytes(StandardCharsets.UTF_8)),
-                                /* isHashedValue= */ true),
-                        Rule.DENY);
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-        String expectedBits =
-                START_BIT
-                        + ATOMIC_FORMULA_START_BITS
-                        + APP_CERTIFICATE
-                        + EQ
-                        + IS_HASHED
-                        + getBits(appCertificate.length(), VALUE_SIZE_BITS)
-                        + getValueBits(appCertificate)
-                        + DENY
-                        + END_BIT;
-        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
-        byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
-        byteArrayOutputStream.write(getBytes(expectedBits));
-        byte[] expectedRules = byteArrayOutputStream.toByteArray();
-
-        byte[] actualRules =
-                binarySerializer.serialize(
-                        Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
-
-        assertThat(actualRules).isEqualTo(expectedRules);
-    }
-
-    @Test
-    public void testBinaryString_serializeValidAtomicFormula_integerValue() throws Exception {
-        long versionCode = 1;
-        Rule rule =
-                new Rule(
-                        new AtomicFormula.LongAtomicFormula(
-                                AtomicFormula.VERSION_CODE, AtomicFormula.EQ, versionCode),
-                        Rule.DENY);
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-        String expectedBits =
-                START_BIT
-                        + ATOMIC_FORMULA_START_BITS
-                        + VERSION_CODE
-                        + EQ
-                        + getBits(versionCode, /* numOfBits= */ 64)
-                        + DENY
-                        + END_BIT;
-        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
-        byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
-        byteArrayOutputStream.write(getBytes(expectedBits));
-        byte[] expectedRules = byteArrayOutputStream.toByteArray();
-
-        byte[] actualRules =
-                binarySerializer.serialize(
-                        Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
-
-        assertThat(actualRules).isEqualTo(expectedRules);
-    }
-
-    @Test
-    public void testBinaryString_serializeValidAtomicFormula_booleanValue() throws Exception {
-        String preInstalled = "1";
-        Rule rule =
-                new Rule(
-                        new AtomicFormula.BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true),
-                        Rule.DENY);
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-        String expectedBits =
-                START_BIT
-                        + ATOMIC_FORMULA_START_BITS
-                        + PRE_INSTALLED
-                        + EQ
-                        + preInstalled
-                        + DENY
-                        + END_BIT;
-        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
-        byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
-        byteArrayOutputStream.write(getBytes(expectedBits));
-        byte[] expectedRules = byteArrayOutputStream.toByteArray();
-
-        byte[] actualRules =
-                binarySerializer.serialize(
-                        Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
-
-        assertThat(actualRules).isEqualTo(expectedRules);
-    }
-
-    @Test
-    public void testBinaryString_serializeInvalidFormulaType() throws Exception {
-        IntegrityFormula invalidFormula = getInvalidFormula();
-        Rule rule = new Rule(invalidFormula, Rule.DENY);
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-
-        assertExpectException(
-                RuleSerializeException.class,
-                /* expectedExceptionMessageRegex= */ "Malformed rule identified.",
-                () ->
-                        binarySerializer.serialize(
-                                Collections.singletonList(rule),
-                                /* formatVersion= */ Optional.empty()));
-    }
-
-    @Test
-    public void testBinaryString_serializeFormatVersion() throws Exception {
-        int formatVersion = 1;
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-        String expectedBits = getBits(formatVersion, FORMAT_VERSION_BITS);
-        byte[] expectedRules = getBytes(expectedBits);
-
-        byte[] actualRules =
-                binarySerializer.serialize(
-                        Collections.emptyList(), /* formatVersion= */ Optional.of(formatVersion));
-
-        assertThat(actualRules).isEqualTo(expectedRules);
-    }
-
-    @Test
-    public void testBinaryString_verifyManyRulesAreIndexedCorrectly() throws Exception {
-        int ruleCount = 225;
-        String packagePrefix = "package.name.";
-        String appCertificatePrefix = "app.cert.";
-        String installerNamePrefix = "installer.";
-
-        // Create the rule set with 225 package name based rules, 225 app certificate indexed rules,
-        // and 225 non-indexed rules..
-        List<Rule> ruleList = new ArrayList();
-        for (int count = 0; count < ruleCount; count++) {
-            ruleList.add(
-                    getRuleWithPackageNameAndSampleInstallerName(
-                            String.format("%s%04d", packagePrefix, count)));
-        }
-        for (int count = 0; count < ruleCount; count++) {
-            ruleList.add(
-                    getRuleWithAppCertificateAndSampleInstallerName(
-                            String.format("%s%04d", appCertificatePrefix, count)));
-        }
-        for (int count = 0; count < ruleCount; count++) {
-            ruleList.add(
-                    getNonIndexedRuleWithInstallerName(
-                            String.format("%s%04d", installerNamePrefix, count)));
-        }
-
-        // Serialize the rules.
-        ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
-        ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-        binarySerializer.serialize(
-                ruleList,
-                /* formatVersion= */ Optional.empty(),
-                ruleOutputStream,
-                indexingOutputStream);
-
-        // Verify the rules file and index files.
-        ByteArrayOutputStream expectedOrderedRuleOutputStream = new ByteArrayOutputStream();
-        ByteArrayOutputStream expectedIndexingOutputStream = new ByteArrayOutputStream();
-
-        expectedOrderedRuleOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
-        int totalBytesWritten = DEFAULT_FORMAT_VERSION_BYTES.length;
-
-        String expectedIndexingBytesForPackageNameIndexed =
-                SERIALIZED_START_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
-        for (int count = 0; count < ruleCount; count++) {
-            String packageName = String.format("%s%04d", packagePrefix, count);
-            if (count > 0 && count % INDEXING_BLOCK_SIZE == 0) {
-                expectedIndexingBytesForPackageNameIndexed +=
-                        IS_NOT_HASHED
-                                + getBits(packageName.length(), VALUE_SIZE_BITS)
-                                + getValueBits(packageName)
-                                + getBits(totalBytesWritten, /* numOfBits= */ 32);
-            }
-
-            byte[] bytesForPackage =
-                    getBytes(
-                            getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(
-                                    packageName));
-            expectedOrderedRuleOutputStream.write(bytesForPackage);
-            totalBytesWritten += bytesForPackage.length;
-        }
-        expectedIndexingBytesForPackageNameIndexed +=
-                SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
-
-        String expectedIndexingBytesForAppCertificateIndexed =
-                SERIALIZED_START_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
-        for (int count = 0; count < ruleCount; count++) {
-            String appCertificate = String.format("%s%04d", appCertificatePrefix, count);
-            if (count > 0 && count % INDEXING_BLOCK_SIZE == 0) {
-                expectedIndexingBytesForAppCertificateIndexed +=
-                        IS_NOT_HASHED
-                                + getBits(appCertificate.length(), VALUE_SIZE_BITS)
-                                + getValueBits(appCertificate)
-                                + getBits(totalBytesWritten, /* numOfBits= */ 32);
-            }
-
-            byte[] bytesForPackage =
-                    getBytes(
-                            getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName(
-                                    appCertificate));
-            expectedOrderedRuleOutputStream.write(bytesForPackage);
-            totalBytesWritten += bytesForPackage.length;
-        }
-        expectedIndexingBytesForAppCertificateIndexed +=
-                SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
-
-        String expectedIndexingBytesForUnindexed =
-                SERIALIZED_START_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
-        for (int count = 0; count < ruleCount; count++) {
-            byte[] bytesForPackage =
-                    getBytes(
-                            getSerializedCompoundRuleWithInstallerNameAndInstallerCert(
-                                    String.format("%s%04d", installerNamePrefix, count)));
-            expectedOrderedRuleOutputStream.write(bytesForPackage);
-            totalBytesWritten += bytesForPackage.length;
-        }
-        expectedIndexingBytesForUnindexed +=
-                SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
-        expectedIndexingOutputStream.write(
-                getBytes(
-                        expectedIndexingBytesForPackageNameIndexed
-                                + expectedIndexingBytesForAppCertificateIndexed
-                                + expectedIndexingBytesForUnindexed));
-
-        assertThat(ruleOutputStream.toByteArray())
-                .isEqualTo(expectedOrderedRuleOutputStream.toByteArray());
-        assertThat(indexingOutputStream.toByteArray())
-                .isEqualTo(expectedIndexingOutputStream.toByteArray());
-    }
-
-    @Test
-    public void testBinaryString_totalRuleSizeLimitReached() {
-        int ruleCount = INDEXED_RULE_SIZE_LIMIT - 1;
-        String packagePrefix = "package.name.";
-        String appCertificatePrefix = "app.cert.";
-        String installerNamePrefix = "installer.";
-
-        // Create the rule set with more rules than the system can handle in total.
-        List<Rule> ruleList = new ArrayList();
-        for (int count = 0; count < ruleCount; count++) {
-            ruleList.add(
-                    getRuleWithPackageNameAndSampleInstallerName(
-                            String.format("%s%04d", packagePrefix, count)));
-        }
-        for (int count = 0; count < ruleCount; count++) {
-            ruleList.add(
-                    getRuleWithAppCertificateAndSampleInstallerName(
-                            String.format("%s%04d", appCertificatePrefix, count)));
-        }
-        for (int count = 0; count < NONINDEXED_RULE_SIZE_LIMIT - 1; count++) {
-            ruleList.add(
-                    getNonIndexedRuleWithInstallerName(
-                            String.format("%s%04d", installerNamePrefix, count)));
-        }
-
-        // Serialize the rules.
-        ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
-        ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-
-        assertExpectException(
-                RuleSerializeException.class,
-                "Too many rules provided",
-                () ->
-                        binarySerializer.serialize(
-                                ruleList,
-                                /* formatVersion= */ Optional.empty(),
-                                ruleOutputStream,
-                                indexingOutputStream));
-    }
-
-    @Test
-    public void testBinaryString_tooManyPackageNameIndexedRules() {
-        String packagePrefix = "package.name.";
-
-        // Create a rule set with too many package name indexed rules.
-        List<Rule> ruleList = new ArrayList();
-        for (int count = 0; count < INDEXED_RULE_SIZE_LIMIT + 1; count++) {
-            ruleList.add(
-                    getRuleWithPackageNameAndSampleInstallerName(
-                            String.format("%s%04d", packagePrefix, count)));
-        }
-
-        // Serialize the rules.
-        ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
-        ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-
-        assertExpectException(
-                RuleSerializeException.class,
-                "Too many rules provided in the indexing group.",
-                () ->
-                        binarySerializer.serialize(
-                                ruleList,
-                                /* formatVersion= */ Optional.empty(),
-                                ruleOutputStream,
-                                indexingOutputStream));
-    }
-
-    @Test
-    public void testBinaryString_tooManyAppCertificateIndexedRules() {
-        String appCertificatePrefix = "app.cert.";
-
-        // Create a rule set with too many app certificate indexed rules.
-        List<Rule> ruleList = new ArrayList();
-        for (int count = 0; count < INDEXED_RULE_SIZE_LIMIT + 1; count++) {
-            ruleList.add(
-                    getRuleWithAppCertificateAndSampleInstallerName(
-                            String.format("%s%04d", appCertificatePrefix, count)));
-        }
-
-        // Serialize the rules.
-        ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
-        ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-
-        assertExpectException(
-                RuleSerializeException.class,
-                "Too many rules provided in the indexing group.",
-                () ->
-                        binarySerializer.serialize(
-                                ruleList,
-                                /* formatVersion= */ Optional.empty(),
-                                ruleOutputStream,
-                                indexingOutputStream));
-    }
-
-    @Test
-    public void testBinaryString_tooManyNonIndexedRules() {
-        String installerNamePrefix = "installer.";
-
-        // Create a rule set with too many unindexed rules.
-        List<Rule> ruleList = new ArrayList();
-        for (int count = 0; count < NONINDEXED_RULE_SIZE_LIMIT + 1; count++) {
-            ruleList.add(
-                    getNonIndexedRuleWithInstallerName(
-                            String.format("%s%04d", installerNamePrefix, count)));
-        }
-
-        // Serialize the rules.
-        ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
-        ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-
-        assertExpectException(
-                RuleSerializeException.class,
-                "Too many rules provided in the indexing group.",
-                () ->
-                        binarySerializer.serialize(
-                                ruleList,
-                                /* formatVersion= */ Optional.empty(),
-                                ruleOutputStream,
-                                indexingOutputStream));
-    }
-
-    private Rule getRuleWithPackageNameAndSampleInstallerName(String packageName) {
-        return new Rule(
-                new CompoundFormula(
-                        CompoundFormula.AND,
-                        Arrays.asList(
-                                new AtomicFormula.StringAtomicFormula(
-                                        AtomicFormula.PACKAGE_NAME,
-                                        packageName,
-                                        /* isHashedValue= */ false),
-                                new AtomicFormula.StringAtomicFormula(
-                                        AtomicFormula.INSTALLER_NAME,
-                                        SAMPLE_INSTALLER_NAME,
-                                        /* isHashedValue= */ false))),
-                Rule.DENY);
-    }
-
-    private String getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(
-            String packageName) {
-        return START_BIT
-                + COMPOUND_FORMULA_START_BITS
-                + AND
-                + ATOMIC_FORMULA_START_BITS
-                + PACKAGE_NAME
-                + EQ
-                + IS_NOT_HASHED
-                + getBits(packageName.length(), VALUE_SIZE_BITS)
-                + getValueBits(packageName)
-                + ATOMIC_FORMULA_START_BITS
-                + INSTALLER_NAME
-                + EQ
-                + IS_NOT_HASHED
-                + getBits(SAMPLE_INSTALLER_NAME.length(), VALUE_SIZE_BITS)
-                + getValueBits(SAMPLE_INSTALLER_NAME)
-                + COMPOUND_FORMULA_END_BITS
-                + DENY
-                + END_BIT;
-    }
-
-    private Rule getRuleWithAppCertificateAndSampleInstallerName(String certificate) {
-        return new Rule(
-                new CompoundFormula(
-                        CompoundFormula.AND,
-                        Arrays.asList(
-                                new AtomicFormula.StringAtomicFormula(
-                                        AtomicFormula.APP_CERTIFICATE,
-                                        certificate,
-                                        /* isHashedValue= */ false),
-                                new AtomicFormula.StringAtomicFormula(
-                                        AtomicFormula.INSTALLER_NAME,
-                                        SAMPLE_INSTALLER_NAME,
-                                        /* isHashedValue= */ false))),
-                Rule.DENY);
-    }
-
-    private String getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName(
-            String appCertificate) {
-        return START_BIT
-                + COMPOUND_FORMULA_START_BITS
-                + AND
-                + ATOMIC_FORMULA_START_BITS
-                + APP_CERTIFICATE
-                + EQ
-                + IS_NOT_HASHED
-                + getBits(appCertificate.length(), VALUE_SIZE_BITS)
-                + getValueBits(appCertificate)
-                + ATOMIC_FORMULA_START_BITS
-                + INSTALLER_NAME
-                + EQ
-                + IS_NOT_HASHED
-                + getBits(SAMPLE_INSTALLER_NAME.length(), VALUE_SIZE_BITS)
-                + getValueBits(SAMPLE_INSTALLER_NAME)
-                + COMPOUND_FORMULA_END_BITS
-                + DENY
-                + END_BIT;
-    }
-
-    private Rule getNonIndexedRuleWithInstallerName(String installerName) {
-        return new Rule(
-                new CompoundFormula(
-                        CompoundFormula.AND,
-                        Arrays.asList(
-                                new AtomicFormula.StringAtomicFormula(
-                                        AtomicFormula.INSTALLER_NAME,
-                                        installerName,
-                                        /* isHashedValue= */ false),
-                                new AtomicFormula.StringAtomicFormula(
-                                        AtomicFormula.INSTALLER_CERTIFICATE,
-                                        SAMPLE_INSTALLER_CERT,
-                                        /* isHashedValue= */ false))),
-                Rule.DENY);
-    }
-
-    private String getSerializedCompoundRuleWithInstallerNameAndInstallerCert(
-            String installerName) {
-        return START_BIT
-                + COMPOUND_FORMULA_START_BITS
-                + AND
-                + ATOMIC_FORMULA_START_BITS
-                + INSTALLER_NAME
-                + EQ
-                + IS_NOT_HASHED
-                + getBits(installerName.length(), VALUE_SIZE_BITS)
-                + getValueBits(installerName)
-                + ATOMIC_FORMULA_START_BITS
-                + INSTALLER_CERTIFICATE
-                + EQ
-                + IS_NOT_HASHED
-                + getBits(SAMPLE_INSTALLER_CERT.length(), VALUE_SIZE_BITS)
-                + getValueBits(SAMPLE_INSTALLER_CERT)
-                + COMPOUND_FORMULA_END_BITS
-                + DENY
-                + END_BIT;
-    }
-
-    private static IntegrityFormula getInvalidFormula() {
-        return new AtomicFormula(0) {
-            @Override
-            public int getTag() {
-                return 0;
-            }
-
-            @Override
-            public boolean matches(AppInstallMetadata appInstallMetadata) {
-                return false;
-            }
-
-            @Override
-            public boolean isAppCertificateFormula() {
-                return false;
-            }
-
-            @Override
-            public boolean isAppCertificateLineageFormula() {
-                return false;
-            }
-
-            @Override
-            public boolean isInstallerFormula() {
-                return false;
-            }
-
-            @Override
-            public int hashCode() {
-                return super.hashCode();
-            }
-
-            @Override
-            public boolean equals(Object obj) {
-                return super.equals(obj);
-            }
-
-            @NonNull
-            @Override
-            protected Object clone() throws CloneNotSupportedException {
-                return super.clone();
-            }
-
-            @Override
-            public String toString() {
-                return super.toString();
-            }
-
-            @Override
-            protected void finalize() throws Throwable {
-                super.finalize();
-            }
-        };
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java
deleted file mode 100644
index 6dccdf5..0000000
--- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java
+++ /dev/null
@@ -1,344 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.serializer;
-
-import static com.android.server.integrity.serializer.RuleIndexingDetails.APP_CERTIFICATE_INDEXED;
-import static com.android.server.integrity.serializer.RuleIndexingDetails.NOT_INDEXED;
-import static com.android.server.integrity.serializer.RuleIndexingDetails.PACKAGE_NAME_INDEXED;
-import static com.android.server.integrity.serializer.RuleIndexingDetailsIdentifier.splitRulesIntoIndexBuckets;
-import static com.android.server.testutils.TestUtils.assertExpectException;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.integrity.AppInstallMetadata;
-import android.content.integrity.AtomicFormula;
-import android.content.integrity.CompoundFormula;
-import android.content.integrity.IntegrityFormula;
-import android.content.integrity.Rule;
-
-import androidx.annotation.NonNull;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-
-/** Unit tests for {@link RuleIndexingDetailsIdentifier}. */
-@RunWith(JUnit4.class)
-public class RuleIndexingDetailsIdentifierTest {
-
-    private static final String SAMPLE_APP_CERTIFICATE = "testcert";
-    private static final String SAMPLE_INSTALLER_NAME = "com.test.installer";
-    private static final String SAMPLE_INSTALLER_CERTIFICATE = "installercert";
-    private static final String SAMPLE_PACKAGE_NAME = "com.test.package";
-
-    private static final AtomicFormula ATOMIC_FORMULA_WITH_PACKAGE_NAME =
-            new AtomicFormula.StringAtomicFormula(
-                    AtomicFormula.PACKAGE_NAME,
-                    SAMPLE_PACKAGE_NAME,
-                    /* isHashedValue= */ false);
-    private static final AtomicFormula ATOMIC_FORMULA_WITH_APP_CERTIFICATE =
-            new AtomicFormula.StringAtomicFormula(
-                    AtomicFormula.APP_CERTIFICATE,
-                    SAMPLE_APP_CERTIFICATE,
-                    /* isHashedValue= */ false);
-    private static final AtomicFormula ATOMIC_FORMULA_WITH_INSTALLER_NAME =
-            new AtomicFormula.StringAtomicFormula(
-                    AtomicFormula.INSTALLER_NAME,
-                    SAMPLE_INSTALLER_NAME,
-                    /* isHashedValue= */ false);
-    private static final AtomicFormula ATOMIC_FORMULA_WITH_INSTALLER_CERTIFICATE =
-            new AtomicFormula.StringAtomicFormula(
-                    AtomicFormula.INSTALLER_CERTIFICATE,
-                    SAMPLE_INSTALLER_CERTIFICATE,
-                    /* isHashedValue= */ false);
-    private static final AtomicFormula ATOMIC_FORMULA_WITH_VERSION_CODE =
-            new AtomicFormula.LongAtomicFormula(AtomicFormula.VERSION_CODE,
-                    AtomicFormula.EQ, 12);
-    private static final AtomicFormula ATOMIC_FORMULA_WITH_ISPREINSTALLED =
-            new AtomicFormula.BooleanAtomicFormula(
-                    AtomicFormula.PRE_INSTALLED, /* booleanValue= */
-                    true);
-
-
-    private static final Rule RULE_WITH_PACKAGE_NAME =
-            new Rule(
-                    new CompoundFormula(
-                            CompoundFormula.AND,
-                            Arrays.asList(
-                                    ATOMIC_FORMULA_WITH_PACKAGE_NAME,
-                                    ATOMIC_FORMULA_WITH_INSTALLER_NAME)),
-                    Rule.DENY);
-    private static final Rule RULE_WITH_APP_CERTIFICATE =
-            new Rule(
-                    new CompoundFormula(
-                            CompoundFormula.AND,
-                            Arrays.asList(
-                                    ATOMIC_FORMULA_WITH_APP_CERTIFICATE,
-                                    ATOMIC_FORMULA_WITH_INSTALLER_NAME)),
-                    Rule.DENY);
-    private static final Rule RULE_WITH_INSTALLER_RESTRICTIONS =
-            new Rule(
-                    new CompoundFormula(
-                            CompoundFormula.AND,
-                            Arrays.asList(
-                                    ATOMIC_FORMULA_WITH_INSTALLER_NAME,
-                                    ATOMIC_FORMULA_WITH_INSTALLER_CERTIFICATE)),
-                    Rule.DENY);
-
-    private static final Rule RULE_WITH_NONSTRING_RESTRICTIONS =
-            new Rule(
-                    new CompoundFormula(
-                            CompoundFormula.AND,
-                            Arrays.asList(
-                                    ATOMIC_FORMULA_WITH_VERSION_CODE,
-                                    ATOMIC_FORMULA_WITH_ISPREINSTALLED)),
-                    Rule.DENY);
-    public static final int INVALID_FORMULA_TAG = -1;
-
-    @Test
-    public void getIndexType_nullRule() {
-        List<Rule> ruleList = null;
-
-        assertExpectException(
-                IllegalArgumentException.class,
-                /* expectedExceptionMessageRegex= */
-                "Index buckets cannot be created for null rule list.",
-                () -> splitRulesIntoIndexBuckets(ruleList));
-    }
-
-    @Test
-    public void getIndexType_invalidFormula() {
-        List<Rule> ruleList = new ArrayList();
-        ruleList.add(new Rule(getInvalidFormula(), Rule.DENY));
-
-        assertExpectException(
-                IllegalArgumentException.class,
-                /* expectedExceptionMessageRegex= */ "Malformed rule identified.",
-                () -> splitRulesIntoIndexBuckets(ruleList));
-    }
-
-    @Test
-    public void getIndexType_ruleContainingPackageNameFormula() {
-        List<Rule> ruleList = new ArrayList();
-        ruleList.add(RULE_WITH_PACKAGE_NAME);
-
-        Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
-
-        // Verify the resulting map content.
-        assertThat(result.keySet())
-                .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
-        assertThat(result.get(NOT_INDEXED)).isEmpty();
-        assertThat(result.get(APP_CERTIFICATE_INDEXED)).isEmpty();
-        assertThat(result.get(PACKAGE_NAME_INDEXED).keySet()).containsExactly(SAMPLE_PACKAGE_NAME);
-        assertThat(result.get(PACKAGE_NAME_INDEXED).get(SAMPLE_PACKAGE_NAME))
-                .containsExactly(RULE_WITH_PACKAGE_NAME);
-    }
-
-    @Test
-    public void getIndexType_ruleContainingAppCertificateFormula() {
-        List<Rule> ruleList = new ArrayList();
-        ruleList.add(RULE_WITH_APP_CERTIFICATE);
-
-        Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
-
-        assertThat(result.keySet())
-                .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
-        assertThat(result.get(NOT_INDEXED)).isEmpty();
-        assertThat(result.get(PACKAGE_NAME_INDEXED)).isEmpty();
-        assertThat(result.get(APP_CERTIFICATE_INDEXED).keySet())
-                .containsExactly(SAMPLE_APP_CERTIFICATE);
-        assertThat(result.get(APP_CERTIFICATE_INDEXED).get(SAMPLE_APP_CERTIFICATE))
-                .containsExactly(RULE_WITH_APP_CERTIFICATE);
-    }
-
-    @Test
-    public void getIndexType_ruleWithUnindexedCompoundFormula() {
-        List<Rule> ruleList = new ArrayList();
-        ruleList.add(RULE_WITH_INSTALLER_RESTRICTIONS);
-
-        Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
-
-        assertThat(result.keySet())
-                .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
-        assertThat(result.get(PACKAGE_NAME_INDEXED)).isEmpty();
-        assertThat(result.get(APP_CERTIFICATE_INDEXED)).isEmpty();
-        assertThat(result.get(NOT_INDEXED).get("N/A"))
-                .containsExactly(RULE_WITH_INSTALLER_RESTRICTIONS);
-    }
-
-    @Test
-    public void getIndexType_ruleContainingCompoundFormulaWithIntAndBoolean() {
-        List<Rule> ruleList = new ArrayList();
-        ruleList.add(RULE_WITH_NONSTRING_RESTRICTIONS);
-
-        Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
-
-        assertThat(result.keySet())
-                .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
-        assertThat(result.get(PACKAGE_NAME_INDEXED)).isEmpty();
-        assertThat(result.get(APP_CERTIFICATE_INDEXED)).isEmpty();
-        assertThat(result.get(NOT_INDEXED).get("N/A"))
-                .containsExactly(RULE_WITH_NONSTRING_RESTRICTIONS);
-    }
-
-    @Test
-    public void getIndexType_negatedRuleContainingPackageNameFormula() {
-        Rule negatedRule =
-                new Rule(
-                        new CompoundFormula(
-                                CompoundFormula.NOT,
-                                Arrays.asList(
-                                        new CompoundFormula(
-                                                CompoundFormula.AND,
-                                                Arrays.asList(
-                                                        ATOMIC_FORMULA_WITH_PACKAGE_NAME,
-                                                        ATOMIC_FORMULA_WITH_APP_CERTIFICATE)))),
-                        Rule.DENY);
-        List<Rule> ruleList = new ArrayList();
-        ruleList.add(negatedRule);
-
-        Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
-
-        assertThat(result.keySet())
-                .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
-        assertThat(result.get(PACKAGE_NAME_INDEXED)).isEmpty();
-        assertThat(result.get(APP_CERTIFICATE_INDEXED)).isEmpty();
-        assertThat(result.get(NOT_INDEXED).get("N/A")).containsExactly(negatedRule);
-    }
-
-    @Test
-    public void getIndexType_allRulesTogetherSplitCorrectly() {
-        Rule packageNameRuleA = getRuleWithPackageName("aaa");
-        Rule packageNameRuleB = getRuleWithPackageName("bbb");
-        Rule packageNameRuleC = getRuleWithPackageName("ccc");
-        Rule certificateRule1 = getRuleWithAppCertificate("cert1");
-        Rule certificateRule2 = getRuleWithAppCertificate("cert2");
-        Rule certificateRule3 = getRuleWithAppCertificate("cert3");
-
-        List<Rule> ruleList = new ArrayList();
-        ruleList.add(packageNameRuleB);
-        ruleList.add(packageNameRuleC);
-        ruleList.add(packageNameRuleA);
-        ruleList.add(certificateRule3);
-        ruleList.add(certificateRule2);
-        ruleList.add(certificateRule1);
-        ruleList.add(RULE_WITH_INSTALLER_RESTRICTIONS);
-        ruleList.add(RULE_WITH_NONSTRING_RESTRICTIONS);
-
-        Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
-
-        assertThat(result.keySet())
-                .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
-
-        // We check asserts this way to ensure ordering based on package name.
-        assertThat(result.get(PACKAGE_NAME_INDEXED).keySet()).containsExactly("aaa", "bbb", "ccc");
-
-        // We check asserts this way to ensure ordering based on app certificate.
-        assertThat(result.get(APP_CERTIFICATE_INDEXED).keySet()).containsExactly("cert1", "cert2",
-                "cert3");
-
-        assertThat(result.get(NOT_INDEXED).get("N/A"))
-                .containsExactly(RULE_WITH_INSTALLER_RESTRICTIONS,
-                        RULE_WITH_NONSTRING_RESTRICTIONS);
-    }
-
-    private Rule getRuleWithPackageName(String packageName) {
-        return new Rule(
-                new CompoundFormula(
-                        CompoundFormula.AND,
-                        Arrays.asList(
-                                new AtomicFormula.StringAtomicFormula(
-                                        AtomicFormula.PACKAGE_NAME,
-                                        packageName,
-                                        /* isHashedValue= */ false),
-                                ATOMIC_FORMULA_WITH_INSTALLER_NAME)),
-                Rule.DENY);
-    }
-
-    private Rule getRuleWithAppCertificate(String certificate) {
-        return new Rule(
-                new CompoundFormula(
-                        CompoundFormula.AND,
-                        Arrays.asList(
-                                new AtomicFormula.StringAtomicFormula(
-                                        AtomicFormula.APP_CERTIFICATE,
-                                        certificate,
-                                        /* isHashedValue= */ false),
-                                ATOMIC_FORMULA_WITH_INSTALLER_NAME)),
-                Rule.DENY);
-    }
-
-    private IntegrityFormula getInvalidFormula() {
-        return new AtomicFormula(0) {
-            @Override
-            public int getTag() {
-                return INVALID_FORMULA_TAG;
-            }
-
-            @Override
-            public boolean matches(AppInstallMetadata appInstallMetadata) {
-                return false;
-            }
-
-            @Override
-            public boolean isAppCertificateFormula() {
-                return false;
-            }
-
-            @Override
-            public boolean isAppCertificateLineageFormula() {
-                return false;
-            }
-
-            @Override
-            public boolean isInstallerFormula() {
-                return false;
-            }
-
-            @Override
-            public int hashCode() {
-                return super.hashCode();
-            }
-
-            @Override
-            public boolean equals(Object obj) {
-                return super.equals(obj);
-            }
-
-            @NonNull
-            @Override
-            protected Object clone() throws CloneNotSupportedException {
-                return super.clone();
-            }
-
-            @Override
-            public String toString() {
-                return super.toString();
-            }
-
-            @Override
-            protected void finalize() throws Throwable {
-                super.finalize();
-            }
-        };
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
index 4a43c2e..9d7b6a1 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
@@ -977,11 +977,19 @@
         assertEquals(USER_ID_1, UserHandle.getUserId(uid));
 
         mPackageListObserver.onPackageRemoved(PACKAGE_NAME_1, uid);
+        // Test that notifyAllCallbacks doesn't trigger for non-background-installed package
+        mPackageListObserver.onPackageRemoved(PACKAGE_NAME_3, uid);
         mTestLooper.dispatchAll();
 
         assertEquals(1, packages.size());
         assertFalse(packages.contains(USER_ID_1, PACKAGE_NAME_1));
         assertTrue(packages.contains(USER_ID_2, PACKAGE_NAME_2));
+
+        verify(mCallbackHelper)
+                .notifyAllCallbacks(
+                        USER_ID_1,
+                        PACKAGE_NAME_1,
+                        BackgroundInstallControlService.INSTALL_EVENT_TYPE_UNINSTALL);
     }
 
     @Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index b5724b5c..48bc9d7 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -21,10 +21,8 @@
 import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
 import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
 import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE;
-import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
 import static android.service.notification.NotificationListenerService.META_DATA_DEFAULT_AUTOBIND;
 
-import static com.android.server.notification.Flags.FLAG_NOTIFICATION_NLS_REBIND;
 import static com.android.server.notification.ManagedServices.APPROVAL_BY_COMPONENT;
 import static com.android.server.notification.ManagedServices.APPROVAL_BY_PACKAGE;
 import static com.android.server.notification.NotificationManagerService.privateSpaceFlagsEnabled;
@@ -65,14 +63,11 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.IInterface;
-import android.os.Looper;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
-import android.testing.TestableLooper;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -87,9 +82,7 @@
 
 import com.google.android.collect.Lists;
 
-import org.junit.After;
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
@@ -110,10 +103,7 @@
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 
-
 public class ManagedServicesTest extends UiServiceTestCase {
-    @Rule
-    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
 
     @Mock
     private IPackageManager mIpm;
@@ -125,7 +115,6 @@
     private ManagedServices.UserProfiles mUserProfiles;
     @Mock private DevicePolicyManager mDpm;
     Object mLock = new Object();
-    private TestableLooper mTestableLooper;
 
     UserInfo mZero = new UserInfo(0, "zero", 0);
     UserInfo mTen = new UserInfo(10, "ten", 0);
@@ -153,7 +142,6 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mTestableLooper = new TestableLooper(Looper.getMainLooper());
 
         mContext.setMockPackageManager(mPm);
         mContext.addMockSystemService(Context.USER_SERVICE, mUm);
@@ -211,11 +199,6 @@
                 mIpm, APPROVAL_BY_COMPONENT);
     }
 
-    @After
-    public void tearDown() throws Exception {
-        mTestableLooper.destroy();
-    }
-
     @Test
     public void testBackupAndRestore_migration() throws Exception {
         for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
@@ -905,7 +888,7 @@
             return true;
         });
 
-        mockServiceInfoWithMetaData(List.of(cn), service, pm, new ArrayMap<>());
+        mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
         service.addApprovedList("a", 0, true);
 
         service.reregisterService(cn, 0);
@@ -936,7 +919,7 @@
             return true;
         });
 
-        mockServiceInfoWithMetaData(List.of(cn), service, pm, new ArrayMap<>());
+        mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
         service.addApprovedList("a", 0, false);
 
         service.reregisterService(cn, 0);
@@ -967,7 +950,7 @@
             return true;
         });
 
-        mockServiceInfoWithMetaData(List.of(cn), service, pm, new ArrayMap<>());
+        mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
         service.addApprovedList("a/a", 0, true);
 
         service.reregisterService(cn, 0);
@@ -998,7 +981,7 @@
             return true;
         });
 
-        mockServiceInfoWithMetaData(List.of(cn), service, pm, new ArrayMap<>());
+        mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
         service.addApprovedList("a/a", 0, false);
 
         service.reregisterService(cn, 0);
@@ -1070,78 +1053,6 @@
     }
 
     @Test
-    @EnableFlags(FLAG_NOTIFICATION_NLS_REBIND)
-    public void registerService_bindingDied_rebindIsClearedOnUserSwitch() throws Exception {
-        Context context = mock(Context.class);
-        PackageManager pm = mock(PackageManager.class);
-        ApplicationInfo ai = new ApplicationInfo();
-        ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
-
-        when(context.getPackageName()).thenReturn(mPkg);
-        when(context.getUserId()).thenReturn(mUser.getIdentifier());
-        when(context.getPackageManager()).thenReturn(pm);
-        when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai);
-
-        ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles, mIpm,
-                APPROVAL_BY_PACKAGE);
-        service = spy(service);
-        ComponentName cn = ComponentName.unflattenFromString("a/a");
-
-        // Trigger onBindingDied for component when registering
-        //  => will schedule a rebind in 10 seconds
-        when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> {
-            Object[] args = invocation.getArguments();
-            ServiceConnection sc = (ServiceConnection) args[1];
-            sc.onBindingDied(cn);
-            return true;
-        });
-        service.registerService(cn, 0);
-        assertThat(service.isBound(cn, 0)).isFalse();
-
-        // Switch to user 10
-        service.onUserSwitched(10);
-
-        // Check that the scheduled rebind for user 0 was cleared
-        mTestableLooper.moveTimeForward(ManagedServices.ON_BINDING_DIED_REBIND_DELAY_MS);
-        mTestableLooper.processAllMessages();
-        verify(service, never()).reregisterService(any(), anyInt());
-    }
-
-    @Test
-    public void registerService_bindingDied_rebindIsExecutedAfterTimeout() throws Exception {
-        Context context = mock(Context.class);
-        PackageManager pm = mock(PackageManager.class);
-        ApplicationInfo ai = new ApplicationInfo();
-        ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
-
-        when(context.getPackageName()).thenReturn(mPkg);
-        when(context.getUserId()).thenReturn(mUser.getIdentifier());
-        when(context.getPackageManager()).thenReturn(pm);
-        when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai);
-
-        ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles, mIpm,
-                APPROVAL_BY_PACKAGE);
-        service = spy(service);
-        ComponentName cn = ComponentName.unflattenFromString("a/a");
-
-        // Trigger onBindingDied for component when registering
-        //  => will schedule a rebind in 10 seconds
-        when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> {
-            Object[] args = invocation.getArguments();
-            ServiceConnection sc = (ServiceConnection) args[1];
-            sc.onBindingDied(cn);
-            return true;
-        });
-        service.registerService(cn, 0);
-        assertThat(service.isBound(cn, 0)).isFalse();
-
-        // Check that the scheduled rebind is run
-        mTestableLooper.moveTimeForward(ManagedServices.ON_BINDING_DIED_REBIND_DELAY_MS);
-        mTestableLooper.processAllMessages();
-        verify(service, times(1)).reregisterService(eq(cn), eq(0));
-    }
-
-    @Test
     public void testPackageUninstall_packageNoLongerInApprovedList() throws Exception {
         for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
             ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
@@ -1300,65 +1211,6 @@
     }
 
     @Test
-    @EnableFlags(FLAG_NOTIFICATION_NLS_REBIND)
-    public void testUpgradeAppNoIntentFilterNoRebind() throws Exception {
-        Context context = spy(getContext());
-        doReturn(true).when(context).bindServiceAsUser(any(), any(), anyInt(), any());
-
-        ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles,
-                mIpm, APPROVAL_BY_COMPONENT);
-
-        List<String> packages = new ArrayList<>();
-        packages.add("package");
-        addExpectedServices(service, packages, 0);
-
-        final ComponentName unapprovedComponent = ComponentName.unflattenFromString("package/C1");
-        final ComponentName approvedComponent = ComponentName.unflattenFromString("package/C2");
-
-        // Both components are approved initially
-        mExpectedPrimaryComponentNames.clear();
-        mExpectedPrimaryPackages.clear();
-        mExpectedPrimaryComponentNames.put(0, "package/C1:package/C2");
-        mExpectedSecondaryComponentNames.clear();
-        mExpectedSecondaryPackages.clear();
-
-        loadXml(service);
-
-        //Component package/C1 loses serviceInterface intent filter
-        ManagedServices.Config config = service.getConfig();
-        when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt()))
-                .thenAnswer(new Answer<List<ResolveInfo>>() {
-                    @Override
-                    public List<ResolveInfo> answer(InvocationOnMock invocationOnMock)
-                            throws Throwable {
-                        Object[] args = invocationOnMock.getArguments();
-                        Intent invocationIntent = (Intent) args[0];
-                        if (invocationIntent != null) {
-                            if (invocationIntent.getAction().equals(config.serviceInterface)
-                                    && packages.contains(invocationIntent.getPackage())) {
-                                List<ResolveInfo> dummyServices = new ArrayList<>();
-                                ResolveInfo resolveInfo = new ResolveInfo();
-                                ServiceInfo serviceInfo = new ServiceInfo();
-                                serviceInfo.packageName = invocationIntent.getPackage();
-                                serviceInfo.name = approvedComponent.getClassName();
-                                serviceInfo.permission = service.getConfig().bindPermission;
-                                resolveInfo.serviceInfo = serviceInfo;
-                                dummyServices.add(resolveInfo);
-                                return dummyServices;
-                            }
-                        }
-                        return new ArrayList<>();
-                    }
-                });
-
-        // Trigger package update
-        service.onPackagesChanged(false, new String[]{"package"}, new int[]{0});
-
-        assertFalse(service.isComponentEnabledForCurrentProfiles(unapprovedComponent));
-        assertTrue(service.isComponentEnabledForCurrentProfiles(approvedComponent));
-    }
-
-    @Test
     public void testSetPackageOrComponentEnabled() throws Exception {
         for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
             ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
@@ -1371,21 +1223,6 @@
                             "user10package1/K", "user10.3/Component", "user10package2/L",
                             "user10.4/Component"}));
 
-            // mock permissions for services
-            PackageManager pm = mock(PackageManager.class);
-            when(getContext().getPackageManager()).thenReturn(pm);
-            List<ComponentName> enabledComponents = List.of(
-                    ComponentName.unflattenFromString("package/Comp"),
-                    ComponentName.unflattenFromString("package/C2"),
-                    ComponentName.unflattenFromString("again/M4"),
-                    ComponentName.unflattenFromString("user10package/B"),
-                    ComponentName.unflattenFromString("user10/Component"),
-                    ComponentName.unflattenFromString("user10package1/K"),
-                    ComponentName.unflattenFromString("user10.3/Component"),
-                    ComponentName.unflattenFromString("user10package2/L"),
-                    ComponentName.unflattenFromString("user10.4/Component"));
-            mockServiceInfoWithMetaData(enabledComponents, service, pm, new ArrayMap<>());
-
             for (int userId : expectedEnabled.keySet()) {
                 ArrayList<String> expectedForUser = expectedEnabled.get(userId);
                 for (int i = 0; i < expectedForUser.size(); i++) {
@@ -1447,90 +1284,6 @@
     }
 
     @Test
-    @EnableFlags(FLAG_NOTIFICATION_NLS_REBIND)
-    public void testSetPackageOrComponentEnabled_pkgInstalledAfterEnabling() throws Exception {
-        ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
-                mIpm, APPROVAL_BY_COMPONENT);
-
-        final int userId = 0;
-        final String validComponent = "again/M4";
-        ArrayList<String> expectedEnabled = Lists.newArrayList("package/Comp", "package/C2",
-                validComponent);
-
-        PackageManager pm = mock(PackageManager.class);
-        when(getContext().getPackageManager()).thenReturn(pm);
-        service = spy(service);
-
-        // Component again/M4 is a valid service and the package is available
-        doReturn(true).when(service)
-                .isValidService(ComponentName.unflattenFromString(validComponent), userId);
-        when(pm.isPackageAvailable("again")).thenReturn(true);
-
-        // "package" is not available and its services are not valid
-        doReturn(false).when(service)
-                .isValidService(ComponentName.unflattenFromString("package/Comp"), userId);
-        doReturn(false).when(service)
-                .isValidService(ComponentName.unflattenFromString("package/C2"), userId);
-        when(pm.isPackageAvailable("package")).thenReturn(false);
-
-        // Enable all components
-        for (String component: expectedEnabled) {
-            service.setPackageOrComponentEnabled(component, userId, true, true);
-        }
-
-        // Verify everything added is approved
-        for (String component: expectedEnabled) {
-            assertTrue("Not allowed: user: " + userId + " entry: " + component
-                    + " for approval level " + APPROVAL_BY_COMPONENT,
-                    service.isPackageOrComponentAllowed(component, userId));
-        }
-
-        // Add missing package "package"
-        service.onPackagesChanged(false, new String[]{"package"}, new int[]{0});
-
-        // Check that component of "package" are not enabled
-        assertFalse(service.isComponentEnabledForCurrentProfiles(
-                ComponentName.unflattenFromString("package/Comp")));
-        assertFalse(service.isPackageOrComponentAllowed("package/Comp", userId));
-
-        assertFalse(service.isComponentEnabledForCurrentProfiles(
-                ComponentName.unflattenFromString("package/C2")));
-        assertFalse(service.isPackageOrComponentAllowed("package/C2", userId));
-
-        // Check that the valid components are still enabled
-        assertTrue(service.isComponentEnabledForCurrentProfiles(
-                ComponentName.unflattenFromString(validComponent)));
-        assertTrue(service.isPackageOrComponentAllowed(validComponent, userId));
-    }
-
-    @Test
-    @EnableFlags(FLAG_NOTIFICATION_NLS_REBIND)
-    public void testSetPackageOrComponentEnabled_invalidComponent() throws Exception {
-        ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
-                mIpm, APPROVAL_BY_COMPONENT);
-
-        final int userId = 0;
-        final String invalidComponent = "package/Comp";
-
-        PackageManager pm = mock(PackageManager.class);
-        when(getContext().getPackageManager()).thenReturn(pm);
-        service = spy(service);
-
-        // Component is an invalid service and the package is available
-        doReturn(false).when(service)
-                .isValidService(ComponentName.unflattenFromString(invalidComponent), userId);
-        when(pm.isPackageAvailable("package")).thenReturn(true);
-        service.setPackageOrComponentEnabled(invalidComponent, userId, true, true);
-
-        // Verify that the component was not enabled
-        assertFalse("Not allowed: user: " + userId + " entry: " + invalidComponent
-                    + " for approval level " + APPROVAL_BY_COMPONENT,
-                service.isPackageOrComponentAllowed(invalidComponent, userId));
-        assertFalse(service.isComponentEnabledForCurrentProfiles(
-                ComponentName.unflattenFromString(invalidComponent)));
-    }
-
-    @Test
     public void testGetAllowedPackages_byUser() throws Exception {
         for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
             ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
@@ -2191,7 +1944,7 @@
         metaDataAutobindAllow.putBoolean(META_DATA_DEFAULT_AUTOBIND, true);
         metaDatas.put(cn_allowed, metaDataAutobindAllow);
 
-        mockServiceInfoWithMetaData(componentNames, service, pm, metaDatas);
+        mockServiceInfoWithMetaData(componentNames, service, metaDatas);
 
         service.addApprovedList(cn_allowed.flattenToString(), 0, true);
         service.addApprovedList(cn_disallowed.flattenToString(), 0, true);
@@ -2236,7 +1989,7 @@
         metaDataAutobindDisallow.putBoolean(META_DATA_DEFAULT_AUTOBIND, false);
         metaDatas.put(cn_disallowed, metaDataAutobindDisallow);
 
-        mockServiceInfoWithMetaData(componentNames, service, pm, metaDatas);
+        mockServiceInfoWithMetaData(componentNames, service, metaDatas);
 
         service.addApprovedList(cn_disallowed.flattenToString(), 0, true);
 
@@ -2275,7 +2028,7 @@
         metaDataAutobindDisallow.putBoolean(META_DATA_DEFAULT_AUTOBIND, false);
         metaDatas.put(cn_disallowed, metaDataAutobindDisallow);
 
-        mockServiceInfoWithMetaData(componentNames, service, pm, metaDatas);
+        mockServiceInfoWithMetaData(componentNames, service, metaDatas);
 
         service.addApprovedList(cn_disallowed.flattenToString(), 0, true);
 
@@ -2346,8 +2099,8 @@
     }
 
     private void mockServiceInfoWithMetaData(List<ComponentName> componentNames,
-            ManagedServices service, PackageManager packageManager,
-            ArrayMap<ComponentName, Bundle> metaDatas) throws RemoteException {
+            ManagedServices service, ArrayMap<ComponentName, Bundle> metaDatas)
+            throws RemoteException {
         when(mIpm.getServiceInfo(any(), anyLong(), anyInt())).thenAnswer(
                 (Answer<ServiceInfo>) invocation -> {
                     ComponentName invocationCn = invocation.getArgument(0);
@@ -2362,39 +2115,6 @@
                     return null;
                 }
         );
-
-        // add components to queryIntentServicesAsUser response
-        final List<String> packages = new ArrayList<>();
-        for (ComponentName cn: componentNames) {
-            packages.add(cn.getPackageName());
-        }
-        ManagedServices.Config config = service.getConfig();
-        when(packageManager.queryIntentServicesAsUser(any(), anyInt(), anyInt())).
-                thenAnswer(new Answer<List<ResolveInfo>>() {
-                @Override
-                public List<ResolveInfo> answer(InvocationOnMock invocationOnMock)
-                    throws Throwable {
-                    Object[] args = invocationOnMock.getArguments();
-                    Intent invocationIntent = (Intent) args[0];
-                    if (invocationIntent != null) {
-                        if (invocationIntent.getAction().equals(config.serviceInterface)
-                            && packages.contains(invocationIntent.getPackage())) {
-                            List<ResolveInfo> dummyServices = new ArrayList<>();
-                            for (ComponentName cn: componentNames) {
-                                ResolveInfo resolveInfo = new ResolveInfo();
-                                ServiceInfo serviceInfo = new ServiceInfo();
-                                serviceInfo.packageName = invocationIntent.getPackage();
-                                serviceInfo.name = cn.getClassName();
-                                serviceInfo.permission = service.getConfig().bindPermission;
-                                resolveInfo.serviceInfo = serviceInfo;
-                                dummyServices.add(resolveInfo);
-                            }
-                            return dummyServices;
-                        }
-                    }
-                    return new ArrayList<>();
-                }
-            });
     }
 
     private void resetComponentsAndPackages() {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
index 2c645e0..0f7de7d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
@@ -28,7 +28,6 @@
 import static junit.framework.Assert.assertTrue;
 
 import static org.junit.Assert.assertNull;
-
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Matchers.any;
@@ -198,8 +197,6 @@
     public void testWriteXml_userTurnedOffNAS() throws Exception {
         int userId = ActivityManager.getCurrentUser();
 
-        doReturn(true).when(mAssistants).isValidService(eq(mCn), eq(userId));
-
         mAssistants.loadDefaultsFromConfig(true);
 
         mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true,
@@ -435,10 +432,6 @@
     public void testSetPackageOrComponentEnabled_onlyOnePackage() throws Exception {
         ComponentName component1 = ComponentName.unflattenFromString("package/Component1");
         ComponentName component2 = ComponentName.unflattenFromString("package/Component2");
-
-        doReturn(true).when(mAssistants).isValidService(eq(component1), eq(mZero.id));
-        doReturn(true).when(mAssistants).isValidService(eq(component2), eq(mZero.id));
-
         mAssistants.setPackageOrComponentEnabled(component1.flattenToString(), mZero.id, true,
                 true, true);
         verify(mNm, never()).setNotificationAssistantAccessGrantedForUserInternal(
@@ -584,7 +577,6 @@
     public void testSetAdjustmentTypeSupportedState() throws Exception {
         int userId = ActivityManager.getCurrentUser();
 
-        doReturn(true).when(mAssistants).isValidService(eq(mCn), eq(userId));
         mAssistants.loadDefaultsFromConfig(true);
         mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true,
                 true, true);
@@ -608,7 +600,6 @@
     public void testSetAdjustmentTypeSupportedState_readWriteXml_entries() throws Exception {
         int userId = ActivityManager.getCurrentUser();
 
-        doReturn(true).when(mAssistants).isValidService(eq(mCn), eq(userId));
         mAssistants.loadDefaultsFromConfig(true);
         mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true,
                 true, true);
@@ -632,7 +623,6 @@
     public void testSetAdjustmentTypeSupportedState_readWriteXml_empty() throws Exception {
         int userId = ActivityManager.getCurrentUser();
 
-        doReturn(true).when(mAssistants).isValidService(eq(mCn), eq(userId));
         mAssistants.loadDefaultsFromConfig(true);
         mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true,
                 true, true);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index cbfdc5f..07fa70e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -43,8 +43,8 @@
 import static android.app.Notification.FLAG_USER_INITIATED_JOB;
 import static android.app.Notification.GROUP_ALERT_CHILDREN;
 import static android.app.Notification.VISIBILITY_PRIVATE;
-import static android.app.NotificationChannel.NEWS_ID;
 import static android.app.NotificationChannel.DEFAULT_CHANNEL_ID;
+import static android.app.NotificationChannel.NEWS_ID;
 import static android.app.NotificationChannel.PROMOTIONS_ID;
 import static android.app.NotificationChannel.RECS_ID;
 import static android.app.NotificationChannel.SOCIAL_MEDIA_ID;
@@ -78,7 +78,6 @@
 import static android.app.StatusBarManager.ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED;
 import static android.app.StatusBarManager.EXTRA_KM_PRIVATE_NOTIFS_ALLOWED;
 import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_CONFIG;
-import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_RULES;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
 import static android.content.pm.PackageManager.FEATURE_TELECOM;
 import static android.content.pm.PackageManager.FEATURE_WATCH;
@@ -336,12 +335,12 @@
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
 
-import com.google.android.collect.Lists;
-import com.google.common.collect.ImmutableList;
-
 import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
 import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
 
+import com.google.android.collect.Lists;
+import com.google.common.collect.ImmutableList;
+
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
@@ -366,7 +365,6 @@
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
-import java.io.OutputStream;
 import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -7480,6 +7478,63 @@
     }
 
     @Test
+    @EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION)
+    public void testClassificationChannelAdjustmentsLogged() throws Exception {
+        NotificationManagerService.WorkerHandler handler = mock(
+                NotificationManagerService.WorkerHandler.class);
+        mService.setHandler(handler);
+        when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
+        when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true);
+
+        // Set up notifications that will be adjusted
+        final NotificationRecord r1 = spy(generateNotificationRecord(
+                mTestNotificationChannel, 1, null, true));
+        when(r1.getLifespanMs(anyLong())).thenReturn(234);
+
+        r1.getSbn().setInstanceId(mNotificationInstanceIdSequence.newInstanceId());
+        // Enqueues the notification to be posted, so hasPosted will be false.
+        mService.addEnqueuedNotification(r1);
+
+        // Test an adjustment for an enqueued notification
+        Bundle signals = new Bundle();
+        signals.putInt(Adjustment.KEY_TYPE, Adjustment.TYPE_NEWS);
+        Adjustment adjustment1 = new Adjustment(
+                r1.getSbn().getPackageName(), r1.getKey(), signals, "",
+                r1.getUser().getIdentifier());
+        mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment1);
+        assertTrue(mService.checkLastClassificationChannelLog(false /*hasPosted*/,
+                true /*isAlerting*/, 3 /*TYPE_NEWS*/, 234));
+
+        // Set up notifications that will be adjusted
+        // This notification starts on a low importance channel, so isAlerting is false.
+        NotificationChannel mLowImportanceNotificationChannel = new NotificationChannel(
+                TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_LOW);
+        final NotificationRecord r2 = spy(generateNotificationRecord(
+                mLowImportanceNotificationChannel, 1, null, true));
+        when(r2.getLifespanMs(anyLong())).thenReturn(345);
+
+        r2.getSbn().setInstanceId(mNotificationInstanceIdSequence.newInstanceId());
+        // Adds the notification as already posted, so hasPosted will be true.
+        mService.addNotification(r2);
+        // The signal is removed when used so it has to be readded.
+        signals.putInt(Adjustment.KEY_TYPE, Adjustment.TYPE_NEWS);
+        Adjustment adjustment2 = new Adjustment(
+                r2.getSbn().getPackageName(), r2.getKey(), signals, "",
+                r2.getUser().getIdentifier());
+        mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment2);
+        assertTrue(mService.checkLastClassificationChannelLog(true /*hasPosted*/,
+                false /*isAlerting*/, 3 /*TYPE_NEWS*/, 345)); // currently failing
+
+        signals.putInt(Adjustment.KEY_TYPE, Adjustment.TYPE_PROMOTION);
+        Adjustment adjustment3 = new Adjustment(
+                r2.getSbn().getPackageName(), r2.getKey(), signals, "",
+                r2.getUser().getIdentifier());
+        mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment3);
+        assertTrue(mService.checkLastClassificationChannelLog(true /*hasPosted*/,
+                false /*isAlerting*/, 1 /*TYPE_PROMOTION*/, 345));
+    }
+
+    @Test
     public void testAdjustmentToImportanceNone_cancelsNotification() throws Exception {
         NotificationManagerService.WorkerHandler handler = mock(
                 NotificationManagerService.WorkerHandler.class);
@@ -14365,9 +14420,10 @@
                 r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isTrue();
     }
 
-    private NotificationRecord createBigPictureRecord(boolean isBigPictureStyle, boolean hasImage,
-                                                      boolean isImageBitmap, boolean isExpired) {
-        Notification.Builder builder = new Notification.Builder(mContext);
+    private Notification createBigPictureNotification(boolean isBigPictureStyle, boolean hasImage,
+            boolean isImageBitmap) {
+        Notification.Builder builder = new Notification.Builder(mContext)
+                .setSmallIcon(android.R.drawable.sym_def_app_icon);
         Notification.BigPictureStyle style = new Notification.BigPictureStyle();
 
         if (isBigPictureStyle && hasImage) {
@@ -14383,12 +14439,18 @@
 
         Notification notification = builder.setChannelId(TEST_CHANNEL_ID).build();
 
+        return notification;
+    }
+
+    private NotificationRecord createBigPictureRecord(boolean isBigPictureStyle, boolean hasImage,
+            boolean isImageBitmap, boolean isExpired) {
         long timePostedMs = System.currentTimeMillis();
         if (isExpired) {
             timePostedMs -= BITMAP_DURATION.toMillis();
         }
         StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, "tag", mUid, 0,
-                notification, UserHandle.getUserHandleForUid(mUid), null, timePostedMs);
+                createBigPictureNotification(isBigPictureStyle, hasImage, isImageBitmap),
+                UserHandle.getUserHandleForUid(mUid), null, timePostedMs);
 
         return new NotificationRecord(mContext, sbn, mTestNotificationChannel);
     }
@@ -14400,6 +14462,33 @@
     }
 
     @Test
+    public void testRemoveBitmaps_canRemoveRevokedDelegate() throws Exception {
+        Notification n = createBigPictureNotification(true, true, true);
+        long timePostedMs = System.currentTimeMillis();
+        timePostedMs -= BITMAP_DURATION.toMillis();
+
+        when(mPermissionHelper.hasPermission(UID_O)).thenReturn(true);
+        when(mPackageManagerInternal.isSameApp(PKG_O, UID_O, UserHandle.getUserId(UID_O)))
+                .thenReturn(true);
+        mService.mPreferencesHelper.createNotificationChannel(PKG_O, UID_O,
+                mTestNotificationChannel, true /* fromTargetApp */, false, UID_O,
+                false);
+        mBinderService.createNotificationChannels(PKG_O, new ParceledListSlice(
+                Arrays.asList(mTestNotificationChannel, mSilentChannel, mMinChannel)));
+
+        StatusBarNotification sbn = new StatusBarNotification(PKG_O, "old.delegate", 8, "tag",
+                UID_O, 0, n, UserHandle.getUserHandleForUid(UID_O), null, timePostedMs);
+
+        mService.addNotification(new NotificationRecord(mContext, sbn, mTestNotificationChannel));
+        mInternalService.removeBitmaps();
+
+        waitForIdle();
+
+        verify(mWorkerHandler, times(1))
+                .post(any(NotificationManagerService.EnqueueNotificationRunnable.class));
+    }
+
+    @Test
     public void testRemoveBitmaps_notBigPicture_noRepost() {
         addRecordAndRemoveBitmaps(
                 createBigPictureRecord(
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
index 07d25df..ba91ca2 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
@@ -52,6 +52,14 @@
     }
     public SensitiveLog lastSensitiveLog = null;
 
+    private static class ClassificationChannelLog {
+        public boolean hasPosted;
+        public boolean isAlerting;
+        public long classification;
+        public long lifetime;
+    }
+    public ClassificationChannelLog  lastClassificationChannelLog = null;
+
     TestableNotificationManagerService(Context context, NotificationRecordLogger logger,
             InstanceIdSequence notificationInstanceIdSequence) {
         super(context, logger, notificationInstanceIdSequence);
@@ -211,4 +219,29 @@
     public interface ComponentPermissionChecker {
         int check(String permission, int uid, int owningUid, boolean exported);
     }
+
+    @Override
+    protected void logClassificationChannelAdjustmentReceived(boolean hasPosted, boolean isAlerting,
+                                                              int classification, int lifetimeMs) {
+        lastClassificationChannelLog = new ClassificationChannelLog();
+        lastClassificationChannelLog.hasPosted = hasPosted;
+        lastClassificationChannelLog.isAlerting = isAlerting;
+        lastClassificationChannelLog.classification = classification;
+        lastClassificationChannelLog.lifetime = lifetimeMs;
+    }
+
+    /**
+     * Returns true if the last recorded classification channel log has all the values specified.
+     */
+    public boolean checkLastClassificationChannelLog(boolean hasPosted, boolean isAlerting,
+                                                     int classification, int lifetime) {
+        if (lastClassificationChannelLog == null) {
+            return false;
+        }
+
+        return hasPosted == lastClassificationChannelLog.hasPosted
+                && isAlerting == lastClassificationChannelLog.isAlerting
+                && classification == lastClassificationChannelLog.classification
+                && lifetime == lastClassificationChannelLog.lifetime;
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
index ad11c26..25a8db6 100644
--- a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
@@ -123,51 +123,7 @@
                 {"MEDIA_PLAY_PAUSE key -> Media Control",
                         new int[]{KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE},
                         KeyGestureEvent.KEY_GESTURE_TYPE_MEDIA_KEY,
-                        KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, 0},
-                {"Meta + B -> Launch Default Browser", new int[]{META_KEY, KeyEvent.KEYCODE_B},
-                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER,
-                        KeyEvent.KEYCODE_B, META_ON},
-                {"EXPLORER key -> Launch Default Browser", new int[]{KeyEvent.KEYCODE_EXPLORER},
-                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER,
-                        KeyEvent.KEYCODE_EXPLORER, 0},
-                {"Meta + C -> Launch Default Contacts", new int[]{META_KEY, KeyEvent.KEYCODE_C},
-                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS,
-                        KeyEvent.KEYCODE_C, META_ON},
-                {"CONTACTS key -> Launch Default Contacts", new int[]{KeyEvent.KEYCODE_CONTACTS},
-                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS,
-                        KeyEvent.KEYCODE_CONTACTS, 0},
-                {"Meta + E -> Launch Default Email", new int[]{META_KEY, KeyEvent.KEYCODE_E},
-                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL,
-                        KeyEvent.KEYCODE_E, META_ON},
-                {"ENVELOPE key -> Launch Default Email", new int[]{KeyEvent.KEYCODE_ENVELOPE},
-                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL,
-                        KeyEvent.KEYCODE_ENVELOPE, 0},
-                {"Meta + K -> Launch Default Calendar", new int[]{META_KEY, KeyEvent.KEYCODE_K},
-                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR,
-                        KeyEvent.KEYCODE_K, META_ON},
-                {"CALENDAR key -> Launch Default Calendar", new int[]{KeyEvent.KEYCODE_CALENDAR},
-                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR,
-                        KeyEvent.KEYCODE_CALENDAR, 0},
-                {"Meta + P -> Launch Default Music", new int[]{META_KEY, KeyEvent.KEYCODE_P},
-                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC,
-                        KeyEvent.KEYCODE_P, META_ON},
-                {"MUSIC key -> Launch Default Music", new int[]{KeyEvent.KEYCODE_MUSIC},
-                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC,
-                        KeyEvent.KEYCODE_MUSIC, 0},
-                {"Meta + U -> Launch Default Calculator", new int[]{META_KEY, KeyEvent.KEYCODE_U},
-                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR,
-                        KeyEvent.KEYCODE_U, META_ON},
-                {"CALCULATOR key -> Launch Default Calculator",
-                        new int[]{KeyEvent.KEYCODE_CALCULATOR},
-                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR,
-                        KeyEvent.KEYCODE_CALCULATOR, 0},
-                {"Meta + M -> Launch Default Maps", new int[]{META_KEY, KeyEvent.KEYCODE_M},
-                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS,
-                        KeyEvent.KEYCODE_M, META_ON},
-                {"Meta + S -> Launch Default Messaging App",
-                        new int[]{META_KEY, KeyEvent.KEYCODE_S},
-                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING,
-                        KeyEvent.KEYCODE_S, META_ON}};
+                        KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, 0}};
     }
 
     @Keep
@@ -295,7 +251,51 @@
                         new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_DOWN},
                         KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE,
                         KeyEvent.KEYCODE_DPAD_DOWN,
-                        META_ON | CTRL_ON}};
+                        META_ON | CTRL_ON},
+                {"Meta + B -> Launch Default Browser", new int[]{META_KEY, KeyEvent.KEYCODE_B},
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER,
+                        KeyEvent.KEYCODE_B, META_ON},
+                {"EXPLORER key -> Launch Default Browser", new int[]{KeyEvent.KEYCODE_EXPLORER},
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER,
+                        KeyEvent.KEYCODE_EXPLORER, 0},
+                {"Meta + C -> Launch Default Contacts", new int[]{META_KEY, KeyEvent.KEYCODE_C},
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS,
+                        KeyEvent.KEYCODE_C, META_ON},
+                {"CONTACTS key -> Launch Default Contacts", new int[]{KeyEvent.KEYCODE_CONTACTS},
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS,
+                        KeyEvent.KEYCODE_CONTACTS, 0},
+                {"Meta + E -> Launch Default Email", new int[]{META_KEY, KeyEvent.KEYCODE_E},
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL,
+                        KeyEvent.KEYCODE_E, META_ON},
+                {"ENVELOPE key -> Launch Default Email", new int[]{KeyEvent.KEYCODE_ENVELOPE},
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL,
+                        KeyEvent.KEYCODE_ENVELOPE, 0},
+                {"Meta + K -> Launch Default Calendar", new int[]{META_KEY, KeyEvent.KEYCODE_K},
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR,
+                        KeyEvent.KEYCODE_K, META_ON},
+                {"CALENDAR key -> Launch Default Calendar", new int[]{KeyEvent.KEYCODE_CALENDAR},
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR,
+                        KeyEvent.KEYCODE_CALENDAR, 0},
+                {"Meta + P -> Launch Default Music", new int[]{META_KEY, KeyEvent.KEYCODE_P},
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC,
+                        KeyEvent.KEYCODE_P, META_ON},
+                {"MUSIC key -> Launch Default Music", new int[]{KeyEvent.KEYCODE_MUSIC},
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC,
+                        KeyEvent.KEYCODE_MUSIC, 0},
+                {"Meta + U -> Launch Default Calculator", new int[]{META_KEY, KeyEvent.KEYCODE_U},
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR,
+                        KeyEvent.KEYCODE_U, META_ON},
+                {"CALCULATOR key -> Launch Default Calculator",
+                        new int[]{KeyEvent.KEYCODE_CALCULATOR},
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR,
+                        KeyEvent.KEYCODE_CALCULATOR, 0},
+                {"Meta + M -> Launch Default Maps", new int[]{META_KEY, KeyEvent.KEYCODE_M},
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS,
+                        KeyEvent.KEYCODE_M, META_ON},
+                {"Meta + S -> Launch Default Messaging App",
+                        new int[]{META_KEY, KeyEvent.KEYCODE_S},
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING,
+                        KeyEvent.KEYCODE_S, META_ON}};
     }
 
     @Keep
@@ -331,6 +331,7 @@
         mPhoneWindowManager.setupAssistForLaunch();
         mPhoneWindowManager.overrideTogglePanel();
         mPhoneWindowManager.overrideInjectKeyEvent();
+        mPhoneWindowManager.overrideRoleManager();
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
index d4ba3b2..9e7575f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -538,7 +538,7 @@
     public void testConsecutiveLaunchNewTask() {
         final IBinder launchCookie = mock(IBinder.class);
         final WindowContainerToken launchRootTask = mock(WindowContainerToken.class);
-        mTrampolineActivity.noDisplay = true;
+        mTrampolineActivity.setIsNoDisplay(true);
         mTrampolineActivity.mLaunchCookie = launchCookie;
         mTrampolineActivity.mLaunchRootTask = launchRootTask;
         onActivityLaunched(mTrampolineActivity);
diff --git a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
index eacb8e9..a0c5b54 100644
--- a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
@@ -25,7 +25,6 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.window.flags.Flags.explicitRefreshRateHints;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -190,14 +189,9 @@
         verify(appWindow.getPendingTransaction(), times(1)).setFrameRate(
                 eq(appWindow.getSurfaceControl()), anyFloat(),
                 eq(Surface.FRAME_RATE_COMPATIBILITY_EXACT), eq(Surface.CHANGE_FRAME_RATE_ALWAYS));
-        if (explicitRefreshRateHints()) {
-            verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
-                    appWindow.getSurfaceControl(),
-                    SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
-        } else {
-            verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
-                    any(SurfaceControl.class), anyInt());
-        }
+        verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
+                appWindow.getSurfaceControl(),
+                SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
     }
 
     @Test
@@ -226,14 +220,9 @@
         verify(appWindow.getPendingTransaction(), times(1)).setFrameRate(
                 eq(appWindow.getSurfaceControl()), anyFloat(),
                 eq(Surface.FRAME_RATE_COMPATIBILITY_EXACT), eq(Surface.CHANGE_FRAME_RATE_ALWAYS));
-        if (explicitRefreshRateHints()) {
-            verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
-                    appWindow.getSurfaceControl(),
-                    SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
-        } else {
-            verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
-                    any(SurfaceControl.class), anyInt());
-        }
+        verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
+                appWindow.getSurfaceControl(),
+                SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
     }
 
     @Test
@@ -288,14 +277,9 @@
         verify(appWindow.getPendingTransaction(), times(1)).setFrameRate(
                 appWindow.getSurfaceControl(), 60,
                 Surface.FRAME_RATE_COMPATIBILITY_EXACT, Surface.CHANGE_FRAME_RATE_ALWAYS);
-        if (explicitRefreshRateHints()) {
-            verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
-                    appWindow.getSurfaceControl(),
-                    SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
-        } else {
-            verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
-                    any(SurfaceControl.class), anyInt());
-        }
+        verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
+                appWindow.getSurfaceControl(),
+                SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
     }
 
     @Test
@@ -352,13 +336,8 @@
         verify(appWindow.getPendingTransaction(), times(1)).setFrameRate(
                 appWindow.getSurfaceControl(), 60,
                 Surface.FRAME_RATE_COMPATIBILITY_DEFAULT, Surface.CHANGE_FRAME_RATE_ALWAYS);
-        if (explicitRefreshRateHints()) {
-            verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
-                    appWindow.getSurfaceControl(),
-                    SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
-        } else {
-            verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
-                    any(SurfaceControl.class), anyInt());
-        }
+        verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
+                appWindow.getSurfaceControl(),
+                SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
index 3fa38bf..3d08ca2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
@@ -21,14 +21,11 @@
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.window.flags.Flags.explicitRefreshRateHints;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
 import android.hardware.display.DisplayManager;
@@ -36,7 +33,6 @@
 import android.platform.test.annotations.Presubmit;
 import android.view.Display.Mode;
 import android.view.Surface;
-import android.view.WindowManager;
 import android.view.WindowManager.LayoutParams;
 
 import androidx.test.filters.SmallTest;
@@ -274,97 +270,6 @@
     }
 
     @Test
-    public void testAnimatingAppOverridePreferredModeId() {
-        final WindowState overrideWindow = createWindow("overrideWindow");
-        overrideWindow.mAttrs.packageName = "com.android.test";
-        overrideWindow.mAttrs.preferredDisplayModeId = LOW_MODE_ID;
-        parcelLayoutParams(overrideWindow);
-        assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(overrideWindow));
-        assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
-        assertEquals(FRAME_RATE_VOTE_LOW_EXACT, overrideWindow.mFrameRateVote);
-        assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
-        assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
-
-        if (explicitRefreshRateHints()) {
-            return;
-        }
-        overrideWindow.mActivityRecord.mSurfaceAnimator.startAnimation(
-                overrideWindow.getPendingTransaction(), mock(AnimationAdapter.class),
-                false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
-        assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
-        assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
-        assertEquals(FRAME_RATE_VOTE_NONE, overrideWindow.mFrameRateVote);
-        assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
-        assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
-
-        // Use default mode if it is animating by shell transition.
-        overrideWindow.mActivityRecord.mSurfaceAnimator.cancelAnimation();
-        registerTestTransitionPlayer();
-        final Transition transition = overrideWindow.mTransitionController.createTransition(
-                WindowManager.TRANSIT_OPEN);
-        transition.collect(overrideWindow.mActivityRecord);
-        assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
-
-        // If there will be display size change when switching from preferred mode to default mode,
-        // then keep the current preferred mode during animating.
-        mDisplayInfo = spy(mDisplayInfo);
-        final Mode defaultMode = new Mode(4321 /* width */, 1234 /* height */, LOW_REFRESH_RATE);
-        doReturn(defaultMode).when(mDisplayInfo).getDefaultMode();
-        mPolicy = new RefreshRatePolicy(mWm, mDisplayInfo, mDenylist);
-        assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(overrideWindow));
-    }
-
-    @Test
-    public void testAnimatingAppOverridePreferredRefreshRate() {
-        final WindowState overrideWindow = createWindow("overrideWindow");
-        overrideWindow.mAttrs.packageName = "com.android.test";
-        overrideWindow.mAttrs.preferredRefreshRate = LOW_REFRESH_RATE;
-        parcelLayoutParams(overrideWindow);
-        assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
-        assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
-        assertEquals(FRAME_RATE_VOTE_LOW_PREFERRED, overrideWindow.mFrameRateVote);
-        assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
-        assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
-
-        if (explicitRefreshRateHints()) {
-            return;
-        }
-        overrideWindow.mActivityRecord.mSurfaceAnimator.startAnimation(
-                overrideWindow.getPendingTransaction(), mock(AnimationAdapter.class),
-                false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
-        assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
-        assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
-        assertEquals(FRAME_RATE_VOTE_NONE, overrideWindow.mFrameRateVote);
-        assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
-        assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
-    }
-
-    @Test
-    public void testAnimatingDenylist() {
-        final WindowState window = createWindow("overrideWindow");
-        window.mAttrs.packageName = "com.android.test";
-        parcelLayoutParams(window);
-        when(mDenylist.isDenylisted("com.android.test")).thenReturn(true);
-        assertEquals(0, mPolicy.getPreferredModeId(window));
-        assertTrue(mPolicy.updateFrameRateVote(window));
-        assertEquals(FRAME_RATE_VOTE_DENY_LIST, window.mFrameRateVote);
-        assertEquals(0, mPolicy.getPreferredMinRefreshRate(window), FLOAT_TOLERANCE);
-        assertEquals(0, mPolicy.getPreferredMaxRefreshRate(window), FLOAT_TOLERANCE);
-
-        if (explicitRefreshRateHints()) {
-            return;
-        }
-        window.mActivityRecord.mSurfaceAnimator.startAnimation(
-                window.getPendingTransaction(), mock(AnimationAdapter.class),
-                false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
-        assertEquals(0, mPolicy.getPreferredModeId(window));
-        assertTrue(mPolicy.updateFrameRateVote(window));
-        assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote);
-        assertEquals(0, mPolicy.getPreferredMinRefreshRate(window), FLOAT_TOLERANCE);
-        assertEquals(0, mPolicy.getPreferredMaxRefreshRate(window), FLOAT_TOLERANCE);
-    }
-
-    @Test
     public void testAnimatingCamera() {
         final WindowState cameraUsingWindow = createWindow("cameraUsingWindow");
         cameraUsingWindow.mAttrs.packageName = "com.android.test";
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 1c87802..62a4711 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -4812,6 +4812,23 @@
         assertFalse(mActivity.isResizeable());
         assertEquals(maxAspect, aspectRatioPolicy.getMaxAspectRatio(), 0 /* delta */);
         assertNotEquals(SCREEN_ORIENTATION_UNSPECIFIED, mActivity.getOverrideOrientation());
+
+        // Activity can opt-out the resizability by component level property.
+        final ComponentName name = getUniqueComponentName(mContext.getPackageName());
+        final PackageManager pm = mContext.getPackageManager();
+        spyOn(pm);
+        final PackageManager.Property property = new PackageManager.Property("propertyName",
+                true /* value */, name.getPackageName(), name.getClassName());
+        try {
+            doReturn(property).when(pm).getPropertyAsUser(
+                    WindowManager.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY,
+                    name.getPackageName(), name.getClassName(), 0 /* userId */);
+        } catch (PackageManager.NameNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+        final ActivityRecord optOutActivity = new ActivityBuilder(mAtm)
+                .setComponent(name).setTask(mTask).build();
+        assertFalse(optOutActivity.isUniversalResizeable());
     }
 
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
index 3c921c6..4568c77 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
@@ -249,7 +249,7 @@
         ActivityRecord reusableActivity = createSourceActivity(fullscreenDisplay);
         ActivityRecord source = createSourceActivity(freeformDisplay);
         source.mHandoverLaunchDisplayId = freeformDisplay.mDisplayId;
-        source.noDisplay = true;
+        source.setIsNoDisplay(true);
 
         assertEquals(RESULT_CONTINUE,
                 new CalculateRequestBuilder()
@@ -272,7 +272,7 @@
         ActivityRecord reusableActivity = createSourceActivity(fullscreenDisplay);
         ActivityRecord source = createSourceActivity(freeformDisplay);
         source.mHandoverTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
-        source.noDisplay = true;
+        source.setIsNoDisplay(true);
 
         assertEquals(RESULT_CONTINUE,
                 new CalculateRequestBuilder()
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index e8779c2..039a3dd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -51,7 +51,6 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
-import static com.android.window.flags.Flags.explicitRefreshRateHints;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -1632,6 +1631,8 @@
         transition.collect(taskA);
         transition.setTransientLaunch(recent, taskA);
         taskRecent.moveToFront("move-recent-to-front");
+        recent.setVisibility(true);
+        recent.setState(ActivityRecord.State.RESUMED, "test");
 
         // During collecting and playing, the recent is on top so it is visible naturally.
         // While B needs isTransientVisible to keep visibility because it is occluded by recents.
@@ -1644,15 +1645,21 @@
 
         // Switch to another task. For example, use gesture navigation to switch tasks.
         taskB.moveToFront("move-b-to-front");
+        appB.setVisibility(true);
         // The previous app (taskA) should be paused first so it loses transient visible. Because
         // visually it is taskA -> taskB, the pause -> resume order should be the same.
         assertFalse(controller.isTransientVisible(taskA));
-        // Keep the recent visible so there won't be 2 activities pausing at the same time. It is
-        // to avoid the latency to resume the current top, i.e. appB.
-        assertTrue(controller.isTransientVisible(taskRecent));
-        // The recent is paused after the transient transition is finished.
-        controller.finishTransition(ActionChain.testFinish(transition));
+        // The recent is occluded by appB.
         assertFalse(controller.isTransientVisible(taskRecent));
+        // Active transient launch won't be paused if the transition is not finished. It is to
+        // avoid the latency to resume the current top (appB) by waiting for both recent and appA
+        // to complete pause.
+        assertEquals(recent, taskRecent.getResumedActivity());
+        assertFalse(taskRecent.startPausing(false /* uiSleeping */, appB /* resuming */, "test"));
+        // ActivityRecord#makeInvisible will add the invisible recent to the stopping list.
+        // So when the transition finished, the recent can still be notified to pause and stop.
+        mDisplayContent.ensureActivitiesVisible(null /* starting */, true /* notifyClients */);
+        assertTrue(mSupervisor.mStoppingActivities.contains(recent));
     }
 
     @Test
@@ -2883,17 +2890,14 @@
 
     @Test
     public void testTransitionsTriggerPerformanceHints() {
-        final boolean explicitRefreshRateHints = explicitRefreshRateHints();
         final var session = new SystemPerformanceHinter.HighPerfSession[1];
-        if (explicitRefreshRateHints) {
-            final SystemPerformanceHinter perfHinter = mWm.mSystemPerformanceHinter;
-            spyOn(perfHinter);
-            doAnswer(invocation -> {
-                session[0] = (SystemPerformanceHinter.HighPerfSession) invocation.callRealMethod();
-                spyOn(session[0]);
-                return session[0];
-            }).when(perfHinter).createSession(anyInt(), anyInt(), anyString());
-        }
+        final SystemPerformanceHinter perfHinter = mWm.mSystemPerformanceHinter;
+        spyOn(perfHinter);
+        doAnswer(invocation -> {
+            session[0] = (SystemPerformanceHinter.HighPerfSession) invocation.callRealMethod();
+            spyOn(session[0]);
+            return session[0];
+        }).when(perfHinter).createSession(anyInt(), anyInt(), anyString());
         final TransitionController controller = mDisplayContent.mTransitionController;
         final TestTransitionPlayer player = registerTestTransitionPlayer();
         final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true).build();
@@ -2905,15 +2909,11 @@
         player.start();
 
         verify(mDisplayContent).enableHighPerfTransition(true);
-        if (explicitRefreshRateHints) {
-            verify(session[0]).start();
-        }
+        verify(session[0]).start();
 
         player.finish();
         verify(mDisplayContent).enableHighPerfTransition(false);
-        if (explicitRefreshRateHints) {
-            verify(session[0]).close();
-        }
+        verify(session[0]).close();
     }
 
     @Test
diff --git a/services/usb/OWNERS b/services/usb/OWNERS
index d35dbb56..2dff392 100644
--- a/services/usb/OWNERS
+++ b/services/usb/OWNERS
@@ -1,9 +1,9 @@
-aprasath@google.com
-kumarashishg@google.com
-sarup@google.com
 anothermark@google.com
+febinthattil@google.com
+aprasath@google.com
 badhri@google.com
 elaurent@google.com
 albertccwang@google.com
 jameswei@google.com
-howardyen@google.com
\ No newline at end of file
+howardyen@google.com
+kumarashishg@google.com
\ No newline at end of file
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
index 45a7faf..07969bd 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
@@ -304,11 +304,17 @@
         @Override
         public void unloadModel(int modelHandle) {
             synchronized (SoundTriggerModule.this) {
-                int sessionId;
                 checkValid();
-                sessionId = mLoadedModels.get(modelHandle).unload();
-                mAudioSessionProvider.releaseSession(sessionId);
+                final var session = mLoadedModels.get(modelHandle).getSession();
+                mLoadedModels.remove(modelHandle);
+                mAudioSessionProvider.releaseSession(session.mSessionHandle);
             }
+            // We don't need to post-synchronize on anything once the HAL has finished the unload
+            // and dispatched any appropriate callbacks -- since we don't do any state checking
+            // onModelUnloaded regardless.
+            // This is generally safe since there is no post-condition on the framework side when
+            // a model is unloaded. We assume that we won't ever have a modelHandle collision.
+            mHalService.unloadSoundModel(modelHandle);
         }
 
         @Override
@@ -402,6 +408,10 @@
                 return mState;
             }
 
+            private SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession getSession() {
+                return mSession;
+            }
+
             private void setState(@NonNull ModelState state) {
                 mState = state;
                 SoundTriggerModule.this.notifyAll();
@@ -426,16 +436,6 @@
                 return mHandle;
             }
 
-            /**
-             * Unloads the model.
-             * @return The audio session handle.
-             */
-            private int unload() {
-                mHalService.unloadSoundModel(mHandle);
-                mLoadedModels.remove(mHandle);
-                return mSession.mSessionHandle;
-            }
-
             private IBinder startRecognition(@NonNull RecognitionConfig config) {
                 if (mIsStopping == true) {
                     throw new RecoverableException(Status.INTERNAL_ERROR, "Race occurred");
diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java
index 9b83719..be34619 100644
--- a/telephony/common/com/android/internal/telephony/SmsApplication.java
+++ b/telephony/common/com/android/internal/telephony/SmsApplication.java
@@ -47,8 +47,6 @@
 import android.util.Log;
 import android.util.SparseArray;
 
-import com.android.internal.annotations.VisibleForTesting;
-
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
@@ -538,7 +536,13 @@
     }
 
     private static String getDefaultSmsPackage(Context context, int userId) {
-        return context.getSystemService(RoleManager.class).getSmsRoleHolder(userId);
+        // RoleManager might be null in unit tests running older mockito versions that do not
+        // support mocking final classes.
+        RoleManager roleManager = context.getSystemService(RoleManager.class);
+        if (roleManager == null) {
+            return "";
+        }
+        return roleManager.getSmsRoleHolder(userId);
     }
 
     /**
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 0808cc3..2a06c3d 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -10153,6 +10153,15 @@
             "satellite_roaming_esos_inactivity_timeout_sec_int";
 
     /**
+     * A string array containing the list of messaging package names that support satellite.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public static final String KEY_SATELLITE_SUPPORTED_MSG_APPS_STRING_ARRAY =
+            "satellite_supported_msg_apps_string_array";
+
+    /**
      * Indicating whether DUN APN should be disabled when the device is roaming. In that case,
      * the default APN (i.e. internet) will be used for tethering.
      *
diff --git a/telephony/java/android/telephony/satellite/ISatelliteDisallowedReasonsCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteDisallowedReasonsCallback.aidl
new file mode 100644
index 0000000..9a6f6b8
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/ISatelliteDisallowedReasonsCallback.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+/**
+ * Interface for satellite disallowed reason change callback.
+ *
+ * @hide
+ */
+oneway interface ISatelliteDisallowedReasonsCallback {
+    /**
+     * Indicates that disallowed reason of satellite has changed.
+     * @param disallowedReasons list of disallowed reasons.
+     */
+    void onSatelliteDisallowedReasonsChanged(in int[] disallowedReasons);
+}
diff --git a/telephony/java/android/telephony/satellite/SatelliteDisallowedReasonsCallback.java b/telephony/java/android/telephony/satellite/SatelliteDisallowedReasonsCallback.java
new file mode 100644
index 0000000..5e276aa
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/SatelliteDisallowedReasonsCallback.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+
+import com.android.internal.telephony.flags.Flags;
+
+/**
+ * A callback class for disallowed reason of satellite change events.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+public interface SatelliteDisallowedReasonsCallback {
+
+    /**
+     * Called when disallowed reason of satellite has changed.
+     * @param disallowedReasons Integer array of disallowed reasons.
+     */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    void onSatelliteDisallowedReasonsChanged(@NonNull int[] disallowedReasons);
+}
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index be02232..7be3f33 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -104,6 +104,11 @@
             sSatelliteCommunicationAllowedStateCallbackMap =
             new ConcurrentHashMap<>();
 
+    private static final ConcurrentHashMap<SatelliteDisallowedReasonsCallback,
+            ISatelliteDisallowedReasonsCallback>
+            sSatelliteDisallowedReasonsCallbackMap =
+            new ConcurrentHashMap<>();
+
     private final int mSubId;
 
     /**
@@ -1487,6 +1492,47 @@
     public @interface SatelliteCommunicationRestrictionReason {}
 
     /**
+     * Satellite is disallowed because it is not supported.
+     * @hide
+     */
+    public static final int SATELLITE_DISALLOWED_REASON_NOT_SUPPORTED = 0;
+
+    /**
+     * Satellite is disallowed because it has not been provisioned.
+     * @hide
+     */
+    public static final int SATELLITE_DISALLOWED_REASON_NOT_PROVISIONED = 1;
+
+    /**
+     * Satellite is disallowed because it is currently outside an allowed region.
+     * @hide
+     */
+    public static final int SATELLITE_DISALLOWED_REASON_NOT_IN_ALLOWED_REGION = 2;
+
+    /**
+     * Satellite is disallowed because an unsupported default message application is being used.
+     * @hide
+     */
+    public static final int SATELLITE_DISALLOWED_REASON_UNSUPPORTED_DEFAULT_MSG_APP = 3;
+
+    /**
+     * Satellite is disallowed because location settings have been disabled.
+     * @hide
+     */
+    public static final int SATELLITE_DISALLOWED_REASON_LOCATION_DISABLED = 4;
+
+    /** @hide */
+    @IntDef(prefix = "SATELLITE_DISALLOWED_REASON_", value = {
+            SATELLITE_DISALLOWED_REASON_NOT_SUPPORTED,
+            SATELLITE_DISALLOWED_REASON_NOT_PROVISIONED,
+            SATELLITE_DISALLOWED_REASON_NOT_IN_ALLOWED_REGION,
+            SATELLITE_DISALLOWED_REASON_UNSUPPORTED_DEFAULT_MSG_APP,
+            SATELLITE_DISALLOWED_REASON_LOCATION_DISABLED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SatelliteDisallowedReason {}
+
+    /**
      * Start receiving satellite transmission updates.
      * This can be called by the pointing UI when the user starts pointing to the satellite.
      * Modem should continue to report the pointing input as the device or satellite moves.
@@ -2579,6 +2625,119 @@
     }
 
     /**
+     * Returns list of disallowed reasons of satellite.
+     *
+     * @return list of disallowed reasons of satellite.
+     *
+     * @throws SecurityException     if caller doesn't have required permission.
+     * @throws IllegalStateException if Telephony process isn't available.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+    @SatelliteDisallowedReason
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    @NonNull
+    public List<Integer> getSatelliteDisallowedReasons() {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                int[] receivedArray = telephony.getSatelliteDisallowedReasons();
+                if (receivedArray.length == 0) {
+                    logd("receivedArray is empty, create empty list");
+                    return new ArrayList<>();
+                } else {
+                    return Arrays.stream(receivedArray).boxed().collect(Collectors.toList());
+                }
+            } else {
+                throw new IllegalStateException("Telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            loge("getSatelliteDisallowedReasons() RemoteException: " + ex);
+            ex.rethrowAsRuntimeException();
+        }
+        return new ArrayList<>();
+    }
+
+    /**
+     * Registers for disallowed reasons change event from satellite service.
+     *
+     * @param executor The executor on which the callback will be called.
+     * @param callback The callback to handle disallowed reasons changed event.
+     *
+     * @throws SecurityException     if caller doesn't have required permission.
+     * @throws IllegalStateException if Telephony process is not available.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public void registerForSatelliteDisallowedReasonsChanged(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull SatelliteDisallowedReasonsCallback callback) {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                ISatelliteDisallowedReasonsCallback internalCallback =
+                        new ISatelliteDisallowedReasonsCallback.Stub() {
+                            @Override
+                            public void onSatelliteDisallowedReasonsChanged(
+                                    int[] disallowedReasons) {
+                                executor.execute(() -> Binder.withCleanCallingIdentity(
+                                        () -> callback.onSatelliteDisallowedReasonsChanged(
+                                                disallowedReasons)));
+                            }
+                        };
+                telephony.registerForSatelliteDisallowedReasonsChanged(internalCallback);
+                sSatelliteDisallowedReasonsCallbackMap.put(callback, internalCallback);
+            } else {
+                throw new IllegalStateException("Telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            loge("registerForSatelliteDisallowedReasonsChanged() RemoteException" + ex);
+            ex.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
+     * Unregisters for disallowed reasons change event from satellite service.
+     *
+     * @param callback The callback that was passed to
+     * {@link #registerForSatelliteDisallowedReasonsChanged(
+     * Executor, SatelliteDisallowedReasonsCallback)}
+     *
+     * @throws SecurityException     if caller doesn't have required permission.
+     * @throws IllegalStateException if Telephony process is not available.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public void unregisterForSatelliteDisallowedReasonsChanged(
+            @NonNull SatelliteDisallowedReasonsCallback callback) {
+        Objects.requireNonNull(callback);
+        ISatelliteDisallowedReasonsCallback internalCallback =
+                sSatelliteDisallowedReasonsCallbackMap.remove(callback);
+
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                if (internalCallback != null) {
+                    telephony.unregisterForSatelliteDisallowedReasonsChanged(internalCallback);
+                } else {
+                    loge("unregisterForSatelliteDisallowedReasonsChanged: No internal callback.");
+                    throw new IllegalArgumentException("callback is not valid");
+                }
+            } else {
+                throw new IllegalStateException("Telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            loge("unregisterForSatelliteDisallowedReasonsChanged() RemoteException: " + ex);
+            ex.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
      * Request to get the signal strength of the satellite connection.
      *
      * <p>
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 231c8f5..62cbb02 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -71,6 +71,7 @@
 import android.telephony.satellite.ISatelliteCapabilitiesCallback;
 import android.telephony.satellite.ISatelliteCommunicationAllowedStateCallback;
 import android.telephony.satellite.ISatelliteDatagramCallback;
+import android.telephony.satellite.ISatelliteDisallowedReasonsCallback;
 import android.telephony.satellite.ISatelliteTransmissionUpdateCallback;
 import android.telephony.satellite.ISatelliteProvisionStateCallback;
 import android.telephony.satellite.ISatelliteSupportedStateCallback;
@@ -2955,6 +2956,37 @@
             in boolean needFullScreenPointingUI, IIntegerConsumer callback);
 
     /**
+     * Returns integer array of disallowed reasons of satellite.
+     *
+     * @return Integer array of disallowed reasons of satellite.
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+            + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+    int[] getSatelliteDisallowedReasons();
+
+    /**
+     * Registers for disallowed reasons change event from satellite service.
+     *
+     * @param callback The callback to handle disallowed reasons changed event.
+     *
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+            + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+    void registerForSatelliteDisallowedReasonsChanged(
+            ISatelliteDisallowedReasonsCallback callback);
+
+    /**
+     * Unregisters for disallowed reasons change event from satellite service.
+     * If callback was not registered before, the request will be ignored.
+     *
+     * @param callback The callback to handle disallowed reasons changed event.
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+            + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+    void unregisterForSatelliteDisallowedReasonsChanged(
+            ISatelliteDisallowedReasonsCallback callback);
+
+    /**
      * Request to get whether satellite communication is allowed for the current location.
      *
      * @param subId The subId of the subscription to get whether satellite communication is allowed
diff --git a/tests/AppJankTest/src/android/app/jank/tests/JankTrackerTest.java b/tests/AppJankTest/src/android/app/jank/tests/JankTrackerTest.java
new file mode 100644
index 0000000..a3e5533
--- /dev/null
+++ b/tests/AppJankTest/src/android/app/jank/tests/JankTrackerTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.jank.tests;
+
+import static org.junit.Assert.assertEquals;
+
+import android.app.jank.Flags;
+import android.app.jank.JankTracker;
+import android.app.jank.StateTracker;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.view.Choreographer;
+import android.view.View;
+
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+@RunWith(AndroidJUnit4.class)
+public class JankTrackerTest {
+    private Choreographer mChoreographer;
+    private JankTracker mJankTracker;
+
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+    /**
+     * Start an empty activity so decore view is not null when creating the JankTracker instance.
+     */
+    private static ActivityScenario<EmptyActivity> sEmptyActivityRule;
+
+    private static String sActivityName;
+
+    private static View sActivityDecorView;
+
+    @BeforeClass
+    public static void classSetup() {
+        sEmptyActivityRule = ActivityScenario.launch(EmptyActivity.class);
+        sEmptyActivityRule.onActivity(activity -> {
+            sActivityDecorView = activity.getWindow().getDecorView();
+            sActivityName = activity.toString();
+        });
+    }
+
+    @AfterClass
+    public static void classTearDown() {
+        sEmptyActivityRule.close();
+    }
+
+    @Before
+    @UiThreadTest
+    public void setup() {
+        mChoreographer = Choreographer.getInstance();
+        mJankTracker = new JankTracker(mChoreographer, sActivityDecorView);
+        mJankTracker.setActivityName(sActivityName);
+    }
+
+    /**
+     * When jank tracking is enabled the activity name should be added as a state to associate
+     * frames to it.
+     */
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+    public void jankTracking_WhenEnabled_ActivityAdded() {
+        mJankTracker.enableAppJankTracking();
+
+        ArrayList<StateTracker.StateData> stateData = new ArrayList<>();
+        mJankTracker.getAllUiStates(stateData);
+
+        assertEquals(1, stateData.size());
+
+        StateTracker.StateData firstState = stateData.getFirst();
+
+        assertEquals(sActivityName, firstState.mWidgetId);
+    }
+
+    /**
+     * No states should be added when tracking is disabled.
+     */
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+    public void jankTrackingDisabled_StatesShouldNot_BeAddedToTracker() {
+        mJankTracker.disableAppJankTracking();
+
+        mJankTracker.addUiState("FAKE_CATEGORY", "FAKE_ID",
+                "FAKE_STATE");
+
+        ArrayList<StateTracker.StateData> stateData = new ArrayList<>();
+        mJankTracker.getAllUiStates(stateData);
+
+        assertEquals(0, stateData.size());
+    }
+
+    /**
+     * The activity name as well as the test state should be added for frame association.
+     */
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+    public void jankTrackingEnabled_StatesShould_BeAddedToTracker() {
+        mJankTracker.forceListenerRegistration();
+
+        mJankTracker.enableAppJankTracking();
+        mJankTracker.addUiState("FAKE_CATEGORY", "FAKE_ID",
+                "FAKE_STATE");
+
+        ArrayList<StateTracker.StateData> stateData = new ArrayList<>();
+        mJankTracker.getAllUiStates(stateData);
+
+        assertEquals(2, stateData.size());
+    }
+
+    /**
+     * Activity state should only be added once even if jank tracking is enabled multiple times.
+     */
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+    public void jankTrackingEnabled_EnabledCalledTwice_ActivityStateOnlyAddedOnce() {
+        mJankTracker.enableAppJankTracking();
+
+        ArrayList<StateTracker.StateData> stateData = new ArrayList<>();
+        mJankTracker.getAllUiStates(stateData);
+
+        assertEquals(1, stateData.size());
+
+        stateData.clear();
+
+        mJankTracker.enableAppJankTracking();
+        mJankTracker.getAllUiStates(stateData);
+
+        assertEquals(1, stateData.size());
+    }
+}
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
index 1c6bd11..332b9b8 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
@@ -132,6 +132,24 @@
         wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
     }
 
+    private fun getMinimizeButtonForTheApp(caption: UiObject2?): UiObject2 {
+        return caption
+            ?.children
+            ?.find { it.resourceName.endsWith(MINIMIZE_BUTTON_VIEW) }
+            ?: error("Unable to find resource $MINIMIZE_BUTTON_VIEW\n")
+    }
+
+    fun minimizeDesktopApp(wmHelper: WindowManagerStateHelper, device: UiDevice) {
+        val caption = getCaptionForTheApp(wmHelper, device)
+        val minimizeButton = getMinimizeButtonForTheApp(caption)
+        minimizeButton.click()
+        wmHelper
+            .StateSyncBuilder()
+            .withAppTransitionIdle()
+            .withWindowSurfaceDisappeared(innerHelper)
+            .waitForAndVerify()
+    }
+
     /** Open maximize menu and click snap resize button on the app header for the given app. */
     fun snapResizeDesktopApp(
         wmHelper: WindowManagerStateHelper,
@@ -400,6 +418,7 @@
         const val DESKTOP_MODE_BUTTON: String = "desktop_button"
         const val SNAP_LEFT_BUTTON: String = "maximize_menu_snap_left_button"
         const val SNAP_RIGHT_BUTTON: String = "maximize_menu_snap_right_button"
+        const val MINIMIZE_BUTTON_VIEW: String = "minimize_window"
         val caption: BySelector
             get() = By.res(SYSTEMUI_PACKAGE, CAPTION)
     }
diff --git a/tests/Input/res/xml/bookmarks.xml b/tests/Input/res/xml/bookmarks.xml
new file mode 100644
index 0000000..ba3f187
--- /dev/null
+++ b/tests/Input/res/xml/bookmarks.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2024 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.
+  -->
+<bookmarks>
+    <!-- the key combinations for the following shortcuts must be in sync
+         with the key combinations sent by the test in KeyGestureControllerTests.java -->
+    <bookmark
+        role="android.app.role.BROWSER"
+        shortcut="b" />
+    <bookmark
+        category="android.intent.category.APP_CONTACTS"
+        shortcut="c" />
+    <bookmark
+        category="android.intent.category.APP_EMAIL"
+        shortcut="e" />
+    <bookmark
+        category="android.intent.category.APP_CALENDAR"
+        shortcut="k" />
+    <bookmark
+        category="android.intent.category.APP_MAPS"
+        shortcut="m" />
+    <bookmark
+        category="android.intent.category.APP_MUSIC"
+        shortcut="p" />
+    <bookmark
+        role="android.app.role.SMS"
+        shortcut="s" />
+    <bookmark
+        category="android.intent.category.APP_CALCULATOR"
+        shortcut="u" />
+
+    <bookmark
+        role="android.app.role.BROWSER"
+        shortcut="b"
+        shift="true" />
+
+    <bookmark
+        category="android.intent.category.APP_CONTACTS"
+        shortcut="c"
+        shift="true" />
+
+    <bookmark
+        package="com.test"
+        class="com.test.BookmarkTest"
+        shortcut="j"
+        shift="true" />
+</bookmarks>
\ No newline at end of file
diff --git a/tests/Input/src/com/android/server/input/InputGestureManagerTests.kt b/tests/Input/src/com/android/server/input/InputGestureManagerTests.kt
index 01c56b7..862886c 100644
--- a/tests/Input/src/com/android/server/input/InputGestureManagerTests.kt
+++ b/tests/Input/src/com/android/server/input/InputGestureManagerTests.kt
@@ -21,6 +21,7 @@
 import android.hardware.input.KeyGestureEvent
 import android.platform.test.annotations.Presubmit
 import android.view.KeyEvent
+import androidx.test.core.app.ApplicationProvider
 import org.junit.Assert.assertEquals
 import org.junit.Before
 import org.junit.Test
@@ -42,7 +43,7 @@
 
     @Before
     fun setup() {
-        inputGestureManager = InputGestureManager()
+        inputGestureManager = InputGestureManager(ApplicationProvider.getApplicationContext())
     }
 
     @Test
diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index 927958e..6eb0045 100644
--- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -95,8 +95,11 @@
 
     @get:Rule
     val extendedMockitoRule =
-        ExtendedMockitoRule.Builder(this).mockStatic(LocalServices::class.java)
-            .mockStatic(PermissionChecker::class.java).build()!!
+        ExtendedMockitoRule.Builder(this)
+            .mockStatic(LocalServices::class.java)
+            .mockStatic(PermissionChecker::class.java)
+            .mockStatic(KeyCharacterMap::class.java)
+            .build()!!
 
     @get:Rule
     val setFlagsRule = SetFlagsRule()
@@ -122,6 +125,9 @@
     @Mock
     private lateinit var kbdController: InputManagerService.KeyboardBacklightControllerInterface
 
+    @Mock
+    private lateinit var kcm: KeyCharacterMap
+
     private lateinit var service: InputManagerService
     private lateinit var localService: InputManagerInternal
     private lateinit var context: Context
@@ -171,6 +177,9 @@
         ExtendedMockito.doReturn(packageManagerInternal).`when` {
             LocalServices.getService(eq(PackageManagerInternal::class.java))
         }
+        ExtendedMockito.doReturn(kcm).`when` {
+            KeyCharacterMap.load(anyInt())
+        }
 
         assertTrue("Local service must be registered", this::localService.isInitialized)
         service.setWindowManagerCallbacks(wmCallbacks)
@@ -206,6 +215,7 @@
         verify(native).setTouchpadTapDraggingEnabled(anyBoolean())
         verify(native).setShouldNotifyTouchpadHardwareState(anyBoolean())
         verify(native).setTouchpadRightClickZoneEnabled(anyBoolean())
+        verify(native).setTouchpadThreeFingerTapShortcutEnabled(anyBoolean())
         verify(native).setShowTouches(anyBoolean())
         verify(native).setMotionClassifierEnabled(anyBoolean())
         verify(native).setMaximumObscuringOpacityForTouch(anyFloat())
diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
index 0b147d6..6c9f764 100644
--- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
@@ -16,13 +16,16 @@
 
 package com.android.server.input
 
+import android.app.role.RoleManager
 import android.content.Context
 import android.content.ContextWrapper
+import android.content.Intent
 import android.content.pm.PackageManager
 import android.content.res.Resources
-import android.hardware.input.IInputManager
+import android.content.res.XmlResourceParser
 import android.hardware.input.AidlKeyGestureEvent
 import android.hardware.input.AppLaunchData
+import android.hardware.input.IInputManager
 import android.hardware.input.IKeyGestureEventListener
 import android.hardware.input.IKeyGestureHandler
 import android.hardware.input.InputGestureData
@@ -34,14 +37,15 @@
 import android.os.SystemClock
 import android.os.SystemProperties
 import android.os.test.TestLooper
-import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.annotations.Presubmit
 import android.platform.test.flag.junit.SetFlagsRule
 import android.view.InputDevice
+import android.view.KeyCharacterMap
 import android.view.KeyEvent
 import android.view.WindowManagerPolicyConstants.FLAG_INTERACTIVE
 import androidx.test.core.app.ApplicationProvider
+import com.android.dx.mockito.inline.extended.ExtendedMockito
 import com.android.internal.R
 import com.android.internal.annotations.Keep
 import com.android.internal.util.FrameworkStatsLog
@@ -99,7 +103,9 @@
     @Rule
     val extendedMockitoRule = ExtendedMockitoRule.Builder(this)
         .mockStatic(FrameworkStatsLog::class.java)
-        .mockStatic(SystemProperties::class.java).build()!!
+        .mockStatic(SystemProperties::class.java)
+        .mockStatic(KeyCharacterMap::class.java)
+        .build()!!
 
     @JvmField
     @Rule
@@ -116,6 +122,7 @@
 
     private var currentPid = 0
     private lateinit var context: Context
+    private lateinit var keyGestureController: KeyGestureController
     private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession
     private lateinit var testLooper: TestLooper
     private var events = mutableListOf<KeyGestureEvent>()
@@ -123,8 +130,6 @@
     @Before
     fun setup() {
         context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
-        Mockito.`when`(context.resources).thenReturn(resources)
-        inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager)
         setupInputDevices()
         setupBehaviors()
         testLooper = TestLooper()
@@ -139,11 +144,13 @@
     }
 
     private fun setupBehaviors() {
-        Mockito.`when`(
-            resources.getBoolean(
-                com.android.internal.R.bool.config_enableScreenshotChord
-            )
-        ).thenReturn(true)
+        Mockito.`when`(SystemProperties.get("ro.debuggable")).thenReturn("1")
+        Mockito.`when`(resources.getBoolean(R.bool.config_enableScreenshotChord)).thenReturn(true)
+        val testBookmarks: XmlResourceParser = context.resources.getXml(
+            com.android.test.input.R.xml.bookmarks
+        )
+        Mockito.`when`(resources.getXml(R.xml.bookmarks)).thenReturn(testBookmarks)
+        Mockito.`when`(context.resources).thenReturn(resources)
         Mockito.`when`(packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH))
             .thenReturn(true)
         Mockito.`when`(packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK))
@@ -152,6 +159,10 @@
     }
 
     private fun setupInputDevices() {
+        val correctIm = context.getSystemService(InputManager::class.java)!!
+        val virtualDevice = correctIm.getInputDevice(KeyCharacterMap.VIRTUAL_KEYBOARD)!!
+        val kcm = virtualDevice.keyCharacterMap!!
+        inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager)
         val inputManager = InputManager(context)
         Mockito.`when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE)))
             .thenReturn(inputManager)
@@ -159,9 +170,17 @@
         val keyboardDevice = InputDevice.Builder().setId(DEVICE_ID).build()
         Mockito.`when`(iInputManager.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID))
         Mockito.`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardDevice)
+        ExtendedMockito.`when`(KeyCharacterMap.load(Mockito.anyInt())).thenReturn(kcm)
     }
 
-    private fun notifyHomeGestureCompleted(keyGestureController: KeyGestureController) {
+    private fun setupKeyGestureController() {
+        keyGestureController = KeyGestureController(context, testLooper.looper)
+        Mockito.`when`(iInputManager.getAppLaunchBookmarks())
+            .thenReturn(keyGestureController.appLaunchBookmarks)
+        keyGestureController.systemRunning()
+    }
+
+    private fun notifyHomeGestureCompleted() {
         keyGestureController.notifyKeyGestureCompleted(
             DEVICE_ID, intArrayOf(KeyEvent.KEYCODE_H),
             KeyEvent.META_META_ON or KeyEvent.META_META_LEFT_ON,
@@ -171,12 +190,12 @@
 
     @Test
     fun testKeyGestureEvent_registerUnregisterListener() {
-        val keyGestureController = KeyGestureController(context, testLooper.looper)
+        setupKeyGestureController()
         val listener = KeyGestureEventListener()
 
         // Register key gesture event listener
         keyGestureController.registerKeyGestureEventListener(listener, 0)
-        notifyHomeGestureCompleted(keyGestureController)
+        notifyHomeGestureCompleted()
         testLooper.dispatchAll()
         assertEquals(
             "Listener should get callbacks on key gesture event completed",
@@ -192,7 +211,7 @@
         // Unregister listener
         events.clear()
         keyGestureController.unregisterKeyGestureEventListener(listener, 0)
-        notifyHomeGestureCompleted(keyGestureController)
+        notifyHomeGestureCompleted()
         testLooper.dispatchAll()
         assertEquals(
             "Listener should not get callback after being unregistered",
@@ -203,7 +222,7 @@
 
     @Test
     fun testKeyGestureEvent_multipleGestureHandlers() {
-        val keyGestureController = KeyGestureController(context, testLooper.looper)
+        setupKeyGestureController()
 
         // Set up two callbacks.
         var callbackCount1 = 0
@@ -267,7 +286,7 @@
     }
 
     @Keep
-    private fun keyGestureEventHandlerTestArguments(): Array<TestData> {
+    private fun systemGesturesTestArguments(): Array<TestData> {
         return arrayOf(
             TestData(
                 "META + A -> Launch Assistant",
@@ -278,25 +297,6 @@
                 intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
             ),
             TestData(
-                "RECENT_APPS -> Show Overview",
-                intArrayOf(KeyEvent.KEYCODE_RECENT_APPS),
-                KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS,
-                intArrayOf(KeyEvent.KEYCODE_RECENT_APPS),
-                0,
-                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
-            ),
-            TestData(
-                "APP_SWITCH -> App Switch",
-                intArrayOf(KeyEvent.KEYCODE_APP_SWITCH),
-                KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH,
-                intArrayOf(KeyEvent.KEYCODE_APP_SWITCH),
-                0,
-                intArrayOf(
-                    KeyGestureEvent.ACTION_GESTURE_START,
-                    KeyGestureEvent.ACTION_GESTURE_COMPLETE
-                )
-            ),
-            TestData(
                 "META + H -> Go Home",
                 intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_H),
                 KeyGestureEvent.KEY_GESTURE_TYPE_HOME,
@@ -465,6 +465,379 @@
                 intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
             ),
             TestData(
+                "META + ALT -> Toggle Caps Lock",
+                intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_ALT_LEFT),
+                KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK,
+                intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_ALT_LEFT),
+                0,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "ALT + META -> Toggle Caps Lock",
+                intArrayOf(KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_META_LEFT),
+                KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK,
+                intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_ALT_LEFT),
+                0,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "META + TAB -> Open Overview",
+                intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_TAB),
+                KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS,
+                intArrayOf(KeyEvent.KEYCODE_TAB),
+                KeyEvent.META_META_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "ALT + TAB -> Toggle Recent Apps Switcher",
+                intArrayOf(KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_TAB),
+                KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER,
+                intArrayOf(KeyEvent.KEYCODE_TAB),
+                KeyEvent.META_ALT_ON,
+                intArrayOf(
+                    KeyGestureEvent.ACTION_GESTURE_START,
+                    KeyGestureEvent.ACTION_GESTURE_COMPLETE
+                )
+            ),
+            TestData(
+                "CTRL + SPACE -> Switch Language Forward",
+                intArrayOf(KeyEvent.KEYCODE_CTRL_LEFT, KeyEvent.KEYCODE_SPACE),
+                KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH,
+                intArrayOf(KeyEvent.KEYCODE_SPACE),
+                KeyEvent.META_CTRL_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "CTRL + SHIFT + SPACE -> Switch Language Backward",
+                intArrayOf(
+                    KeyEvent.KEYCODE_CTRL_LEFT,
+                    KeyEvent.KEYCODE_SHIFT_LEFT,
+                    KeyEvent.KEYCODE_SPACE
+                ),
+                KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH,
+                intArrayOf(KeyEvent.KEYCODE_SPACE),
+                KeyEvent.META_CTRL_ON or KeyEvent.META_SHIFT_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "CTRL + ALT + Z -> Accessibility Shortcut",
+                intArrayOf(
+                    KeyEvent.KEYCODE_CTRL_LEFT,
+                    KeyEvent.KEYCODE_ALT_LEFT,
+                    KeyEvent.KEYCODE_Z
+                ),
+                KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT,
+                intArrayOf(KeyEvent.KEYCODE_Z),
+                KeyEvent.META_CTRL_ON or KeyEvent.META_ALT_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "META + B -> Launch Default Browser",
+                intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_B),
+                KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+                intArrayOf(KeyEvent.KEYCODE_B),
+                KeyEvent.META_META_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+                AppLaunchData.createLaunchDataForRole(RoleManager.ROLE_BROWSER)
+            ),
+            TestData(
+                "META + C -> Launch Default Contacts",
+                intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_C),
+                KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+                intArrayOf(KeyEvent.KEYCODE_C),
+                KeyEvent.META_META_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+                AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CONTACTS)
+            ),
+            TestData(
+                "META + E -> Launch Default Email",
+                intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_E),
+                KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+                intArrayOf(KeyEvent.KEYCODE_E),
+                KeyEvent.META_META_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+                AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_EMAIL)
+            ),
+            TestData(
+                "META + K -> Launch Default Calendar",
+                intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_K),
+                KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+                intArrayOf(KeyEvent.KEYCODE_K),
+                KeyEvent.META_META_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+                AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CALENDAR)
+            ),
+            TestData(
+                "META + M -> Launch Default Maps",
+                intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_M),
+                KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+                intArrayOf(KeyEvent.KEYCODE_M),
+                KeyEvent.META_META_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+                AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_MAPS)
+            ),
+            TestData(
+                "META + P -> Launch Default Music",
+                intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_P),
+                KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+                intArrayOf(KeyEvent.KEYCODE_P),
+                KeyEvent.META_META_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+                AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_MUSIC)
+            ),
+            TestData(
+                "META + S -> Launch Default SMS",
+                intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_S),
+                KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+                intArrayOf(KeyEvent.KEYCODE_S),
+                KeyEvent.META_META_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+                AppLaunchData.createLaunchDataForRole(RoleManager.ROLE_SMS)
+            ),
+            TestData(
+                "META + U -> Launch Default Calculator",
+                intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_U),
+                KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+                intArrayOf(KeyEvent.KEYCODE_U),
+                KeyEvent.META_META_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+                AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CALCULATOR)
+            ),
+            TestData(
+                "META + SHIFT + B -> Launch Default Browser",
+                intArrayOf(
+                    KeyEvent.KEYCODE_META_LEFT,
+                    KeyEvent.KEYCODE_SHIFT_LEFT,
+                    KeyEvent.KEYCODE_B
+                ),
+                KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+                intArrayOf(KeyEvent.KEYCODE_B),
+                KeyEvent.META_META_ON or KeyEvent.META_SHIFT_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+                AppLaunchData.createLaunchDataForRole(RoleManager.ROLE_BROWSER)
+            ),
+            TestData(
+                "META + SHIFT + C -> Launch Default Contacts",
+                intArrayOf(
+                    KeyEvent.KEYCODE_META_LEFT,
+                    KeyEvent.KEYCODE_SHIFT_LEFT,
+                    KeyEvent.KEYCODE_C
+                ),
+                KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+                intArrayOf(KeyEvent.KEYCODE_C),
+                KeyEvent.META_META_ON or KeyEvent.META_SHIFT_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+                AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CONTACTS)
+            ),
+            TestData(
+                "META + SHIFT + J -> Launch Target Activity",
+                intArrayOf(
+                    KeyEvent.KEYCODE_META_LEFT,
+                    KeyEvent.KEYCODE_SHIFT_LEFT,
+                    KeyEvent.KEYCODE_J
+                ),
+                KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+                intArrayOf(KeyEvent.KEYCODE_J),
+                KeyEvent.META_META_ON or KeyEvent.META_SHIFT_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+                AppLaunchData.createLaunchDataForComponent("com.test", "com.test.BookmarkTest")
+            ),
+            TestData(
+                "META + CTRL + DEL -> Trigger Bug Report",
+                intArrayOf(
+                    KeyEvent.KEYCODE_META_LEFT,
+                    KeyEvent.KEYCODE_CTRL_LEFT,
+                    KeyEvent.KEYCODE_DEL
+                ),
+                KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT,
+                intArrayOf(KeyEvent.KEYCODE_DEL),
+                KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "Meta + Alt + 3 -> Toggle Bounce Keys",
+                intArrayOf(
+                    KeyEvent.KEYCODE_META_LEFT,
+                    KeyEvent.KEYCODE_ALT_LEFT,
+                    KeyEvent.KEYCODE_3
+                ),
+                KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS,
+                intArrayOf(KeyEvent.KEYCODE_3),
+                KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "Meta + Alt + 4 -> Toggle Mouse Keys",
+                intArrayOf(
+                    KeyEvent.KEYCODE_META_LEFT,
+                    KeyEvent.KEYCODE_ALT_LEFT,
+                    KeyEvent.KEYCODE_4
+                ),
+                KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS,
+                intArrayOf(KeyEvent.KEYCODE_4),
+                KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "Meta + Alt + 5 -> Toggle Sticky Keys",
+                intArrayOf(
+                    KeyEvent.KEYCODE_META_LEFT,
+                    KeyEvent.KEYCODE_ALT_LEFT,
+                    KeyEvent.KEYCODE_5
+                ),
+                KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS,
+                intArrayOf(KeyEvent.KEYCODE_5),
+                KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "Meta + Alt + 6 -> Toggle Slow Keys",
+                intArrayOf(
+                    KeyEvent.KEYCODE_META_LEFT,
+                    KeyEvent.KEYCODE_ALT_LEFT,
+                    KeyEvent.KEYCODE_6
+                ),
+                KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS,
+                intArrayOf(KeyEvent.KEYCODE_6),
+                KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "META + CTRL + D -> Move a task to next display",
+                intArrayOf(
+                    KeyEvent.KEYCODE_META_LEFT,
+                    KeyEvent.KEYCODE_CTRL_LEFT,
+                    KeyEvent.KEYCODE_D
+                ),
+                KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY,
+                intArrayOf(KeyEvent.KEYCODE_D),
+                KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "ALT + [ -> Resizes a task to fit the left half of the screen",
+                intArrayOf(
+                    KeyEvent.KEYCODE_ALT_LEFT,
+                    KeyEvent.KEYCODE_LEFT_BRACKET
+                ),
+                KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW,
+                intArrayOf(KeyEvent.KEYCODE_LEFT_BRACKET),
+                KeyEvent.META_ALT_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "ALT + ] -> Resizes a task to fit the right half of the screen",
+                intArrayOf(
+                    KeyEvent.KEYCODE_ALT_LEFT,
+                    KeyEvent.KEYCODE_RIGHT_BRACKET
+                ),
+                KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW,
+                intArrayOf(KeyEvent.KEYCODE_RIGHT_BRACKET),
+                KeyEvent.META_ALT_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "ALT + '=' -> Maximizes a task to fit the screen",
+                intArrayOf(
+                    KeyEvent.KEYCODE_ALT_LEFT,
+                    KeyEvent.KEYCODE_EQUALS
+                ),
+                KeyGestureEvent.KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW,
+                intArrayOf(KeyEvent.KEYCODE_EQUALS),
+                KeyEvent.META_ALT_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "ALT + '-' -> Restores a task size to its previous bounds",
+                intArrayOf(
+                    KeyEvent.KEYCODE_ALT_LEFT,
+                    KeyEvent.KEYCODE_MINUS
+                ),
+                KeyGestureEvent.KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE,
+                intArrayOf(KeyEvent.KEYCODE_MINUS),
+                KeyEvent.META_ALT_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            )
+        )
+    }
+
+    @Test
+    @Parameters(method = "systemGesturesTestArguments")
+    @EnableFlags(
+        com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT,
+        com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SHORTCUT_CONTROL,
+        com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_BOUNCE_KEYS_FLAG,
+        com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG,
+        com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG,
+        com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS,
+        com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT,
+        com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS
+    )
+    fun testKeyGestures(test: TestData) {
+        setupKeyGestureController()
+        testKeyGestureInternal(test)
+    }
+
+    @Test
+    @Parameters(method = "systemGesturesTestArguments")
+    @EnableFlags(
+        com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT,
+        com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SHORTCUT_CONTROL,
+        com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_BOUNCE_KEYS_FLAG,
+        com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG,
+        com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG,
+        com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS,
+        com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT,
+        com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS
+    )
+    fun testCustomKeyGesturesNotAllowedForSystemGestures(test: TestData) {
+        setupKeyGestureController()
+        // Need to re-init so that bookmarks are correctly blocklisted
+        Mockito.`when`(iInputManager.getAppLaunchBookmarks())
+            .thenReturn(keyGestureController.appLaunchBookmarks)
+        keyGestureController.systemRunning()
+
+        val builder = InputGestureData.Builder()
+            .setKeyGestureType(test.expectedKeyGestureType)
+            .setTrigger(
+                InputGestureData.createKeyTrigger(
+                    test.expectedKeys[0],
+                    test.expectedModifierState
+                )
+            )
+        if (test.expectedAppLaunchData != null) {
+            builder.setAppLaunchData(test.expectedAppLaunchData)
+        }
+        assertEquals(
+            test.toString(),
+            InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE,
+            keyGestureController.addCustomInputGesture(0, builder.build().aidlData)
+        )
+    }
+
+    @Keep
+    private fun systemKeysTestArguments(): Array<TestData> {
+        return arrayOf(
+            TestData(
+                "RECENT_APPS -> Show Overview",
+                intArrayOf(KeyEvent.KEYCODE_RECENT_APPS),
+                KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS,
+                intArrayOf(KeyEvent.KEYCODE_RECENT_APPS),
+                0,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "APP_SWITCH -> App Switch",
+                intArrayOf(KeyEvent.KEYCODE_APP_SWITCH),
+                KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH,
+                intArrayOf(KeyEvent.KEYCODE_APP_SWITCH),
+                0,
+                intArrayOf(
+                    KeyGestureEvent.ACTION_GESTURE_START,
+                    KeyGestureEvent.ACTION_GESTURE_COMPLETE
+                )
+            ),
+            TestData(
                 "BRIGHTNESS_UP -> Brightness Up",
                 intArrayOf(KeyEvent.KEYCODE_BRIGHTNESS_UP),
                 KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_UP,
@@ -553,73 +926,6 @@
                 intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
             ),
             TestData(
-                "META + ALT -> Toggle Caps Lock",
-                intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_ALT_LEFT),
-                KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK,
-                intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_ALT_LEFT),
-                0,
-                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
-            ),
-            TestData(
-                "ALT + META -> Toggle Caps Lock",
-                intArrayOf(KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_META_LEFT),
-                KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK,
-                intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_ALT_LEFT),
-                0,
-                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
-            ),
-            TestData(
-                "META + TAB -> Open Overview",
-                intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_TAB),
-                KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS,
-                intArrayOf(KeyEvent.KEYCODE_TAB),
-                KeyEvent.META_META_ON,
-                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
-            ),
-            TestData(
-                "ALT + TAB -> Toggle Recent Apps Switcher",
-                intArrayOf(KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_TAB),
-                KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER,
-                intArrayOf(KeyEvent.KEYCODE_TAB),
-                KeyEvent.META_ALT_ON,
-                intArrayOf(
-                    KeyGestureEvent.ACTION_GESTURE_START,
-                    KeyGestureEvent.ACTION_GESTURE_COMPLETE
-                )
-            ),
-            TestData(
-                "CTRL + SPACE -> Switch Language Forward",
-                intArrayOf(KeyEvent.KEYCODE_CTRL_LEFT, KeyEvent.KEYCODE_SPACE),
-                KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH,
-                intArrayOf(KeyEvent.KEYCODE_SPACE),
-                KeyEvent.META_CTRL_ON,
-                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
-            ),
-            TestData(
-                "CTRL + SHIFT + SPACE -> Switch Language Backward",
-                intArrayOf(
-                    KeyEvent.KEYCODE_CTRL_LEFT,
-                    KeyEvent.KEYCODE_SHIFT_LEFT,
-                    KeyEvent.KEYCODE_SPACE
-                ),
-                KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH,
-                intArrayOf(KeyEvent.KEYCODE_SPACE),
-                KeyEvent.META_CTRL_ON or KeyEvent.META_SHIFT_ON,
-                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
-            ),
-            TestData(
-                "CTRL + ALT + Z -> Accessibility Shortcut",
-                intArrayOf(
-                    KeyEvent.KEYCODE_CTRL_LEFT,
-                    KeyEvent.KEYCODE_ALT_LEFT,
-                    KeyEvent.KEYCODE_Z
-                ),
-                KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT,
-                intArrayOf(KeyEvent.KEYCODE_Z),
-                KeyEvent.META_CTRL_ON or KeyEvent.META_ALT_ON,
-                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
-            ),
-            TestData(
                 "SYSRQ -> Take screenshot",
                 intArrayOf(KeyEvent.KEYCODE_SYSRQ),
                 KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT,
@@ -635,19 +941,73 @@
                 0,
                 intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
             ),
+            TestData(
+                "EXPLORER -> Launch Default Browser",
+                intArrayOf(KeyEvent.KEYCODE_EXPLORER),
+                KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+                intArrayOf(KeyEvent.KEYCODE_EXPLORER),
+                0,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+                AppLaunchData.createLaunchDataForRole(RoleManager.ROLE_BROWSER)
+            ),
+            TestData(
+                "ENVELOPE -> Launch Default Email",
+                intArrayOf(KeyEvent.KEYCODE_ENVELOPE),
+                KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+                intArrayOf(KeyEvent.KEYCODE_ENVELOPE),
+                0,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+                AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_EMAIL)
+            ),
+            TestData(
+                "CONTACTS -> Launch Default Contacts",
+                intArrayOf(KeyEvent.KEYCODE_CONTACTS),
+                KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+                intArrayOf(KeyEvent.KEYCODE_CONTACTS),
+                0,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+                AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CONTACTS)
+            ),
+            TestData(
+                "CALENDAR -> Launch Default Calendar",
+                intArrayOf(KeyEvent.KEYCODE_CALENDAR),
+                KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+                intArrayOf(KeyEvent.KEYCODE_CALENDAR),
+                0,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+                AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CALENDAR)
+            ),
+            TestData(
+                "MUSIC -> Launch Default Music",
+                intArrayOf(KeyEvent.KEYCODE_MUSIC),
+                KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+                intArrayOf(KeyEvent.KEYCODE_MUSIC),
+                0,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+                AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_MUSIC)
+            ),
+            TestData(
+                "CALCULATOR -> Launch Default Calculator",
+                intArrayOf(KeyEvent.KEYCODE_CALCULATOR),
+                KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+                intArrayOf(KeyEvent.KEYCODE_CALCULATOR),
+                0,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+                AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CALCULATOR)
+            ),
         )
     }
 
     @Test
-    @Parameters(method = "keyGestureEventHandlerTestArguments")
-    fun testKeyGestures(test: TestData) {
-        val keyGestureController = KeyGestureController(context, testLooper.looper)
-        testKeyGestureInternal(keyGestureController, test)
+    @Parameters(method = "systemKeysTestArguments")
+    fun testSystemKeys(test: TestData) {
+        setupKeyGestureController()
+        testKeyGestureInternal(test)
     }
 
     @Test
     fun testKeycodesFullyConsumed_irrespectiveOfHandlers() {
-        val keyGestureController = KeyGestureController(context, testLooper.looper)
+        setupKeyGestureController()
         val testKeys = intArrayOf(
             KeyEvent.KEYCODE_RECENT_APPS,
             KeyEvent.KEYCODE_APP_SWITCH,
@@ -675,7 +1035,7 @@
         keyGestureController.registerKeyGestureHandler(handler, 0)
 
         for (key in testKeys) {
-            sendKeys(keyGestureController, intArrayOf(key), assertNotSentToApps = true)
+            sendKeys(intArrayOf(key), assertNotSentToApps = true)
         }
     }
 
@@ -683,9 +1043,8 @@
     fun testSearchKeyGestures_defaultSearch() {
         Mockito.`when`(resources.getInteger(R.integer.config_searchKeyBehavior))
             .thenReturn(SEARCH_KEY_BEHAVIOR_DEFAULT_SEARCH)
-        val keyGestureController = KeyGestureController(context, testLooper.looper)
+        setupKeyGestureController()
         testKeyGestureNotProduced(
-            keyGestureController,
             "SEARCH -> Default Search",
             intArrayOf(KeyEvent.KEYCODE_SEARCH),
         )
@@ -695,9 +1054,8 @@
     fun testSearchKeyGestures_searchActivity() {
         Mockito.`when`(resources.getInteger(R.integer.config_searchKeyBehavior))
             .thenReturn(SEARCH_KEY_BEHAVIOR_TARGET_ACTIVITY)
-        val keyGestureController = KeyGestureController(context, testLooper.looper)
+        setupKeyGestureController()
         testKeyGestureInternal(
-            keyGestureController,
             TestData(
                 "SEARCH -> Launch Search Activity",
                 intArrayOf(KeyEvent.KEYCODE_SEARCH),
@@ -713,9 +1071,8 @@
     fun testSettingKeyGestures_doNothing() {
         Mockito.`when`(resources.getInteger(R.integer.config_settingsKeyBehavior))
             .thenReturn(SETTINGS_KEY_BEHAVIOR_NOTHING)
-        val keyGestureController = KeyGestureController(context, testLooper.looper)
+        setupKeyGestureController()
         testKeyGestureNotProduced(
-            keyGestureController,
             "SETTINGS -> Do Nothing",
             intArrayOf(KeyEvent.KEYCODE_SETTINGS),
         )
@@ -725,9 +1082,8 @@
     fun testSettingKeyGestures_settingsActivity() {
         Mockito.`when`(resources.getInteger(R.integer.config_settingsKeyBehavior))
             .thenReturn(SETTINGS_KEY_BEHAVIOR_SETTINGS_ACTIVITY)
-        val keyGestureController = KeyGestureController(context, testLooper.looper)
+        setupKeyGestureController()
         testKeyGestureInternal(
-            keyGestureController,
             TestData(
                 "SETTINGS -> Launch Settings Activity",
                 intArrayOf(KeyEvent.KEYCODE_SETTINGS),
@@ -743,9 +1099,8 @@
     fun testSettingKeyGestures_notificationPanel() {
         Mockito.`when`(resources.getInteger(R.integer.config_settingsKeyBehavior))
             .thenReturn(SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL)
-        val keyGestureController = KeyGestureController(context, testLooper.looper)
+        setupKeyGestureController()
         testKeyGestureInternal(
-            keyGestureController,
             TestData(
                 "SETTINGS -> Toggle Notification Panel",
                 intArrayOf(KeyEvent.KEYCODE_SETTINGS),
@@ -758,221 +1113,12 @@
     }
 
     @Test
-    @EnableFlags(com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT)
-    fun testTriggerBugReport() {
-        Mockito.`when`(SystemProperties.get("ro.debuggable")).thenReturn("1")
-        val keyGestureController = KeyGestureController(context, testLooper.looper)
-        testKeyGestureInternal(
-            keyGestureController,
-            TestData(
-                "META + CTRL + DEL -> Trigger Bug Report",
-                intArrayOf(
-                    KeyEvent.KEYCODE_META_LEFT,
-                    KeyEvent.KEYCODE_CTRL_LEFT,
-                    KeyEvent.KEYCODE_DEL
-                ),
-                KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT,
-                intArrayOf(KeyEvent.KEYCODE_DEL),
-                KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON,
-                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
-            )
-        )
-    }
-
-    @Test
-    @DisableFlags(com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT)
-    fun testTriggerBugReport_flagDisabled() {
-        Mockito.`when`(SystemProperties.get("ro.debuggable")).thenReturn("1")
-        val keyGestureController = KeyGestureController(context, testLooper.looper)
-        testKeyGestureInternal(
-            keyGestureController,
-            TestData(
-                "META + CTRL + DEL -> Not Trigger Bug Report (Fallback to BACK)",
-                intArrayOf(
-                    KeyEvent.KEYCODE_META_LEFT,
-                    KeyEvent.KEYCODE_CTRL_LEFT,
-                    KeyEvent.KEYCODE_DEL
-                ),
-                KeyGestureEvent.KEY_GESTURE_TYPE_BACK,
-                intArrayOf(KeyEvent.KEYCODE_DEL),
-                KeyEvent.META_META_ON,
-                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
-            )
-        )
-    }
-
-    @Test
-    @EnableFlags(
-        com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SHORTCUT_CONTROL,
-        com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_BOUNCE_KEYS_FLAG,
-        com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG,
-        com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG,
-        com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS
-    )
-    fun testKeyboardAccessibilityToggleShortcutPress() {
-        val keyGestureController = KeyGestureController(context, testLooper.looper)
-        testKeyGestureInternal(
-            keyGestureController,
-            TestData(
-                "Meta + Alt + 3 -> Toggle Bounce Keys",
-                intArrayOf(
-                    KeyEvent.KEYCODE_META_LEFT,
-                    KeyEvent.KEYCODE_ALT_LEFT,
-                    KeyEvent.KEYCODE_3
-                ),
-                KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS,
-                intArrayOf(KeyEvent.KEYCODE_3),
-                KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
-                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)))
-        testKeyGestureInternal(
-            keyGestureController,
-            TestData(
-                "Meta + Alt + 4 -> Toggle Mouse Keys",
-                intArrayOf(
-                    KeyEvent.KEYCODE_META_LEFT,
-                    KeyEvent.KEYCODE_ALT_LEFT,
-                    KeyEvent.KEYCODE_4
-                ),
-                KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS,
-                intArrayOf(KeyEvent.KEYCODE_4),
-                KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
-                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)))
-        testKeyGestureInternal(
-            keyGestureController,
-            TestData(
-                "Meta + Alt + 5 -> Toggle Sticky Keys",
-                intArrayOf(
-                    KeyEvent.KEYCODE_META_LEFT,
-                    KeyEvent.KEYCODE_ALT_LEFT,
-                    KeyEvent.KEYCODE_5
-                ),
-                KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS,
-                intArrayOf(KeyEvent.KEYCODE_5),
-                KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
-                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)))
-        testKeyGestureInternal(
-            keyGestureController,
-            TestData(
-                "Meta + Alt + 6 -> Toggle Slow Keys",
-                intArrayOf(
-                    KeyEvent.KEYCODE_META_LEFT,
-                    KeyEvent.KEYCODE_ALT_LEFT,
-                    KeyEvent.KEYCODE_6
-                ),
-                KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS,
-                intArrayOf(KeyEvent.KEYCODE_6),
-                KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
-                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)))
-    }
-
-    @Test
-    @EnableFlags(com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT)
-    fun testMoveToNextDisplay() {
-        val keyGestureController = KeyGestureController(context, testLooper.looper)
-        testKeyGestureInternal(
-            keyGestureController,
-            TestData(
-                "META + CTRL + D -> Move a task to next display",
-                intArrayOf(
-                    KeyEvent.KEYCODE_META_LEFT,
-                    KeyEvent.KEYCODE_CTRL_LEFT,
-                    KeyEvent.KEYCODE_D
-                ),
-                KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY,
-                intArrayOf(KeyEvent.KEYCODE_D),
-                KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON,
-                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
-            )
-        )
-    }
-
-    @Test
-    @EnableFlags(com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS)
-    fun testSnapLeftFreeformTask() {
-        val keyGestureController = KeyGestureController(context, testLooper.looper)
-        testKeyGestureInternal(
-            keyGestureController,
-            TestData(
-                "ALT + [ -> Resizes a task to fit the left half of the screen",
-                intArrayOf(
-                    KeyEvent.KEYCODE_ALT_LEFT,
-                    KeyEvent.KEYCODE_LEFT_BRACKET
-                ),
-                KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW,
-                intArrayOf(KeyEvent.KEYCODE_LEFT_BRACKET),
-                KeyEvent.META_ALT_ON,
-                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
-            )
-        )
-    }
-
-    @Test
-    @EnableFlags(com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS)
-    fun testSnapRightFreeformTask() {
-        val keyGestureController = KeyGestureController(context, testLooper.looper)
-        testKeyGestureInternal(
-            keyGestureController,
-            TestData(
-                "ALT + ] -> Resizes a task to fit the right half of the screen",
-                intArrayOf(
-                    KeyEvent.KEYCODE_ALT_LEFT,
-                    KeyEvent.KEYCODE_RIGHT_BRACKET
-                ),
-                KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW,
-                intArrayOf(KeyEvent.KEYCODE_RIGHT_BRACKET),
-                KeyEvent.META_ALT_ON,
-                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
-            )
-        )
-    }
-
-    @Test
-    @EnableFlags(com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS)
-    fun testMaximizeFreeformTask() {
-        val keyGestureController = KeyGestureController(context, testLooper.looper)
-        testKeyGestureInternal(
-            keyGestureController,
-            TestData(
-                "ALT + '=' -> Maximizes a task to fit the screen",
-                intArrayOf(
-                    KeyEvent.KEYCODE_ALT_LEFT,
-                    KeyEvent.KEYCODE_EQUALS
-                ),
-                KeyGestureEvent.KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW,
-                intArrayOf(KeyEvent.KEYCODE_EQUALS),
-                KeyEvent.META_ALT_ON,
-                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
-            )
-        )
-    }
-
-    @Test
-    @EnableFlags(com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS)
-    fun testRestoreFreeformTask() {
-        val keyGestureController = KeyGestureController(context, testLooper.looper)
-        testKeyGestureInternal(
-            keyGestureController,
-            TestData(
-                "ALT + '-' -> Restores a task size to its previous bounds",
-                intArrayOf(
-                    KeyEvent.KEYCODE_ALT_LEFT,
-                    KeyEvent.KEYCODE_MINUS
-                ),
-                KeyGestureEvent.KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE,
-                intArrayOf(KeyEvent.KEYCODE_MINUS),
-                KeyEvent.META_ALT_ON,
-                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
-            )
-        )
-    }
-
-    @Test
     fun testCapsLockPressNotified() {
-        val keyGestureController = KeyGestureController(context, testLooper.looper)
+        setupKeyGestureController()
         val listener = KeyGestureEventListener()
 
         keyGestureController.registerKeyGestureEventListener(listener, 0)
-        sendKeys(keyGestureController, intArrayOf(KeyEvent.KEYCODE_CAPS_LOCK))
+        sendKeys(intArrayOf(KeyEvent.KEYCODE_CAPS_LOCK))
         testLooper.dispatchAll()
         assertEquals(
             "Listener should get callbacks on key gesture event completed",
@@ -987,7 +1133,7 @@
     }
 
     @Keep
-    private fun keyGestureEventHandlerTestArguments_forKeyCombinations(): Array<TestData> {
+    private fun systemGesturesTestArguments_forKeyCombinations(): Array<TestData> {
         return arrayOf(
             TestData(
                 "VOLUME_DOWN + POWER -> Screenshot Chord",
@@ -1048,14 +1194,14 @@
     }
 
     @Test
-    @Parameters(method = "keyGestureEventHandlerTestArguments_forKeyCombinations")
+    @Parameters(method = "systemGesturesTestArguments_forKeyCombinations")
     @EnableFlags(
         com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER,
         com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER_MULTI_PRESS_GESTURES
     )
     fun testKeyCombinationGestures(test: TestData) {
-        val keyGestureController = KeyGestureController(context, testLooper.looper)
-        testKeyGestureInternal(keyGestureController, test)
+        setupKeyGestureController()
+        testKeyGestureInternal(test)
     }
 
     @Keep
@@ -1096,7 +1242,7 @@
     @Test
     @Parameters(method = "customInputGesturesTestArguments")
     fun testCustomKeyGestures(test: TestData) {
-        val keyGestureController = KeyGestureController(context, testLooper.looper)
+        setupKeyGestureController()
         val builder = InputGestureData.Builder()
             .setKeyGestureType(test.expectedKeyGestureType)
             .setTrigger(
@@ -1104,17 +1250,17 @@
                     test.expectedKeys[0],
                     test.expectedModifierState
                 )
-            );
+            )
         if (test.expectedAppLaunchData != null) {
             builder.setAppLaunchData(test.expectedAppLaunchData)
         }
-        val inputGestureData = builder.build();
+        val inputGestureData = builder.build()
 
         keyGestureController.addCustomInputGesture(0, inputGestureData.aidlData)
-        testKeyGestureInternal(keyGestureController, test)
+        testKeyGestureInternal(test)
     }
 
-    private fun testKeyGestureInternal(keyGestureController: KeyGestureController, test: TestData) {
+    private fun testKeyGestureInternal(test: TestData) {
         var handleEvents = mutableListOf<KeyGestureEvent>()
         val handler = KeyGestureHandler { event, _ ->
             handleEvents.add(KeyGestureEvent(event))
@@ -1123,7 +1269,7 @@
         keyGestureController.registerKeyGestureHandler(handler, 0)
         handleEvents.clear()
 
-        sendKeys(keyGestureController, test.keys)
+        sendKeys(test.keys)
 
         assertEquals(
             "Test: $test doesn't produce correct number of key gesture events",
@@ -1162,11 +1308,7 @@
         keyGestureController.unregisterKeyGestureHandler(handler, 0)
     }
 
-    private fun testKeyGestureNotProduced(
-        keyGestureController: KeyGestureController,
-        testName: String,
-        testKeys: IntArray
-    ) {
+    private fun testKeyGestureNotProduced(testName: String, testKeys: IntArray) {
         var handleEvents = mutableListOf<KeyGestureEvent>()
         val handler = KeyGestureHandler { event, _ ->
             handleEvents.add(KeyGestureEvent(event))
@@ -1175,15 +1317,11 @@
         keyGestureController.registerKeyGestureHandler(handler, 0)
         handleEvents.clear()
 
-        sendKeys(keyGestureController, testKeys)
+        sendKeys(testKeys)
         assertEquals("Test: $testName should not produce Key gesture", 0, handleEvents.size)
     }
 
-    private fun sendKeys(
-        keyGestureController: KeyGestureController,
-        testKeys: IntArray,
-        assertNotSentToApps: Boolean = false
-    ) {
+    private fun sendKeys(testKeys: IntArray, assertNotSentToApps: Boolean = false) {
         var metaState = 0
         val now = SystemClock.uptimeMillis()
         for (key in testKeys) {
@@ -1192,7 +1330,7 @@
                 DEVICE_ID, 0 /*scancode*/, 0 /*flags*/,
                 InputDevice.SOURCE_KEYBOARD
             )
-            interceptKey(keyGestureController, downEvent, assertNotSentToApps)
+            interceptKey(downEvent, assertNotSentToApps)
             metaState = metaState or MODIFIER.getOrDefault(key, 0)
 
             downEvent.recycle()
@@ -1205,7 +1343,7 @@
                 DEVICE_ID, 0 /*scancode*/, 0 /*flags*/,
                 InputDevice.SOURCE_KEYBOARD
             )
-            interceptKey(keyGestureController, upEvent, assertNotSentToApps)
+            interceptKey(upEvent, assertNotSentToApps)
             metaState = metaState and MODIFIER.getOrDefault(key, 0).inv()
 
             upEvent.recycle()
@@ -1213,11 +1351,7 @@
         }
     }
 
-    private fun interceptKey(
-        keyGestureController: KeyGestureController,
-        event: KeyEvent,
-        assertNotSentToApps: Boolean
-    ) {
+    private fun interceptKey(event: KeyEvent, assertNotSentToApps: Boolean) {
         keyGestureController.interceptKeyBeforeQueueing(event, FLAG_INTERACTIVE)
         testLooper.dispatchAll()
 
diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java
index be5c84c..ac96ef2 100644
--- a/tests/testables/src/android/testing/TestableLooper.java
+++ b/tests/testables/src/android/testing/TestableLooper.java
@@ -53,6 +53,7 @@
     private static final Field MESSAGE_QUEUE_MESSAGES_FIELD;
     private static final Field MESSAGE_NEXT_FIELD;
     private static final Field MESSAGE_WHEN_FIELD;
+    private static Field MESSAGE_QUEUE_USE_CONCURRENT_FIELD = null;
 
     private Looper mLooper;
     private MessageQueue mQueue;
@@ -63,6 +64,14 @@
 
     static {
         try {
+            MESSAGE_QUEUE_USE_CONCURRENT_FIELD =
+                    MessageQueue.class.getDeclaredField("mUseConcurrent");
+            MESSAGE_QUEUE_USE_CONCURRENT_FIELD.setAccessible(true);
+        } catch (NoSuchFieldException ignored) {
+            // Ignore - maybe this is not CombinedMessageQueue?
+        }
+
+        try {
             MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages");
             MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true);
             MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next");
@@ -146,6 +155,15 @@
         mLooper = l;
         mQueue = mLooper.getQueue();
         mHandler = new Handler(mLooper);
+
+        // If we are using CombinedMessageQueue, we need to disable concurrent mode for testing.
+        if (MESSAGE_QUEUE_USE_CONCURRENT_FIELD != null) {
+            try {
+                MESSAGE_QUEUE_USE_CONCURRENT_FIELD.set(mQueue, false);
+            } catch (IllegalAccessException e) {
+                throw new RuntimeException(e);
+            }
+        }
     }
 
     /**
diff --git a/tests/utils/testutils/java/android/os/test/TestLooper.java b/tests/utils/testutils/java/android/os/test/TestLooper.java
index e6eabd8..1bcfaf6 100644
--- a/tests/utils/testutils/java/android/os/test/TestLooper.java
+++ b/tests/utils/testutils/java/android/os/test/TestLooper.java
@@ -90,20 +90,6 @@
      * and call {@link #dispatchAll()}.
      */
     public TestLooper(Clock clock) {
-        Field messageQueueUseConcurrentField = null;
-        boolean previousUseConcurrentValue = false;
-        try {
-            messageQueueUseConcurrentField = MessageQueue.class.getDeclaredField("sUseConcurrent");
-            messageQueueUseConcurrentField.setAccessible(true);
-            previousUseConcurrentValue = messageQueueUseConcurrentField.getBoolean(null);
-            // If we are using CombinedMessageQueue, we need to disable concurrent mode for testing.
-            messageQueueUseConcurrentField.set(null, false);
-        } catch (NoSuchFieldException e) {
-            // Ignore - maybe this is not CombinedMessageQueue?
-        } catch (IllegalAccessException e) {
-            throw new RuntimeException("Reflection error constructing or accessing looper", e);
-        }
-
         try {
             mLooper = LOOPER_CONSTRUCTOR.newInstance(false);
 
@@ -114,15 +100,19 @@
             throw new RuntimeException("Reflection error constructing or accessing looper", e);
         }
 
-        mClock = clock;
-
-        if (messageQueueUseConcurrentField != null) {
-            try {
-                messageQueueUseConcurrentField.set(null, previousUseConcurrentValue);
-            } catch (IllegalAccessException e) {
-                throw new RuntimeException("Reflection error constructing or accessing looper", e);
-            }
+        // If we are using CombinedMessageQueue, we need to disable concurrent mode for testing.
+        try {
+            Field messageQueueUseConcurrentField =
+                    MessageQueue.class.getDeclaredField("mUseConcurrent");
+            messageQueueUseConcurrentField.setAccessible(true);
+            messageQueueUseConcurrentField.set(mLooper.getQueue(), false);
+        } catch (NoSuchFieldException e) {
+            // Ignore - maybe this is not CombinedMessageQueue?
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException("Reflection error constructing or accessing looper", e);
         }
+
+        mClock = clock;
     }
 
     public Looper getLooper() {