Merge changes Ib8b3a874,I7935231d into main

* changes:
  Log event when tapping on do not bubble convo
  Log event when tapping on app settings
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 26fbd27..497619a 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -348,6 +348,7 @@
 aconfig_declarations {
     name: "android.security.flags-aconfig",
     package: "android.security",
+    exportable: true,
     container: "system",
     srcs: ["core/java/android/security/*.aconfig"],
 }
@@ -365,6 +366,13 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+java_aconfig_library {
+    name: "android.security.flags-aconfig-java-export",
+    aconfig_declarations: "android.security.flags-aconfig",
+    mode: "exported",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 cc_aconfig_library {
     name: "android_security_flags_aconfig_c_lib",
     aconfig_declarations: "android.security.flags-aconfig",
diff --git a/apex/jobscheduler/framework/aconfig/job.aconfig b/apex/jobscheduler/framework/aconfig/job.aconfig
index 79aef1e..47a85498f 100644
--- a/apex/jobscheduler/framework/aconfig/job.aconfig
+++ b/apex/jobscheduler/framework/aconfig/job.aconfig
@@ -45,3 +45,11 @@
     description: "Introduce a new getPendingJobReasons() API which returns reasons why a job may not have executed. Also deprecate the existing getPendingJobReason() API."
     bug: "372031023"
 }
+
+flag {
+    name: "get_pending_job_reasons_history_api"
+    is_exported: true
+    namespace: "backstage_power"
+    description: "Introduce a new getPendingJobReasonsHistory() API which returns a limited historical view of getPendingJobReasons()."
+    bug: "372031023"
+}
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/api/Android.bp b/api/Android.bp
index ff674c7..0ac85e2 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -73,6 +73,7 @@
         "framework-bluetooth",
         "framework-configinfrastructure",
         "framework-connectivity",
+        "framework-connectivity-b",
         "framework-connectivity-t",
         "framework-devicelock",
         "framework-graphics",
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 d9ab273..9c10615 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;
   }
 
@@ -12732,6 +12735,7 @@
     method public abstract void onPackagesUnavailable(String[], android.os.UserHandle, boolean);
     method public void onPackagesUnsuspended(String[], android.os.UserHandle);
     method public void onShortcutsChanged(@NonNull String, @NonNull java.util.List<android.content.pm.ShortcutInfo>, @NonNull android.os.UserHandle);
+    method @FlaggedApi("android.multiuser.add_launcher_user_config") public void onUserConfigChanged(@NonNull android.content.pm.LauncherUserInfo);
   }
 
   public static final class LauncherApps.PinItemRequest implements android.os.Parcelable {
@@ -12767,10 +12771,12 @@
 
   @FlaggedApi("android.os.allow_private_profile") public final class LauncherUserInfo implements android.os.Parcelable {
     method @FlaggedApi("android.os.allow_private_profile") public int describeContents();
+    method @FlaggedApi("android.multiuser.add_launcher_user_config") @NonNull public android.os.Bundle getUserConfig();
     method @FlaggedApi("android.os.allow_private_profile") public int getUserSerialNumber();
     method @FlaggedApi("android.os.allow_private_profile") @NonNull public String getUserType();
     method @FlaggedApi("android.os.allow_private_profile") public void writeToParcel(@NonNull android.os.Parcel, int);
     field @FlaggedApi("android.os.allow_private_profile") @NonNull public static final android.os.Parcelable.Creator<android.content.pm.LauncherUserInfo> CREATOR;
+    field @FlaggedApi("android.multiuser.add_launcher_user_config") public static final String PRIVATE_SPACE_ENTRYPOINT_HIDDEN = "private_space_entrypoint_hidden";
   }
 
   public final class ModuleInfo implements android.os.Parcelable {
@@ -13701,7 +13707,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 +16424,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 +16851,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 +18716,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
@@ -32944,11 +32953,16 @@
 
   public class Build {
     ctor public Build();
+    method @FlaggedApi("android.os.api_for_backported_fixes") public static int getBackportedFixStatus(long);
     method @NonNull public static java.util.List<android.os.Build.Partition> getFingerprintedPartitions();
     method @FlaggedApi("android.sdk.major_minor_versioning_scheme") public static int getMajorSdkVersion(int);
     method @FlaggedApi("android.sdk.major_minor_versioning_scheme") public static int getMinorSdkVersion(int);
     method public static String getRadioVersion();
     method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public static String getSerial();
+    field @FlaggedApi("android.os.api_for_backported_fixes") public static final int BACKPORTED_FIX_STATUS_FIXED = 1; // 0x1
+    field @FlaggedApi("android.os.api_for_backported_fixes") public static final int BACKPORTED_FIX_STATUS_NOT_APPLICABLE = 2; // 0x2
+    field @FlaggedApi("android.os.api_for_backported_fixes") public static final int BACKPORTED_FIX_STATUS_NOT_FIXED = 3; // 0x3
+    field @FlaggedApi("android.os.api_for_backported_fixes") public static final int BACKPORTED_FIX_STATUS_UNKNOWN = 0; // 0x0
     field public static final String BOARD;
     field public static final String BOOTLOADER;
     field public static final String BRAND;
@@ -33965,12 +33979,14 @@
   }
 
   public final class PowerManager {
+    method @FlaggedApi("android.os.allow_thermal_thresholds_callback") public void addThermalHeadroomListener(@NonNull android.os.PowerManager.OnThermalHeadroomChangedListener);
+    method @FlaggedApi("android.os.allow_thermal_thresholds_callback") public void addThermalHeadroomListener(@NonNull java.util.concurrent.Executor, @NonNull android.os.PowerManager.OnThermalHeadroomChangedListener);
     method public void addThermalStatusListener(@NonNull android.os.PowerManager.OnThermalStatusChangedListener);
     method public void addThermalStatusListener(@NonNull java.util.concurrent.Executor, @NonNull android.os.PowerManager.OnThermalStatusChangedListener);
     method @Nullable public java.time.Duration getBatteryDischargePrediction();
     method public int getCurrentThermalStatus();
     method public int getLocationPowerSaveMode();
-    method public float getThermalHeadroom(@IntRange(from=0, to=60) int);
+    method @FloatRange(from=0.0f) public float getThermalHeadroom(@IntRange(from=0, to=60) int);
     method @FlaggedApi("android.os.allow_thermal_headroom_thresholds") @NonNull public java.util.Map<java.lang.Integer,java.lang.Float> getThermalHeadroomThresholds();
     method public boolean isAllowedInLowPowerStandby(int);
     method public boolean isAllowedInLowPowerStandby(@NonNull String);
@@ -33988,6 +34004,7 @@
     method public boolean isWakeLockLevelSupported(int);
     method public android.os.PowerManager.WakeLock newWakeLock(int, String);
     method @RequiresPermission(android.Manifest.permission.REBOOT) public void reboot(@Nullable String);
+    method @FlaggedApi("android.os.allow_thermal_thresholds_callback") public void removeThermalHeadroomListener(@NonNull android.os.PowerManager.OnThermalHeadroomChangedListener);
     method public void removeThermalStatusListener(@NonNull android.os.PowerManager.OnThermalStatusChangedListener);
     field @Deprecated @RequiresPermission(value=android.Manifest.permission.TURN_SCREEN_ON, conditional=true) public static final int ACQUIRE_CAUSES_WAKEUP = 268435456; // 0x10000000
     field public static final String ACTION_DEVICE_IDLE_MODE_CHANGED = "android.os.action.DEVICE_IDLE_MODE_CHANGED";
@@ -34020,6 +34037,10 @@
     field public static final int THERMAL_STATUS_SHUTDOWN = 6; // 0x6
   }
 
+  @FlaggedApi("android.os.allow_thermal_thresholds_callback") public static interface PowerManager.OnThermalHeadroomChangedListener {
+    method public void onThermalHeadroomChanged(@FloatRange(from=0.0f) float, @FloatRange(from=0.0f) float, @IntRange(from=0) int, @NonNull java.util.Map<java.lang.Integer,java.lang.Float>);
+  }
+
   public static interface PowerManager.OnThermalStatusChangedListener {
     method public void onThermalStatusChanged(int);
   }
@@ -55573,6 +55594,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 +56997,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 +57019,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 860089f..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);
@@ -3595,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);
@@ -5289,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);
@@ -7023,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/api/test-current.txt b/core/api/test-current.txt
index 98d6f58..1173519 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -402,6 +402,7 @@
     method @FlaggedApi("android.service.notification.notification_classification") @NonNull public java.util.Set<java.lang.String> getUnsupportedAdjustmentTypes();
     method public boolean isNotificationPolicyAccessGrantedForPackage(@NonNull String);
     method @FlaggedApi("android.app.modes_api") public boolean removeAutomaticZenRule(@NonNull String, boolean);
+    method @FlaggedApi("android.service.notification.notification_classification") public void setAssistantAdjustmentKeyTypeState(int, boolean);
     method @FlaggedApi("android.app.api_rich_ongoing") public void setCanPostPromotedNotifications(@NonNull String, int, boolean);
     method @RequiresPermission(android.Manifest.permission.MANAGE_NOTIFICATION_LISTENERS) public void setNotificationListenerAccessGranted(@NonNull android.content.ComponentName, boolean, boolean);
     method @RequiresPermission(android.Manifest.permission.MANAGE_TOAST_RATE_LIMITING) public void setToastRateLimitingEnabled(boolean);
diff --git a/core/java/Android.bp b/core/java/Android.bp
index 9875efe..71623c5 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -206,6 +206,7 @@
         "android/os/Temperature.aidl",
         "android/os/CoolingDevice.aidl",
         "android/os/IThermalEventListener.aidl",
+        "android/os/IThermalHeadroomListener.aidl",
         "android/os/IThermalStatusListener.aidl",
         "android/os/IThermalService.aidl",
         "android/os/IPowerManager.aidl",
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 36fc65a..b447897 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -2764,14 +2764,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 +2798,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 +2809,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 +2993,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/AppCompatCallbacks.java b/core/java/android/app/AppCompatCallbacks.java
index 4bfa3b3..cf01f50 100644
--- a/core/java/android/app/AppCompatCallbacks.java
+++ b/core/java/android/app/AppCompatCallbacks.java
@@ -28,6 +28,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class AppCompatCallbacks implements Compatibility.BehaviorChangeDelegate {
     private final long[] mDisabledChanges;
     private final long[] mLoggableChanges;
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 0629b8a..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;
@@ -1611,9 +1614,16 @@
     /** @hide Access to 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.
@@ -1768,6 +1778,7 @@
             OPSTR_RECEIVE_SENSITIVE_NOTIFICATIONS,
             OPSTR_READ_HEART_RATE,
             OPSTR_READ_SKIN_TEMPERATURE,
+            OPSTR_RANGING,
     })
     public @interface AppOpString {}
 
@@ -2515,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} */
@@ -2586,6 +2602,7 @@
             OP_BLUETOOTH_ADVERTISE,
             OP_UWB_RANGING,
             OP_NEARBY_WIFI_DEVICES,
+            Flags.rangingPermissionEnabled() ? OP_RANGING : OP_NONE,
             // Notifications
             OP_POST_NOTIFICATION,
             // Health
@@ -3108,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
@@ -7797,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;
@@ -8851,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();
         }
@@ -9041,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,
@@ -9284,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));
             }
@@ -9324,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/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index a97fa18..0654ac2 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -267,4 +267,7 @@
 
     void setAdjustmentTypeSupportedState(in INotificationListener token, String key, boolean supported);
     List<String> getUnsupportedAdjustmentTypes();
+
+    int[] getAllowedAdjustmentKeyTypes();
+    void setAssistantAdjustmentKeyTypeState(int type, boolean enabled);
 }
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 768b70c..c49b022 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -1848,6 +1848,20 @@
     /**
      * @hide
      */
+    @TestApi
+    @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+    public void setAssistantAdjustmentKeyTypeState(@Adjustment.Types int type, boolean enabled) {
+        INotificationManager service = getService();
+        try {
+            service.setAssistantAdjustmentKeyTypeState(type, enabled);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     */
     public List<String> getEnabledNotificationListenerPackages() {
         INotificationManager service = getService();
         try {
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/TaskInfo.java b/core/java/android/app/TaskInfo.java
index c4a6dec..aac963a 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -364,8 +364,9 @@
         // Do nothing
     }
 
-    private TaskInfo(Parcel source) {
-        readFromParcel(source);
+    /** @hide */
+    public TaskInfo(Parcel source) {
+        readTaskFromParcel(source);
     }
 
     /**
@@ -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();
@@ -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);
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/compat/ChangeIdStateCache.java b/core/java/android/app/compat/ChangeIdStateCache.java
index db663f8..7d21cbf 100644
--- a/core/java/android/app/compat/ChangeIdStateCache.java
+++ b/core/java/android/app/compat/ChangeIdStateCache.java
@@ -31,13 +31,24 @@
  * Handles caching of calls to {@link com.android.internal.compat.IPlatformCompat}
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class ChangeIdStateCache
         extends PropertyInvalidatedCache<ChangeIdStateQuery, Boolean> {
     private static final String CACHE_KEY = createSystemCacheKey("is_compat_change_enabled");
     private static final int MAX_ENTRIES = 2048;
-    private static boolean sDisabled = false;
+    private static boolean sDisabled = getDefaultDisabled();
     private volatile IPlatformCompat mPlatformCompat;
 
+
+    @android.ravenwood.annotation.RavenwoodReplace
+    private static boolean getDefaultDisabled() {
+        return false;
+    }
+
+    private static boolean getDefaultDisabled$ravenwood() {
+        return true; // TODO(b/376676753) Disable the cache for now.
+    }
+
     /** @hide */
     public ChangeIdStateCache() {
         super(MAX_ENTRIES, CACHE_KEY);
diff --git a/core/java/android/app/compat/ChangeIdStateQuery.java b/core/java/android/app/compat/ChangeIdStateQuery.java
index 7598d6c..26d9ab6 100644
--- a/core/java/android/app/compat/ChangeIdStateQuery.java
+++ b/core/java/android/app/compat/ChangeIdStateQuery.java
@@ -35,6 +35,7 @@
  * @hide
  */
 @Immutable
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 final class ChangeIdStateQuery {
 
     static final int QUERY_BY_PACKAGE_NAME = 0;
diff --git a/core/java/android/app/compat/CompatChanges.java b/core/java/android/app/compat/CompatChanges.java
index d7b2ab4..643d4c9 100644
--- a/core/java/android/app/compat/CompatChanges.java
+++ b/core/java/android/app/compat/CompatChanges.java
@@ -39,6 +39,7 @@
  * @hide
  */
 @SystemApi
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class CompatChanges {
     private static final ChangeIdStateCache QUERY_CACHE = new ChangeIdStateCache();
 
diff --git a/core/java/android/app/compat/PackageOverride.java b/core/java/android/app/compat/PackageOverride.java
index ebc2945..ffc1eec 100644
--- a/core/java/android/app/compat/PackageOverride.java
+++ b/core/java/android/app/compat/PackageOverride.java
@@ -36,6 +36,7 @@
  * @hide
  */
 @SystemApi
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class PackageOverride {
 
     /** @hide */
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/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index 6dad015..2be27da 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -333,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,
@@ -348,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));
@@ -366,6 +370,8 @@
         mVirtualSensorCallback = virtualSensorCallback;
         mAudioPlaybackSessionId = audioPlaybackSessionId;
         mAudioRecordingSessionId = audioRecordingSessionId;
+        mDimDuration = dimDuration;
+        mScreenOffTimeout = screenOffTimeout;
     }
 
     @SuppressWarnings("unchecked")
@@ -386,6 +392,8 @@
         mAudioRecordingSessionId = parcel.readInt();
         mHomeComponent = parcel.readTypedObject(ComponentName.CREATOR);
         mInputMethodComponent = parcel.readTypedObject(ComponentName.CREATOR);
+        mDimDuration = parcel.readLong();
+        mScreenOffTimeout = parcel.readLong();
     }
 
     /**
@@ -397,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.
      *
@@ -619,6 +647,8 @@
         dest.writeInt(mAudioRecordingSessionId);
         dest.writeTypedObject(mHomeComponent, flags);
         dest.writeTypedObject(mInputMethodComponent, flags);
+        dest.writeLong(mDimDuration);
+        dest.writeLong(mScreenOffTimeout);
     }
 
     @Override
@@ -653,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
@@ -662,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);
@@ -686,6 +718,8 @@
                 + " mInputMethodComponent=" + mInputMethodComponent
                 + " mAudioPlaybackSessionId=" + mAudioPlaybackSessionId
                 + " mAudioRecordingSessionId=" + mAudioRecordingSessionId
+                + " mDimDuration=" + mDimDuration
+                + " mScreenOffTimeout=" + mScreenOffTimeout
                 + ")";
     }
 
@@ -707,6 +741,8 @@
         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
@@ -726,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();
@@ -748,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
@@ -825,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.
          * *
@@ -1220,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);
             }
@@ -1269,7 +1368,9 @@
                     mVirtualSensorConfigs,
                     virtualSensorCallbackDelegate,
                     mAudioPlaybackSessionId,
-                    mAudioRecordingSessionId);
+                    mAudioRecordingSessionId,
+                    mDimDuration.toMillis(),
+                    mScreenOffTimeout.toMillis());
         }
     }
 }
diff --git a/core/java/android/content/pm/IOnAppsChangedListener.aidl b/core/java/android/content/pm/IOnAppsChangedListener.aidl
index 830cbe0..ade58c4 100644
--- a/core/java/android/content/pm/IOnAppsChangedListener.aidl
+++ b/core/java/android/content/pm/IOnAppsChangedListener.aidl
@@ -16,6 +16,7 @@
 
 package android.content.pm;
 
+import android.content.pm.LauncherUserInfo;
 import android.content.pm.ParceledListSlice;
 import android.os.Bundle;
 import android.os.UserHandle;
@@ -34,4 +35,5 @@
     void onPackagesUnsuspended(in UserHandle user, in String[] packageNames);
     void onShortcutChanged(in UserHandle user, String packageName, in ParceledListSlice shortcuts);
     void onPackageLoadingProgressChanged(in UserHandle user, String packageName, float progress);
+    void onUserConfigChanged(in LauncherUserInfo launcherUserInfo);
 }
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 26f919f..26b8356 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -182,6 +182,8 @@
      */
     public static final int FLAG_CACHE_PEOPLE_TILE_SHORTCUTS = 2;
 
+    private static final String LAUNCHER_USER_INFO_EXTRA_KEY = "launcher_user_info";
+
     /** @hide */
     @IntDef(flag = false, prefix = { "FLAG_CACHE_" }, value = {
             FLAG_CACHE_NOTIFICATION_SHORTCUTS,
@@ -349,6 +351,19 @@
          */
         public void onPackageLoadingProgressChanged(@NonNull String packageName,
                 @NonNull UserHandle user, float progress) {}
+
+        /**
+         * Indicates {@link LauncherUserInfo} configs for a user have changed. The new
+         * {@link LauncherUserInfo} is given as a parameter.
+         *
+         * {@link LauncherUserInfo#getUserConfig} to get the updated user configs.
+         *
+         * @param launcherUserInfo The LauncherUserInfo of the user/profile whose configs have
+         *                         changed.
+         */
+        @FlaggedApi(android.multiuser.Flags.FLAG_ADD_LAUNCHER_USER_CONFIG)
+        public void onUserConfigChanged(@NonNull LauncherUserInfo launcherUserInfo) {
+        }
     }
 
     /**
@@ -2168,6 +2183,21 @@
                 }
             }
         }
+
+        public void onUserConfigChanged(LauncherUserInfo launcherUserInfo) {
+            if (DEBUG) {
+                if (Flags.allowPrivateProfile()
+                        && android.multiuser.Flags.addLauncherUserConfig()) {
+                    Log.d(TAG, "OnUserConfigChanged for user type " + launcherUserInfo.getUserType()
+                            + ", new userConfig: " + launcherUserInfo.getUserConfig());
+                }
+            }
+            synchronized (LauncherApps.this) {
+                for (CallbackMessageHandler callback : mCallbacks) {
+                    callback.postOnUserConfigChanged(launcherUserInfo);
+                }
+            }
+        }
     };
 
     /**
@@ -2224,6 +2254,7 @@
         private static final int MSG_UNSUSPENDED = 7;
         private static final int MSG_SHORTCUT_CHANGED = 8;
         private static final int MSG_LOADING_PROGRESS_CHANGED = 9;
+        private static final int MSG_USER_CONFIG_CHANGED = 10;
 
         private final LauncherApps.Callback mCallback;
 
@@ -2278,6 +2309,14 @@
                     mCallback.onPackageLoadingProgressChanged(info.packageName, info.user,
                             info.mLoadingProgress);
                     break;
+                case MSG_USER_CONFIG_CHANGED:
+                    if (Flags.allowPrivateProfile()
+                            && android.multiuser.Flags.addLauncherUserConfig()) {
+                        mCallback.onUserConfigChanged(Objects.requireNonNull(
+                                info.launcherExtras.getParcelable(LAUNCHER_USER_INFO_EXTRA_KEY,
+                                        LauncherUserInfo.class)));
+                    }
+                    break;
             }
         }
 
@@ -2353,6 +2392,13 @@
             info.mLoadingProgress = progress;
             obtainMessage(MSG_LOADING_PROGRESS_CHANGED, info).sendToTarget();
         }
+
+        public void postOnUserConfigChanged(LauncherUserInfo launcherUserInfo) {
+            CallbackInfo info = new CallbackInfo();
+            info.launcherExtras = new Bundle();
+            info.launcherExtras.putParcelable(LAUNCHER_USER_INFO_EXTRA_KEY, launcherUserInfo);
+            obtainMessage(MSG_USER_CONFIG_CHANGED, info).sendToTarget();
+        }
     }
 
     /**
diff --git a/core/java/android/content/pm/LauncherUserInfo.java b/core/java/android/content/pm/LauncherUserInfo.java
index 8426f54..574af59 100644
--- a/core/java/android/content/pm/LauncherUserInfo.java
+++ b/core/java/android/content/pm/LauncherUserInfo.java
@@ -18,6 +18,7 @@
 
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
+import android.os.Bundle;
 import android.os.Flags;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -31,11 +32,25 @@
 @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
 public final class LauncherUserInfo implements Parcelable {
 
+    /**
+     * A boolean extra indicating whether the private space entrypoint should be hidden when locked.
+     *
+     * @see #getUserConfig
+     */
+    @FlaggedApi(android.multiuser.Flags.FLAG_ADD_LAUNCHER_USER_CONFIG)
+    public static final String PRIVATE_SPACE_ENTRYPOINT_HIDDEN =
+            "private_space_entrypoint_hidden";
+
     private final String mUserType;
 
     // Serial number for the user, should be same as in the {@link UserInfo} object.
     private final int mUserSerialNumber;
 
+    // Additional configs for the user, e.g., whether to hide the private space entrypoint when
+    // locked.
+    private final Bundle mUserConfig;
+
+
     /**
      * Returns type of the user as defined in {@link UserManager}. e.g.,
      * {@link UserManager.USER_TYPE_PROFILE_MANAGED} or {@link UserManager.USER_TYPE_PROFILE_ClONE}
@@ -50,6 +65,17 @@
     }
 
     /**
+     * Returns additional configs for this launcher user
+     *
+     * @see #PRIVATE_SPACE_ENTRYPOINT_HIDDEN
+     */
+    @FlaggedApi(android.multiuser.Flags.FLAG_ADD_LAUNCHER_USER_CONFIG)
+    @NonNull
+    public Bundle getUserConfig() {
+        return mUserConfig;
+    }
+
+    /**
      * Returns serial number of user as returned by
      * {@link UserManager#getSerialNumberForUser(UserHandle)}
      *
@@ -63,6 +89,7 @@
     private LauncherUserInfo(@NonNull Parcel in) {
         mUserType = in.readString16NoHelper();
         mUserSerialNumber = in.readInt();
+        mUserConfig = in.readBundle(Bundle.class.getClassLoader());
     }
 
     @Override
@@ -70,6 +97,7 @@
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeString16NoHelper(mUserType);
         dest.writeInt(mUserSerialNumber);
+        dest.writeBundle(mUserConfig);
     }
 
     @Override
@@ -99,23 +127,36 @@
         private final String mUserType;
 
         private final int mUserSerialNumber;
+        private final Bundle mUserConfig;
+
+
+        @FlaggedApi(android.multiuser.Flags.FLAG_ADD_LAUNCHER_USER_CONFIG)
+        public Builder(@NonNull String userType, int userSerialNumber, @NonNull Bundle config) {
+            this.mUserType = userType;
+            this.mUserSerialNumber = userSerialNumber;
+            this.mUserConfig = config;
+        }
 
         public Builder(@NonNull String userType, int userSerialNumber) {
             this.mUserType = userType;
             this.mUserSerialNumber = userSerialNumber;
+            this.mUserConfig = new Bundle();
         }
 
         /**
          * Builds the LauncherUserInfo object
          */
-        @NonNull public LauncherUserInfo build() {
-            return new LauncherUserInfo(this.mUserType, this.mUserSerialNumber);
+        @NonNull
+        public LauncherUserInfo build() {
+            return new LauncherUserInfo(this.mUserType, this.mUserSerialNumber, this.mUserConfig);
         }
 
     } // End builder
 
-    private LauncherUserInfo(@NonNull  String userType, int userSerialNumber) {
+    private LauncherUserInfo(@NonNull String userType, int userSerialNumber,
+            @NonNull Bundle config) {
         this.mUserType = userType;
         this.mUserSerialNumber = userSerialNumber;
+        this.mUserConfig = config;
     }
 }
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/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 528bde8..3d89ce1 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -543,3 +543,10 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+
+flag {
+    name: "add_launcher_user_config"
+    namespace: "profile_experiences"
+    description: "Add support for LauncherUserInfo configs"
+    bug: "346553745"
+}
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/sqlite/flags.aconfig b/core/java/android/database/sqlite/flags.aconfig
index 2d18d26..d43a669 100644
--- a/core/java/android/database/sqlite/flags.aconfig
+++ b/core/java/android/database/sqlite/flags.aconfig
@@ -2,13 +2,6 @@
 container: "system"
 
 flag {
-     name: "oneway_finalizer_close"
-     namespace: "system_performance"
-     description: "Make BuildCursorNative.close oneway if in the the finalizer"
-     bug: "368221351"
-}
-
-flag {
      name: "oneway_finalizer_close_fixed"
      namespace: "system_performance"
      is_fixed_read_only: true
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/AidlInputGestureData.aidl b/core/java/android/hardware/input/AidlInputGestureData.aidl
index 137f672..e33ec53 100644
--- a/core/java/android/hardware/input/AidlInputGestureData.aidl
+++ b/core/java/android/hardware/input/AidlInputGestureData.aidl
@@ -19,13 +19,26 @@
 /** @hide */
 @JavaDerive(equals=true)
 parcelable AidlInputGestureData {
-    int keycode;
-    int modifierState;
-    int gestureType;
+    Trigger trigger;
 
-    // App launch parameters: Only set if gestureType is KEY_GESTURE_TYPE_LAUNCH_APPLICATION
+    int gestureType;
+    // App launch parameters (Only set if gestureType is LAUNCH_APPLICATION)
     String appLaunchCategory;
     String appLaunchRole;
     String appLaunchPackageName;
     String appLaunchClassName;
+
+    parcelable KeyTrigger {
+        int keycode;
+        int modifierState;
+    }
+
+    parcelable TouchpadGestureTrigger {
+        int gestureType;
+    }
+
+    union Trigger {
+        KeyTrigger key;
+        TouchpadGestureTrigger touchpadGesture;
+    }
 }
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index bce9518..1b96224 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -266,17 +266,19 @@
     @PermissionManuallyEnforced
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
             + "android.Manifest.permission.MANAGE_KEY_GESTURES)")
-    int addCustomInputGesture(in AidlInputGestureData data);
+    int addCustomInputGesture(int userId, in AidlInputGestureData data);
 
     @PermissionManuallyEnforced
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
             + "android.Manifest.permission.MANAGE_KEY_GESTURES)")
-    int removeCustomInputGesture(in AidlInputGestureData data);
+    int removeCustomInputGesture(int userId, in AidlInputGestureData data);
 
     @PermissionManuallyEnforced
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
             + "android.Manifest.permission.MANAGE_KEY_GESTURES)")
-    void removeAllCustomInputGestures();
+    void removeAllCustomInputGestures(int userId);
 
-    AidlInputGestureData[] getCustomInputGestures();
+    AidlInputGestureData[] getCustomInputGestures(int userId);
+
+    AidlInputGestureData[] getAppLaunchBookmarks();
 }
diff --git a/core/java/android/hardware/input/InputGestureData.java b/core/java/android/hardware/input/InputGestureData.java
index 5ab73ce..ee0a2a9 100644
--- a/core/java/android/hardware/input/InputGestureData.java
+++ b/core/java/android/hardware/input/InputGestureData.java
@@ -35,20 +35,40 @@
  */
 public final class InputGestureData {
 
+    public static final int TOUCHPAD_GESTURE_TYPE_UNKNOWN = 0;
+    public static final int TOUCHPAD_GESTURE_TYPE_THREE_FINGER_TAP = 1;
+
     @NonNull
     private final AidlInputGestureData mInputGestureData;
 
-    public InputGestureData(AidlInputGestureData inputGestureData) {
+    public InputGestureData(@NonNull AidlInputGestureData inputGestureData) {
         this.mInputGestureData = inputGestureData;
         validate();
     }
 
     /** Returns the trigger information for this input gesture */
     public Trigger getTrigger() {
-        if (mInputGestureData.keycode != KeyEvent.KEYCODE_UNKNOWN) {
-            return new KeyTrigger(mInputGestureData.keycode, mInputGestureData.modifierState);
+        switch (mInputGestureData.trigger.getTag()) {
+            case AidlInputGestureData.Trigger.Tag.key: {
+                AidlInputGestureData.KeyTrigger trigger = mInputGestureData.trigger.getKey();
+                if (trigger == null) {
+                    throw new RuntimeException("InputGestureData is corrupted, null key trigger!");
+                }
+                return createKeyTrigger(trigger.keycode, trigger.modifierState);
+            }
+            case AidlInputGestureData.Trigger.Tag.touchpadGesture: {
+                AidlInputGestureData.TouchpadGestureTrigger trigger =
+                        mInputGestureData.trigger.getTouchpadGesture();
+                if (trigger == null) {
+                    throw new RuntimeException(
+                            "InputGestureData is corrupted, null touchpad trigger!");
+                }
+                return createTouchpadTrigger(trigger.gestureType);
+            }
+            default:
+                throw new RuntimeException("InputGestureData is corrupted, invalid trigger type!");
+
         }
-        throw new RuntimeException("InputGestureData is corrupted, invalid trigger type!");
     }
 
     /** Returns the action to perform for this input gesture */
@@ -127,9 +147,15 @@
                         "No app launch data for system action launch application");
             }
             AidlInputGestureData data = new AidlInputGestureData();
+            data.trigger = new AidlInputGestureData.Trigger();
             if (mTrigger instanceof KeyTrigger keyTrigger) {
-                data.keycode = keyTrigger.getKeycode();
-                data.modifierState = keyTrigger.getModifierState();
+                data.trigger.setKey(new AidlInputGestureData.KeyTrigger());
+                data.trigger.getKey().keycode = keyTrigger.getKeycode();
+                data.trigger.getKey().modifierState = keyTrigger.getModifierState();
+            } else if (mTrigger instanceof TouchpadTrigger touchpadTrigger) {
+                data.trigger.setTouchpadGesture(new AidlInputGestureData.TouchpadGestureTrigger());
+                data.trigger.getTouchpadGesture().gestureType =
+                        touchpadTrigger.getTouchpadGestureType();
             } else {
                 throw new IllegalArgumentException("Invalid trigger type!");
             }
@@ -163,30 +189,12 @@
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
         InputGestureData that = (InputGestureData) o;
-        return mInputGestureData.keycode == that.mInputGestureData.keycode
-                && mInputGestureData.modifierState == that.mInputGestureData.modifierState
-                && mInputGestureData.gestureType == that.mInputGestureData.gestureType
-                && Objects.equals(mInputGestureData.appLaunchCategory, that.mInputGestureData.appLaunchCategory)
-                && Objects.equals(mInputGestureData.appLaunchRole, that.mInputGestureData.appLaunchRole)
-                && Objects.equals(mInputGestureData.appLaunchPackageName, that.mInputGestureData.appLaunchPackageName)
-                && Objects.equals(mInputGestureData.appLaunchPackageName, that.mInputGestureData.appLaunchPackageName);
+        return Objects.equals(mInputGestureData, that.mInputGestureData);
     }
 
     @Override
     public int hashCode() {
-        int _hash = 1;
-        _hash = 31 * _hash + mInputGestureData.keycode;
-        _hash = 31 * _hash + mInputGestureData.modifierState;
-        _hash = 31 * _hash + mInputGestureData.gestureType;
-        _hash = 31 * _hash + (mInputGestureData.appLaunchCategory != null
-                ? mInputGestureData.appLaunchCategory.hashCode() : 0);
-        _hash = 31 * _hash + (mInputGestureData.appLaunchRole != null
-                ? mInputGestureData.appLaunchRole.hashCode() : 0);
-        _hash = 31 * _hash + (mInputGestureData.appLaunchPackageName != null
-                ? mInputGestureData.appLaunchPackageName.hashCode() : 0);
-        _hash = 31 * _hash + (mInputGestureData.appLaunchPackageName != null
-                ? mInputGestureData.appLaunchPackageName.hashCode() : 0);
-        return _hash;
+        return mInputGestureData.hashCode();
     }
 
     public interface Trigger {
@@ -197,6 +205,11 @@
         return new KeyTrigger(keycode, modifierState);
     }
 
+    /** Creates a input gesture trigger based on a touchpad gesture */
+    public static Trigger createTouchpadTrigger(int touchpadGestureType) {
+        return new TouchpadTrigger(touchpadGestureType);
+    }
+
     /** Key based input gesture trigger */
     public static class KeyTrigger implements Trigger {
         private static final int SHORTCUT_META_MASK =
@@ -242,6 +255,43 @@
         }
     }
 
+    /** Touchpad based input gesture trigger */
+    public static class TouchpadTrigger implements Trigger {
+        private final int mTouchpadGestureType;
+
+        private TouchpadTrigger(int touchpadGestureType) {
+            if (touchpadGestureType != TOUCHPAD_GESTURE_TYPE_THREE_FINGER_TAP) {
+                throw new IllegalArgumentException(
+                        "Invalid touchpadGestureType = " + touchpadGestureType);
+            }
+            mTouchpadGestureType = touchpadGestureType;
+        }
+
+        public int getTouchpadGestureType() {
+            return mTouchpadGestureType;
+        }
+
+        @Override
+        public String toString() {
+            return "TouchpadTrigger{" +
+                    "mTouchpadGestureType=" + mTouchpadGestureType +
+                    '}';
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            TouchpadTrigger that = (TouchpadTrigger) o;
+            return mTouchpadGestureType == that.mTouchpadGestureType;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hashCode(mTouchpadGestureType);
+        }
+    }
+
     /** Data for action to perform when input gesture is triggered */
     public record Action(@KeyGestureEvent.KeyGestureType int keyGestureType,
                          @Nullable AppLaunchData appLaunchData) {
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 876ba10..9050ae2 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -34,6 +34,7 @@
 import android.annotation.SuppressLint;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
+import android.annotation.UserHandleAware;
 import android.annotation.UserIdInt;
 import android.app.ActivityThread;
 import android.compat.annotation.ChangeId;
@@ -1487,16 +1488,16 @@
      */
     @RequiresPermission(Manifest.permission.MANAGE_KEY_GESTURES)
     @CustomInputGestureResult
+    @UserHandleAware
     public int addCustomInputGesture(@NonNull InputGestureData inputGestureData) {
         if (!enableCustomizableInputGestures()) {
             return CUSTOM_INPUT_GESTURE_RESULT_ERROR_OTHER;
         }
         try {
-            return mIm.addCustomInputGesture(inputGestureData.getAidlData());
+            return mIm.addCustomInputGesture(mContext.getUserId(), inputGestureData.getAidlData());
         } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
+            throw e.rethrowFromSystemServer();
         }
-        return CUSTOM_INPUT_GESTURE_RESULT_ERROR_OTHER;
     }
 
     /** Removes an existing custom gesture
@@ -1510,16 +1511,17 @@
      */
     @RequiresPermission(Manifest.permission.MANAGE_KEY_GESTURES)
     @CustomInputGestureResult
+    @UserHandleAware
     public int removeCustomInputGesture(@NonNull InputGestureData inputGestureData) {
         if (!enableCustomizableInputGestures()) {
             return CUSTOM_INPUT_GESTURE_RESULT_ERROR_OTHER;
         }
         try {
-            return mIm.removeCustomInputGesture(inputGestureData.getAidlData());
+            return mIm.removeCustomInputGesture(mContext.getUserId(),
+                    inputGestureData.getAidlData());
         } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
+            throw e.rethrowFromSystemServer();
         }
-        return CUSTOM_INPUT_GESTURE_RESULT_ERROR_OTHER;
     }
 
     /** Removes all custom input gestures
@@ -1527,14 +1529,15 @@
      * @hide
      */
     @RequiresPermission(Manifest.permission.MANAGE_KEY_GESTURES)
+    @UserHandleAware
     public void removeAllCustomInputGestures() {
         if (!enableCustomizableInputGestures()) {
             return;
         }
         try {
-            mIm.removeAllCustomInputGestures();
+            mIm.removeAllCustomInputGestures(mContext.getUserId());
         } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -1542,22 +1545,43 @@
      *
      * @hide
      */
+    @UserHandleAware
     public List<InputGestureData> getCustomInputGestures() {
         List<InputGestureData> result = new ArrayList<>();
         if (!enableCustomizableInputGestures()) {
             return result;
         }
         try {
-            for (AidlInputGestureData data : mIm.getCustomInputGestures()) {
+            for (AidlInputGestureData data : mIm.getCustomInputGestures(mContext.getUserId())) {
                 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 c456698..96f6ad1 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -30,6 +30,7 @@
 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.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;
@@ -386,7 +387,7 @@
      * @hide
      */
     public static boolean isTouchpadThreeFingerTapShortcutFeatureFlagEnabled() {
-        return enableCustomizableInputGestures() && touchpadThreeFingerTapShortcut();
+        return isCustomizableInputGesturesFeatureFlagEnabled() && touchpadThreeFingerTapShortcut();
     }
 
     /**
@@ -1132,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 f9cb94a..38e32c6 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -141,7 +141,7 @@
 flag {
   name: "keyboard_a11y_shortcut_control"
   namespace: "input"
-  description: "Adds shortcuts to toggle and control a11y features"
+  description: "Adds shortcuts to toggle and control a11y keyboard features"
   bug: "373458181"
 }
 
@@ -165,3 +165,10 @@
   description: "Turns three-finger touchpad taps into a customizable shortcut."
   bug: "365063048"
 }
+
+flag {
+  name: "enable_talkback_and_magnifier_key_gestures"
+  namespace: "input"
+  description: "Adds key gestures for talkback and magnifier"
+  bug: "375277034"
+}
\ No newline at end of file
diff --git a/core/java/android/hardware/location/OWNERS b/core/java/android/hardware/location/OWNERS
index 747f909..340d6f2 100644
--- a/core/java/android/hardware/location/OWNERS
+++ b/core/java/android/hardware/location/OWNERS
@@ -9,4 +9,4 @@
 yuhany@google.com
 
 # ContextHub team
-per-file *ContextHub*,*NanoApp* = file:platform/system/chre:/OWNERS
+per-file Android.bp,*Hub*,*NanoApp* = file:platform/system/chre:/OWNERS
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index c6fd0ee..7745b03 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -1521,8 +1521,7 @@
         private final boolean mAllowMultipleTriggers;
         private final KeyphraseRecognitionExtra mKeyphrases[];
         private final byte[] mData;
-        @ModuleProperties.AudioCapabilities
-        private final int mAudioCapabilities;
+        private final @ModuleProperties.AudioCapabilities int mAudioCapabilities;
 
         /**
          * Constructor for {@link RecognitionConfig} with {@code audioCapabilities} describes a
@@ -1535,11 +1534,12 @@
          * @param keyphrases List of keyphrases in the sound model.
          * @param data Opaque data for use by system applications who know about voice engine
          *             internals, typically during enrollment.
-         * @param audioCapabilities Bit field encoding of the AudioCapabilities.
+         * @param audioCapabilities Bit field encoding of the AudioCapabilities. See
+         *                          {@link ModuleProperties.AudioCapabilities} for details.
          */
         private RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
                 @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data,
-                int audioCapabilities) {
+                @ModuleProperties.AudioCapabilities int audioCapabilities) {
             this.mCaptureRequested = captureRequested;
             this.mAllowMultipleTriggers = allowMultipleTriggers;
             this.mKeyphrases = keyphrases != null ? keyphrases : new KeyphraseRecognitionExtra[0];
@@ -1617,8 +1617,11 @@
             return mData;
         }
 
-        /** Bit field encoding of the AudioCapabilities supported by the firmware. */
-        public int getAudioCapabilities() {
+        /**
+         * Bit field encoding of the AudioCapabilities supported by the firmware. See
+         * {@link ModuleProperties.AudioCapabilities} for details.
+         */
+        public @ModuleProperties.AudioCapabilities int getAudioCapabilities() {
             return mAudioCapabilities;
         }
 
@@ -1702,7 +1705,7 @@
             private boolean mAllowMultipleTriggers;
             @Nullable private KeyphraseRecognitionExtra[] mKeyphrases;
             @Nullable private byte[] mData;
-            private int mAudioCapabilities;
+            private @ModuleProperties.AudioCapabilities int mAudioCapabilities;
 
             /**
              * Constructs a new Builder with the default values.
@@ -1750,18 +1753,20 @@
              *             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;
             }
 
             /**
              * Sets the audio capabilities field.
              * @param audioCapabilities The bit field encoding of the audio capabilities associated
-             *                          with this recognition session.
+             *                          with this recognition session. See
+             *                          {@link ModuleProperties.AudioCapabilities} for details.
              * @return the same Builder instance.
              */
-            public @NonNull Builder setAudioCapabilities(int audioCapabilities) {
+            public @NonNull Builder setAudioCapabilities(
+                @ModuleProperties.AudioCapabilities int audioCapabilities) {
                 mAudioCapabilities = audioCapabilities;
                 return this;
             }
diff --git a/core/java/android/os/AggregateBatteryConsumer.java b/core/java/android/os/AggregateBatteryConsumer.java
index c7f8878..f0e12ca 100644
--- a/core/java/android/os/AggregateBatteryConsumer.java
+++ b/core/java/android/os/AggregateBatteryConsumer.java
@@ -85,7 +85,7 @@
             throw new XmlPullParserException("Invalid XML parser state");
         }
 
-        consumerBuilder.setConsumedPower(
+        consumerBuilder.addConsumedPower(
                 parser.getAttributeDouble(null, BatteryUsageStats.XML_ATTR_POWER));
 
         while (!(eventType == XmlPullParser.END_TAG && parser.getName().equals(
@@ -132,11 +132,19 @@
         }
 
         /**
+         * Adds the total power included in this aggregate.
+         */
+        public Builder addConsumedPower(double consumedPowerMah) {
+            mData.putDouble(COLUMN_INDEX_CONSUMED_POWER,
+                    mData.getDouble(COLUMN_INDEX_CONSUMED_POWER) + consumedPowerMah);
+            return this;
+        }
+
+        /**
          * Adds power and usage duration from the supplied AggregateBatteryConsumer.
          */
         public void add(AggregateBatteryConsumer aggregateBatteryConsumer) {
-            setConsumedPower(mData.getDouble(COLUMN_INDEX_CONSUMED_POWER)
-                    + aggregateBatteryConsumer.getConsumedPower());
+            addConsumedPower(aggregateBatteryConsumer.getConsumedPower());
             mPowerComponentsBuilder.addPowerAndDuration(aggregateBatteryConsumer.mPowerComponents);
         }
 
diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java
index b0ecca7..14b67f6 100644
--- a/core/java/android/os/BatteryConsumer.java
+++ b/core/java/android/os/BatteryConsumer.java
@@ -1064,7 +1064,9 @@
          * @param componentId    The ID of the power component, e.g.
          *                       {@link BatteryConsumer#POWER_COMPONENT_CPU}.
          * @param componentPower Amount of consumed power in mAh.
+         * @deprecated use {@link #addConsumedPower}
          */
+        @Deprecated
         @NonNull
         public T setConsumedPower(@PowerComponentId int componentId, double componentPower) {
             return setConsumedPower(componentId, componentPower, POWER_MODEL_POWER_PROFILE);
@@ -1076,7 +1078,9 @@
          * @param componentId    The ID of the power component, e.g.
          *                       {@link BatteryConsumer#POWER_COMPONENT_CPU}.
          * @param componentPower Amount of consumed power in mAh.
+         * @deprecated use {@link #addConsumedPower}
          */
+        @Deprecated
         @SuppressWarnings("unchecked")
         @NonNull
         public T setConsumedPower(@PowerComponentId int componentId, double componentPower,
@@ -1104,6 +1108,21 @@
 
         @SuppressWarnings("unchecked")
         @NonNull
+        public T addConsumedPower(@PowerComponentId int componentId, double componentPower) {
+            mPowerComponentsBuilder.addConsumedPower(getKey(componentId, PROCESS_STATE_UNSPECIFIED),
+                    componentPower, POWER_MODEL_UNDEFINED);
+            return (T) this;
+        }
+
+        @SuppressWarnings("unchecked")
+        @NonNull
+        public T addConsumedPower(Key key, double componentPower) {
+            mPowerComponentsBuilder.addConsumedPower(key, componentPower, POWER_MODEL_UNDEFINED);
+            return (T) this;
+        }
+
+        @SuppressWarnings("unchecked")
+        @NonNull
         public T addConsumedPower(Key key, double componentPower, @PowerModel int powerModel) {
             mPowerComponentsBuilder.addConsumedPower(key, componentPower, powerModel);
             return (T) this;
@@ -1115,7 +1134,9 @@
          * @param componentId              The ID of the power component, e.g.
          *                                 {@link UidBatteryConsumer#POWER_COMPONENT_CPU}.
          * @param componentUsageTimeMillis Amount of time in microseconds.
+         * @deprecated use {@link #addUsageDurationMillis}
          */
+        @Deprecated
         @SuppressWarnings("unchecked")
         @NonNull
         public T setUsageDurationMillis(@PowerComponentId int componentId,
@@ -1126,6 +1147,7 @@
             return (T) this;
         }
 
+        @Deprecated
         @SuppressWarnings("unchecked")
         @NonNull
         public T setUsageDurationMillis(Key key, long componentUsageTimeMillis) {
@@ -1133,6 +1155,14 @@
             return (T) this;
         }
 
+        @NonNull
+        public T addUsageDurationMillis(@PowerComponentId int componentId,
+                long componentUsageTimeMillis) {
+            mPowerComponentsBuilder.addUsageDurationMillis(
+                    getKey(componentId, PROCESS_STATE_UNSPECIFIED), componentUsageTimeMillis);
+            return (T) this;
+        }
+
         @SuppressWarnings("unchecked")
         @NonNull
         public T addUsageDurationMillis(Key key, long componentUsageTimeMillis) {
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index 5ae425f..72e4cef 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -209,7 +209,7 @@
         }
 
         builder.getAggregateBatteryConsumerBuilder(AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
-                .setConsumedPower(totalPowerMah);
+                .addConsumedPower(totalPowerMah);
 
         mAggregateBatteryConsumers =
                 new AggregateBatteryConsumer[AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT];
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index b3aebad..102bdd0 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -1565,6 +1565,61 @@
     /** A string that uniquely identifies this build.  Do not attempt to parse this value. */
     public static final String FINGERPRINT = deriveFingerprint();
 
+    /** The status of the known issue on this device is not known. */
+    @FlaggedApi(android.os.Flags.FLAG_API_FOR_BACKPORTED_FIXES)
+    public static final int BACKPORTED_FIX_STATUS_UNKNOWN = 0;
+    /** The known issue is fixed on this device. */
+    @FlaggedApi(android.os.Flags.FLAG_API_FOR_BACKPORTED_FIXES)
+    public static final int BACKPORTED_FIX_STATUS_FIXED = 1;
+    /**
+     * The known issue is not applicable to this device.
+     *
+     * <p>For example if the issue only affects a specific brand, devices
+     * from other brands would report not applicable.
+     */
+    @FlaggedApi(android.os.Flags.FLAG_API_FOR_BACKPORTED_FIXES)
+    public static final int BACKPORTED_FIX_STATUS_NOT_APPLICABLE = 2;
+    /** The known issue is not fixed on this device. */
+    @FlaggedApi(android.os.Flags.FLAG_API_FOR_BACKPORTED_FIXES)
+    public static final int BACKPORTED_FIX_STATUS_NOT_FIXED = 3;
+
+    /**
+     * The status of the backported fix for a known issue on this device.
+     *
+     * @hide
+     */
+    @IntDef(
+            prefix = {"BACKPORTED_FIX_STATUS_"},
+            value = {
+                    BACKPORTED_FIX_STATUS_UNKNOWN,
+                    BACKPORTED_FIX_STATUS_FIXED,
+                    BACKPORTED_FIX_STATUS_NOT_APPLICABLE,
+                    BACKPORTED_FIX_STATUS_NOT_FIXED,
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface BackportedFixStatus {
+    }
+
+    /**
+     * The status of the backported fix for a known issue on this device.
+     *
+     * @param id The id of the known issue to check.
+     * @return {@link #BACKPORTED_FIX_STATUS_FIXED} if the known issue is
+     * fixed on this device,
+     * {@link #BACKPORTED_FIX_STATUS_NOT_FIXED} if the known issue is not
+     * fixed on this device,
+     * {@link #BACKPORTED_FIX_STATUS_NOT_APPLICABLE} if the known issue is
+     * is not applicable on this device,
+     * otherwise {@link #BACKPORTED_FIX_STATUS_UNKNOWN}.
+     */
+
+    @FlaggedApi(android.os.Flags.FLAG_API_FOR_BACKPORTED_FIXES)
+    public static @BackportedFixStatus int getBackportedFixStatus(long id) {
+        // TODO: b/308461809 - query aliases from system prop
+        // TODO: b/372518979 - use backported fix datastore.
+        return BACKPORTED_FIX_STATUS_UNKNOWN;
+    }
+
     /**
      * Some devices split the fingerprint components between multiple
      * partitions, so we might derive the fingerprint at runtime.
diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
index 66f4198..acda0c5 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) {
@@ -431,13 +426,26 @@
         // Update the file descriptor record if the listener changed the set of
         // events to watch and the listener itself hasn't been updated since.
         if (newWatchedEvents != oldWatchedEvents) {
-            synchronized (this) {
-                int index = mFileDescriptorRecords.indexOfKey(fd);
-                if (index >= 0 && mFileDescriptorRecords.valueAt(index) == record
-                        && record.mSeq == seq) {
-                    record.mEvents = newWatchedEvents;
-                    if (newWatchedEvents == 0) {
-                        mFileDescriptorRecords.removeAt(index);
+            if (mUseConcurrent) {
+                synchronized (mFileDescriptorRecordsLock) {
+                    int index = mFileDescriptorRecords.indexOfKey(fd);
+                    if (index >= 0 && mFileDescriptorRecords.valueAt(index) == record
+                            && record.mSeq == seq) {
+                        record.mEvents = newWatchedEvents;
+                        if (newWatchedEvents == 0) {
+                            mFileDescriptorRecords.removeAt(index);
+                        }
+                    }
+                }
+            } else {
+                synchronized (this) {
+                    int index = mFileDescriptorRecords.indexOfKey(fd);
+                    if (index >= 0 && mFileDescriptorRecords.valueAt(index) == record
+                            && record.mSeq == seq) {
+                        record.mEvents = newWatchedEvents;
+                        if (newWatchedEvents == 0) {
+                            mFileDescriptorRecords.removeAt(index);
+                        }
                     }
                 }
             }
@@ -708,7 +716,7 @@
 
     @UnsupportedAppUsage
     Message next() {
-        if (sUseConcurrent) {
+        if (mUseConcurrent) {
             return nextConcurrent();
         }
 
@@ -834,7 +842,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 +906,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 +999,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 +1066,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 +1195,7 @@
         if (h == null) {
             return false;
         }
-        if (sUseConcurrent) {
+        if (mUseConcurrent) {
             return findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObject,
                     false);
         }
@@ -1219,7 +1227,7 @@
         if (h == null) {
             return false;
         }
-        if (sUseConcurrent) {
+        if (mUseConcurrent) {
             return findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObjectEquals,
                     false);
 
@@ -1253,7 +1261,7 @@
         if (h == null) {
             return false;
         }
-        if (sUseConcurrent) {
+        if (mUseConcurrent) {
             return findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObject,
                     false);
         }
@@ -1285,7 +1293,7 @@
         if (h == null) {
             return false;
         }
-        if (sUseConcurrent) {
+        if (mUseConcurrent) {
             return findOrRemoveMessages(h, -1, null, null, 0, mMatchHandler, false);
         }
         synchronized (this) {
@@ -1304,7 +1312,7 @@
         if (h == null) {
             return;
         }
-        if (sUseConcurrent) {
+        if (mUseConcurrent) {
             findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObject, true);
             return;
         }
@@ -1355,7 +1363,7 @@
             return;
         }
 
-        if (sUseConcurrent) {
+        if (mUseConcurrent) {
             findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObjectEquals, true);
             return;
         }
@@ -1407,7 +1415,7 @@
             return;
         }
 
-        if (sUseConcurrent) {
+        if (mUseConcurrent) {
             findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObject, true);
             return;
         }
@@ -1470,7 +1478,7 @@
             return;
         }
 
-        if (sUseConcurrent) {
+        if (mUseConcurrent) {
             findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObjectEquals, true);
             return;
         }
@@ -1532,7 +1540,7 @@
             return;
         }
 
-        if (sUseConcurrent) {
+        if (mUseConcurrent) {
             findOrRemoveMessages(h, -1, object, null, 0, mMatchHandlerAndObject, true);
             return;
         }
@@ -1594,7 +1602,7 @@
             return;
         }
 
-        if (sUseConcurrent) {
+        if (mUseConcurrent) {
             findOrRemoveMessages(h, -1, object, null, 0, mMatchHandlerAndObjectEquals, true);
             return;
         }
@@ -1742,7 +1750,7 @@
 
     @NeverCompile
     void dump(Printer pw, String prefix, Handler h) {
-        if (sUseConcurrent) {
+        if (mUseConcurrent) {
             long now = SystemClock.uptimeMillis();
             int n = 0;
 
@@ -1803,7 +1811,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/IThermalHeadroomListener.aidl b/core/java/android/os/IThermalHeadroomListener.aidl
new file mode 100644
index 0000000..b2797d8
--- /dev/null
+++ b/core/java/android/os/IThermalHeadroomListener.aidl
@@ -0,0 +1,31 @@
+/*
+** 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.os;
+
+/**
+ * Listener for thermal headroom and threshold changes.
+ * This is mainly used by {@link android.os.PowerManager} to serve public thermal headoom related
+ * APIs.
+ * {@hide}
+ */
+oneway interface IThermalHeadroomListener {
+    /**
+     * Called when thermal headroom or thresholds changed.
+     */
+    void onHeadroomChange(in float headroom, in float forecastHeadroom,
+                                 in int forecastSeconds, in float[] thresholds);
+}
diff --git a/core/java/android/os/IThermalService.aidl b/core/java/android/os/IThermalService.aidl
index bcffa45..aa3bcfa 100644
--- a/core/java/android/os/IThermalService.aidl
+++ b/core/java/android/os/IThermalService.aidl
@@ -18,6 +18,7 @@
 
 import android.os.CoolingDevice;
 import android.os.IThermalEventListener;
+import android.os.IThermalHeadroomListener;
 import android.os.IThermalStatusListener;
 import android.os.Temperature;
 
@@ -116,4 +117,20 @@
      * @return thermal headroom for each thermal status
      */
     float[] getThermalHeadroomThresholds();
+
+    /**
+      * Register a listener for thermal headroom change.
+      * @param listener the {@link android.os.IThermalHeadroomListener} to be notified.
+      * @return true if registered successfully.
+      * {@hide}
+      */
+    boolean registerThermalHeadroomListener(in IThermalHeadroomListener listener);
+
+    /**
+      * Unregister a previously-registered listener for thermal headroom.
+      * @param listener the {@link android.os.IThermalHeadroomListener} to no longer be notified.
+      * @return true if unregistered successfully.
+      * {@hide}
+      */
+    boolean unregisterThermalHeadroomListener(in IThermalHeadroomListener listener);
 }
diff --git a/core/java/android/os/PowerComponents.java b/core/java/android/os/PowerComponents.java
index f4e3f3b..d116e07 100644
--- a/core/java/android/os/PowerComponents.java
+++ b/core/java/android/os/PowerComponents.java
@@ -439,8 +439,8 @@
                         }
                         final BatteryConsumer.Key key = builder.mData.layout.getKey(componentId,
                                 processState, screenState, powerState);
-                        builder.setConsumedPower(key, powerMah, model);
-                        builder.setUsageDurationMillis(key, durationMs);
+                        builder.addConsumedPower(key, powerMah, model);
+                        builder.addUsageDurationMillis(key, durationMs);
                         break;
                     }
                 }
@@ -468,6 +468,10 @@
             }
         }
 
+        /**
+         * @deprecated use {@link #addConsumedPower(BatteryConsumer.Key, double, int)}
+         */
+        @Deprecated
         @NonNull
         public Builder setConsumedPower(BatteryConsumer.Key key, double componentPower,
                 int powerModel) {
@@ -489,6 +493,10 @@
             return this;
         }
 
+        /**
+         * @deprecated use {@link #addUsageDurationMillis(BatteryConsumer.Key, long)}
+         */
+        @Deprecated
         @NonNull
         public Builder setUsageDurationMillis(BatteryConsumer.Key key,
                 long componentUsageDurationMillis) {
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 9d4ac29..07fded1 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -20,6 +20,7 @@
 import android.annotation.CallbackExecutor;
 import android.annotation.CurrentTimeMillisLong;
 import android.annotation.FlaggedApi;
+import android.annotation.FloatRange;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -40,6 +41,7 @@
 import android.util.proto.ProtoOutputStream;
 import android.view.Display;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.Preconditions;
 
 import java.lang.annotation.ElementType;
@@ -1199,10 +1201,12 @@
     /** We lazily initialize it.*/
     private PowerExemptionManager mPowerExemptionManager;
 
+    @GuardedBy("mThermalStatusListenerMap")
     private final ArrayMap<OnThermalStatusChangedListener, IThermalStatusListener>
-            mListenerMap = new ArrayMap<>();
-    private final Object mThermalHeadroomThresholdsLock = new Object();
-    private float[] mThermalHeadroomThresholds = null;
+            mThermalStatusListenerMap = new ArrayMap<>();
+    @GuardedBy("mThermalHeadroomListenerMap")
+    private final ArrayMap<OnThermalHeadroomChangedListener, IThermalHeadroomListener>
+            mThermalHeadroomListenerMap = new ArrayMap<>();
 
     /**
      * {@hide}
@@ -2689,15 +2693,59 @@
         void onThermalStatusChanged(@ThermalStatus int status);
     }
 
+    /**
+     * Listener passed to
+     * {@link PowerManager#addThermalHeadroomListener} and
+     * {@link PowerManager#removeThermalHeadroomListener}
+     * to notify caller of Thermal headroom or thresholds changes.
+     */
+    @FlaggedApi(Flags.FLAG_ALLOW_THERMAL_THRESHOLDS_CALLBACK)
+    public interface OnThermalHeadroomChangedListener {
+
+        /**
+         * Called when overall thermal headroom or headroom thresholds have significantly
+         * changed that requires action.
+         * <p>
+         * This may not be used to fully replace the {@link #getThermalHeadroom(int)} API as it will
+         * only notify on one of the conditions below that will significantly change one or both
+         * values of current headroom and headroom thresholds since previous callback:
+         *   1. thermal throttling events: when the skin temperature has cross any of the thresholds
+         *      and there isn't a previous callback in a short time ago with similar values.
+         *   2. skin temperature threshold change events: note that if the absolute °C threshold
+         *      values change in a way that does not significantly change the current headroom nor
+         *      headroom thresholds, it will not trigger any callback. The client should not
+         *      need to take action in such case since the difference from temperature vs threshold
+         *      hasn't changed.
+         * <p>
+         * By API version 36, it provides a forecast in the same call for developer's convenience
+         * based on a {@code forecastSeconds} defined by the device, which can be static or dynamic
+         * varied by OEM. Be aware that it will not notify on forecast temperature change but the
+         * events mentioned above. So periodically polling against {@link #getThermalHeadroom(int)}
+         * API should still be used to actively monitor temperature forecast in advance.
+         * <p>
+         * This serves as a more advanced option compared to thermal status listener, where the
+         * latter will only notify on thermal throttling events with status update.
+         *
+         * @param headroom current headroom
+         * @param forecastHeadroom forecasted headroom in future
+         * @param forecastSeconds how many seconds in the future used in forecast
+         * @param thresholds new headroom thresholds, see {@link #getThermalHeadroomThresholds()}
+         */
+        void onThermalHeadroomChanged(
+                @FloatRange(from = 0f) float headroom,
+                @FloatRange(from = 0f) float forecastHeadroom,
+                @IntRange(from = 0) int forecastSeconds,
+                @NonNull Map<@ThermalStatus Integer, Float> thresholds);
+    }
 
     /**
-     * This function adds a listener for thermal status change, listen call back will be
+     * This function adds a listener for thermal status change, listener callback will be
      * enqueued tasks on the main thread
      *
      * @param listener listener to be added,
      */
     public void addThermalStatusListener(@NonNull OnThermalStatusChangedListener listener) {
-        Objects.requireNonNull(listener, "listener cannot be null");
+        Objects.requireNonNull(listener, "Thermal status listener cannot be null");
         addThermalStatusListener(mContext.getMainExecutor(), listener);
     }
 
@@ -2709,29 +2757,31 @@
      */
     public void addThermalStatusListener(@NonNull @CallbackExecutor Executor executor,
             @NonNull OnThermalStatusChangedListener listener) {
-        Objects.requireNonNull(listener, "listener cannot be null");
-        Objects.requireNonNull(executor, "executor cannot be null");
-        Preconditions.checkArgument(!mListenerMap.containsKey(listener),
-                "Listener already registered: %s", listener);
-        IThermalStatusListener internalListener = new IThermalStatusListener.Stub() {
-            @Override
-            public void onStatusChange(int status) {
-                final long token = Binder.clearCallingIdentity();
-                try {
-                    executor.execute(() -> listener.onThermalStatusChanged(status));
-                } finally {
-                    Binder.restoreCallingIdentity(token);
+        Objects.requireNonNull(listener, "Thermal status listener cannot be null");
+        Objects.requireNonNull(executor, "Executor cannot be null");
+        synchronized (mThermalStatusListenerMap) {
+            Preconditions.checkArgument(!mThermalStatusListenerMap.containsKey(listener),
+                    "Thermal status listener already registered: %s", listener);
+            IThermalStatusListener internalListener = new IThermalStatusListener.Stub() {
+                @Override
+                public void onStatusChange(int status) {
+                    final long token = Binder.clearCallingIdentity();
+                    try {
+                        executor.execute(() -> listener.onThermalStatusChanged(status));
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
                 }
+            };
+            try {
+                if (mThermalService.registerThermalStatusListener(internalListener)) {
+                    mThermalStatusListenerMap.put(listener, internalListener);
+                } else {
+                    throw new RuntimeException("Thermal status listener failed to set");
+                }
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
             }
-        };
-        try {
-            if (mThermalService.registerThermalStatusListener(internalListener)) {
-                mListenerMap.put(listener, internalListener);
-            } else {
-                throw new RuntimeException("Listener failed to set");
-            }
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -2741,20 +2791,101 @@
      * @param listener listener to be removed
      */
     public void removeThermalStatusListener(@NonNull OnThermalStatusChangedListener listener) {
-        Objects.requireNonNull(listener, "listener cannot be null");
-        IThermalStatusListener internalListener = mListenerMap.get(listener);
-        Preconditions.checkArgument(internalListener != null, "Listener was not added");
-        try {
-            if (mThermalService.unregisterThermalStatusListener(internalListener)) {
-                mListenerMap.remove(listener);
-            } else {
-                throw new RuntimeException("Listener failed to remove");
+        Objects.requireNonNull(listener, "Thermal status listener cannot be null");
+        synchronized (mThermalStatusListenerMap) {
+            IThermalStatusListener internalListener = mThermalStatusListenerMap.get(listener);
+            Preconditions.checkArgument(internalListener != null,
+                    "Thermal status listener was not added");
+            try {
+                if (mThermalService.unregisterThermalStatusListener(internalListener)) {
+                    mThermalStatusListenerMap.remove(listener);
+                } else {
+                    throw new RuntimeException("Failed to unregister thermal status listener");
+                }
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
             }
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
         }
     }
 
+    /**
+     * This function adds a listener for thermal headroom change, listener callback will be
+     * enqueued tasks on the main thread
+     *
+     * @param listener listener to be added,
+     */
+    @FlaggedApi(Flags.FLAG_ALLOW_THERMAL_THRESHOLDS_CALLBACK)
+    public void addThermalHeadroomListener(@NonNull OnThermalHeadroomChangedListener listener) {
+        Objects.requireNonNull(listener, "Thermal headroom listener cannot be null");
+        addThermalHeadroomListener(mContext.getMainExecutor(), listener);
+    }
+
+    /**
+     * This function adds a listener for thermal headroom change.
+     *
+     * @param executor {@link Executor} to handle listener callback.
+     * @param listener listener to be added.
+     */
+    @FlaggedApi(Flags.FLAG_ALLOW_THERMAL_THRESHOLDS_CALLBACK)
+    public void addThermalHeadroomListener(@NonNull @CallbackExecutor Executor executor,
+            @NonNull OnThermalHeadroomChangedListener listener) {
+        Objects.requireNonNull(listener, "Thermal headroom listener cannot be null");
+        Objects.requireNonNull(executor, "Executor cannot be null");
+        synchronized (mThermalHeadroomListenerMap) {
+            Preconditions.checkArgument(!mThermalHeadroomListenerMap.containsKey(listener),
+                    "Thermal headroom listener already registered: %s", listener);
+            IThermalHeadroomListener internalListener = new IThermalHeadroomListener.Stub() {
+                @Override
+                public void onHeadroomChange(float headroom, float forecastHeadroom,
+                        int forecastSeconds, float[] thresholds)
+                        throws RemoteException {
+                    final Map<Integer, Float> map = convertThresholdsToMap(thresholds);
+                    final long token = Binder.clearCallingIdentity();
+                    try {
+                        executor.execute(() -> listener.onThermalHeadroomChanged(headroom,
+                                forecastHeadroom, forecastSeconds, map));
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
+                }
+            };
+            try {
+                if (mThermalService.registerThermalHeadroomListener(internalListener)) {
+                    mThermalHeadroomListenerMap.put(listener, internalListener);
+                } else {
+                    throw new RuntimeException("Thermal headroom listener failed to set");
+                }
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * This function removes a listener for Thermal headroom change
+     *
+     * @param listener listener to be removed
+     */
+    @FlaggedApi(Flags.FLAG_ALLOW_THERMAL_THRESHOLDS_CALLBACK)
+    public void removeThermalHeadroomListener(@NonNull OnThermalHeadroomChangedListener listener) {
+        Objects.requireNonNull(listener, "Thermal headroom listener cannot be null");
+        synchronized (mThermalHeadroomListenerMap) {
+            IThermalHeadroomListener internalListener = mThermalHeadroomListenerMap.get(listener);
+            Preconditions.checkArgument(internalListener != null,
+                    "Thermal headroom listener was not added");
+            try {
+                if (mThermalService.unregisterThermalHeadroomListener(internalListener)) {
+                    mThermalHeadroomListenerMap.remove(listener);
+                } else {
+                    throw new RuntimeException("Failed to unregister thermal status listener");
+                }
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+
     @CurrentTimeMillisLong
     private final AtomicLong mLastHeadroomUpdate = new AtomicLong(0L);
     private static final int MINIMUM_HEADROOM_TIME_MILLIS = 500;
@@ -2794,7 +2925,8 @@
      *         functionality or if this function is called significantly faster than once per
      *         second.
      */
-    public float getThermalHeadroom(@IntRange(from = 0, to = 60) int forecastSeconds) {
+    public @FloatRange(from = 0f) float getThermalHeadroom(
+            @IntRange(from = 0, to = 60) int forecastSeconds) {
         // Rate-limit calls into the thermal service
         long now = SystemClock.elapsedRealtime();
         long timeSinceLastUpdate = now - mLastHeadroomUpdate.get();
@@ -2839,9 +2971,11 @@
      * headroom of 0.75 will never come with {@link #THERMAL_STATUS_MODERATE} but lower, and 0.65
      * will never come with {@link #THERMAL_STATUS_LIGHT} but {@link #THERMAL_STATUS_NONE}.
      * <p>
-     * The returned map of thresholds will not change between calls to this function, so it's
-     * best to call this once on initialization. Modifying the result will not change the thresholds
-     * cached by the system, and a new call to the API will get a new copy.
+     * Starting at {@link android.os.Build.VERSION_CODES#BAKLAVA} the returned map of thresholds can
+     * change between calls to this function, one could use the new
+     * {@link #addThermalHeadroomListener(Executor, OnThermalHeadroomChangedListener)} API to
+     * register a listener and get callback for changes to thresholds.
+     * <p>
      *
      * @return map from each thermal status to its thermal headroom
      * @throws IllegalStateException if the thermal service is not ready
@@ -2850,24 +2984,22 @@
     @FlaggedApi(Flags.FLAG_ALLOW_THERMAL_HEADROOM_THRESHOLDS)
     public @NonNull Map<@ThermalStatus Integer, Float> getThermalHeadroomThresholds() {
         try {
-            synchronized (mThermalHeadroomThresholdsLock) {
-                if (mThermalHeadroomThresholds == null) {
-                    mThermalHeadroomThresholds = mThermalService.getThermalHeadroomThresholds();
-                }
-                final ArrayMap<Integer, Float> ret = new ArrayMap<>(THERMAL_STATUS_SHUTDOWN);
-                for (int status = THERMAL_STATUS_LIGHT; status <= THERMAL_STATUS_SHUTDOWN;
-                        status++) {
-                    if (!Float.isNaN(mThermalHeadroomThresholds[status])) {
-                        ret.put(status, mThermalHeadroomThresholds[status]);
-                    }
-                }
-                return ret;
-            }
+            return convertThresholdsToMap(mThermalService.getThermalHeadroomThresholds());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
+    private Map<@ThermalStatus Integer, Float> convertThresholdsToMap(final float[] thresholds) {
+        final ArrayMap<Integer, Float> ret = new ArrayMap<>(THERMAL_STATUS_SHUTDOWN);
+        for (int status = THERMAL_STATUS_LIGHT; status <= THERMAL_STATUS_SHUTDOWN; status++) {
+            if (!Float.isNaN(thresholds[status])) {
+                ret.put(status, thresholds[status]);
+            }
+        }
+        return ret;
+    }
+
     /**
      * If true, the doze component is not started until after the screen has been
      * turned off and the screen off animation has been performed.
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/os/UidBatteryConsumer.java b/core/java/android/os/UidBatteryConsumer.java
index f893739..976bfe4 100644
--- a/core/java/android/os/UidBatteryConsumer.java
+++ b/core/java/android/os/UidBatteryConsumer.java
@@ -210,12 +210,6 @@
             serializer.attribute(null, BatteryUsageStats.XML_ATTR_HIGHEST_DRAIN_PACKAGE,
                     packageWithHighestDrain);
         }
-        serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_TIME_IN_FOREGROUND,
-                getTimeInProcessStateMs(PROCESS_STATE_FOREGROUND));
-        serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_TIME_IN_BACKGROUND,
-                getTimeInProcessStateMs(PROCESS_STATE_BACKGROUND));
-        serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_TIME_IN_FOREGROUND_SERVICE,
-                getTimeInProcessStateMs(PROCESS_STATE_FOREGROUND_SERVICE));
         mPowerComponents.writeToXml(serializer);
         serializer.endTag(null, BatteryUsageStats.XML_TAG_UID);
     }
@@ -235,13 +229,6 @@
 
         consumerBuilder.setPackageWithHighestDrain(
                 parser.getAttributeValue(null, BatteryUsageStats.XML_ATTR_HIGHEST_DRAIN_PACKAGE));
-        consumerBuilder.setTimeInProcessStateMs(PROCESS_STATE_FOREGROUND,
-                parser.getAttributeLong(null, BatteryUsageStats.XML_ATTR_TIME_IN_FOREGROUND));
-        consumerBuilder.setTimeInProcessStateMs(PROCESS_STATE_BACKGROUND,
-                parser.getAttributeLong(null, BatteryUsageStats.XML_ATTR_TIME_IN_BACKGROUND));
-        consumerBuilder.setTimeInProcessStateMs(PROCESS_STATE_FOREGROUND_SERVICE,
-                parser.getAttributeLong(null,
-                        BatteryUsageStats.XML_ATTR_TIME_IN_FOREGROUND_SERVICE));
         while (!(eventType == XmlPullParser.END_TAG
                 && parser.getName().equals(BatteryUsageStats.XML_TAG_UID))
                 && eventType != XmlPullParser.END_DOCUMENT) {
@@ -335,7 +322,11 @@
         /**
          * Sets the duration, in milliseconds, that this UID was active in a particular process
          * state, such as foreground service.
+         *
+         * @deprecated time in process is now derived from the
+         * {@link BatteryConsumer#POWER_COMPONENT_BASE} duration
          */
+        @Deprecated
         @NonNull
         public Builder setTimeInProcessStateMs(@ProcessState int state, long timeInProcessStateMs) {
             Key key = getKey(POWER_COMPONENT_BASE, state);
diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl
index 1ab48a2..09b96da 100644
--- a/core/java/android/os/storage/IStorageManager.aidl
+++ b/core/java/android/os/storage/IStorageManager.aidl
@@ -181,6 +181,5 @@
      * device's useful lifetime remains. If no information is available, -1
      * is returned.
      */
-    @EnforcePermission("READ_PRIVILEGED_PHONE_STATE")
     int getInternalStorageRemainingLifetime() = 99;
 }
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/security/responsible_apis_flags.aconfig b/core/java/android/security/responsible_apis_flags.aconfig
index 5995760..66e1f38 100644
--- a/core/java/android/security/responsible_apis_flags.aconfig
+++ b/core/java/android/security/responsible_apis_flags.aconfig
@@ -67,6 +67,7 @@
     name: "aapm_api"
     namespace: "responsible_apis"
     description: "Android Advanced Protection Mode Service and Manager"
+    is_exported: true
     bug: "352420507"
     is_fixed_read_only: true
 }
diff --git a/core/java/android/service/notification/flags.aconfig b/core/java/android/service/notification/flags.aconfig
index 34e311f..d065939 100644
--- a/core/java/android/service/notification/flags.aconfig
+++ b/core/java/android/service/notification/flags.aconfig
@@ -65,4 +65,11 @@
    namespace: "systemui"
    description: "Allows the NAS to create and modify conversation notifications"
    bug: "373599715"
-}
\ No newline at end of file
+}
+
+flag {
+  name: "notification_regroup_on_classification"
+  namespace: "systemui"
+  description: "This flag controls regrouping after notification classification"
+  bug: "372775153"
+}
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/ImeBackAnimationController.java b/core/java/android/view/ImeBackAnimationController.java
index b801465..19e0913 100644
--- a/core/java/android/view/ImeBackAnimationController.java
+++ b/core/java/android/view/ImeBackAnimationController.java
@@ -33,6 +33,7 @@
 import android.view.animation.BackGestureInterpolator;
 import android.view.animation.Interpolator;
 import android.view.animation.PathInterpolator;
+import android.view.inputmethod.Flags;
 import android.view.inputmethod.ImeTracker;
 import android.window.BackEvent;
 import android.window.OnBackAnimationCallback;
@@ -142,9 +143,15 @@
             // control has been cancelled by the system. This can happen in multi-window mode for
             // example (i.e. split-screen or activity-embedding)
             notifyHideIme();
-            return;
+        } else {
+            startPostCommitAnim(/*hideIme*/ true);
         }
-        startPostCommitAnim(/*hideIme*/ true);
+        if (Flags.refactorInsetsController()) {
+            // Unregister all IME back callbacks so that back events are sent to the next callback
+            // even while the hide animation is playing
+            mInsetsController.getHost().getInputMethodManager().getImeOnBackInvokedDispatcher()
+                    .preliminaryClear();
+        }
     }
 
     private void setPreCommitProgress(float progress) {
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 25d2246..26ca813 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -1344,6 +1344,11 @@
             boolean fromPredictiveBack) {
         final boolean visible = layoutInsetsDuringAnimation == LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
 
+        if (Flags.refactorInsetsController() && !fromPredictiveBack && !visible
+                && (types & ime()) != 0 && (mRequestedVisibleTypes & ime()) != 0) {
+            // Clear IME back callbacks if a IME hide animation is requested
+            mHost.getInputMethodManager().getImeOnBackInvokedDispatcher().preliminaryClear();
+        }
         // Basically, we accept the requested visibilities from the upstream callers...
         setRequestedVisibleTypes(visible ? types : 0, types);
 
@@ -1921,6 +1926,14 @@
         final @InsetsType int requestedVisibleTypes =
                 (mRequestedVisibleTypes & ~mask) | (visibleTypes & mask);
         if (mRequestedVisibleTypes != requestedVisibleTypes) {
+            if (Flags.refactorInsetsController() && (mRequestedVisibleTypes & ime()) == 0
+                    && (requestedVisibleTypes & ime()) != 0) {
+                // In case the IME back callbacks have been preliminarily cleared before, let's
+                // reregister them. This can happen if an IME hide animation was interrupted and the
+                // IME is requested to be shown again.
+                getHost().getInputMethodManager().getImeOnBackInvokedDispatcher()
+                        .undoPreliminaryClear();
+            }
             ProtoLog.d(IME_INSETS_CONTROLLER, "Setting requestedVisibleTypes to %d (was %d)",
                     requestedVisibleTypes, mRequestedVisibleTypes);
             mRequestedVisibleTypes = requestedVisibleTypes;
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/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 5e5f33e..fe4f0cd 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()}.
      *
@@ -5460,26 +5492,6 @@
         }
     }
 
-    private static String getExpandedStateSymbolicName(int state) {
-        if (Flags.a11yExpansionStateApi()) {
-            switch (state) {
-                case EXPANDED_STATE_UNDEFINED:
-                    return "EXPANDED_STATE_UNDEFINED";
-                case EXPANDED_STATE_COLLAPSED:
-                    return "EXPANDED_STATE_COLLAPSED";
-                case EXPANDED_STATE_PARTIAL:
-                    return "EXPANDED_STATE_PARTIAL";
-                case EXPANDED_STATE_FULL:
-                    return "EXPANDED_STATE_FULL";
-                default:
-                    throw new IllegalArgumentException("Unknown expanded state: " + state);
-            }
-        } else {
-            // TODO(b/362782158) Remove when flag is removed.
-            return "";
-        }
-    }
-
     private static boolean canPerformRequestOverConnection(int connectionId,
             int windowId, long accessibilityNodeId) {
         final boolean hasWindowId = windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
@@ -5573,20 +5585,12 @@
         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/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 7dc77b1..73f9d9f 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -3667,6 +3667,14 @@
     }
 
     /**
+     * Returns the ImeOnBackInvokedDispatcher.
+     * @hide
+     */
+    public ImeOnBackInvokedDispatcher getImeOnBackInvokedDispatcher() {
+        return mImeDispatcher;
+    }
+
+    /**
      * Check the next served view if needs to start input.
      */
     @GuardedBy("mH")
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/ImeOnBackInvokedDispatcher.java b/core/java/android/window/ImeOnBackInvokedDispatcher.java
index bd01899..c67b9ca 100644
--- a/core/java/android/window/ImeOnBackInvokedDispatcher.java
+++ b/core/java/android/window/ImeOnBackInvokedDispatcher.java
@@ -203,6 +203,34 @@
         mImeCallbacks.remove(callback);
     }
 
+    /**
+     * Unregisters all callbacks on the receiving dispatcher but keeps a reference of the callbacks
+     * in case the clearance is reverted in
+     * {@link ImeOnBackInvokedDispatcher#undoPreliminaryClear()}.
+     */
+    public void preliminaryClear() {
+        // Unregister previously registered callbacks if there's any.
+        if (getReceivingDispatcher() != null) {
+            for (ImeOnBackInvokedCallback callback : mImeCallbacks) {
+                getReceivingDispatcher().unregisterOnBackInvokedCallback(callback);
+            }
+        }
+    }
+
+    /**
+     * Reregisters all callbacks on the receiving dispatcher that have previously been cleared by
+     * calling {@link ImeOnBackInvokedDispatcher#preliminaryClear()}. This can happen if an IME hide
+     * animation is interrupted causing the IME to reappear.
+     */
+    public void undoPreliminaryClear() {
+        if (getReceivingDispatcher() != null) {
+            for (ImeOnBackInvokedCallback callback : mImeCallbacks) {
+                getReceivingDispatcher().registerOnBackInvokedCallbackUnchecked(callback,
+                        callback.mPriority);
+            }
+        }
+    }
+
     /** Clears all registered callbacks on the instance. */
     public void clear() {
         // Unregister previously registered callbacks if there's any.
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/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 731d100..d39ecab 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -96,6 +96,16 @@
 }
 
 flag {
+    name: "enable_accessible_custom_headers"
+    namespace: "lse_desktop_experience"
+    description: "Enables a11y-friendly custom header input handling"
+    bug: "339302584"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "enable_app_header_with_task_density"
     namespace: "lse_desktop_experience"
     description: "Matches the App Header density to that of the app window, instead of SysUI's"
diff --git a/core/java/com/android/internal/compat/AndroidBuildClassifier.java b/core/java/com/android/internal/compat/AndroidBuildClassifier.java
index 364db06..19f8889 100644
--- a/core/java/com/android/internal/compat/AndroidBuildClassifier.java
+++ b/core/java/com/android/internal/compat/AndroidBuildClassifier.java
@@ -22,6 +22,7 @@
  * Platform private class for determining the type of Android build installed.
  *
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class AndroidBuildClassifier {
 
     public boolean isDebuggableBuild() {
diff --git a/core/java/com/android/internal/compat/ChangeReporter.java b/core/java/com/android/internal/compat/ChangeReporter.java
index f611571..f714098 100644
--- a/core/java/com/android/internal/compat/ChangeReporter.java
+++ b/core/java/com/android/internal/compat/ChangeReporter.java
@@ -42,6 +42,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class ChangeReporter {
     private static final String TAG = "CompatChangeReporter";
     private static final Function<Integer, Set<ChangeReport>> NEW_CHANGE_REPORT_SET =
diff --git a/core/java/com/android/internal/compat/CompatibilityChangeConfig.java b/core/java/com/android/internal/compat/CompatibilityChangeConfig.java
index 182dba7..8fd914ae 100644
--- a/core/java/com/android/internal/compat/CompatibilityChangeConfig.java
+++ b/core/java/com/android/internal/compat/CompatibilityChangeConfig.java
@@ -28,6 +28,7 @@
  * Parcelable containing compat config overrides for a given application.
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class CompatibilityChangeConfig implements Parcelable {
     private final ChangeConfig mChangeConfig;
 
diff --git a/core/java/com/android/internal/compat/CompatibilityChangeInfo.java b/core/java/com/android/internal/compat/CompatibilityChangeInfo.java
index 03fe455..505fd23 100644
--- a/core/java/com/android/internal/compat/CompatibilityChangeInfo.java
+++ b/core/java/com/android/internal/compat/CompatibilityChangeInfo.java
@@ -25,6 +25,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class CompatibilityChangeInfo implements Parcelable {
     private final long mChangeId;
     private final @Nullable String mName;
diff --git a/core/java/com/android/internal/compat/CompatibilityOverrideConfig.java b/core/java/com/android/internal/compat/CompatibilityOverrideConfig.java
index 9a02b7b..32206c9 100644
--- a/core/java/com/android/internal/compat/CompatibilityOverrideConfig.java
+++ b/core/java/com/android/internal/compat/CompatibilityOverrideConfig.java
@@ -28,6 +28,7 @@
  * Parcelable containing compat config overrides for a given application.
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class CompatibilityOverrideConfig implements Parcelable {
     public final Map<Long, PackageOverride> overrides;
 
diff --git a/core/java/com/android/internal/compat/CompatibilityOverridesByPackageConfig.java b/core/java/com/android/internal/compat/CompatibilityOverridesByPackageConfig.java
index 8652bb6..998b48a 100644
--- a/core/java/com/android/internal/compat/CompatibilityOverridesByPackageConfig.java
+++ b/core/java/com/android/internal/compat/CompatibilityOverridesByPackageConfig.java
@@ -26,6 +26,7 @@
  * Parcelable containing compat config overrides by application.
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class CompatibilityOverridesByPackageConfig implements Parcelable {
     public final Map<String, CompatibilityOverrideConfig> packageNameToOverrides;
 
diff --git a/core/java/com/android/internal/compat/CompatibilityOverridesToRemoveByPackageConfig.java b/core/java/com/android/internal/compat/CompatibilityOverridesToRemoveByPackageConfig.java
index b408d64..c0e2217 100644
--- a/core/java/com/android/internal/compat/CompatibilityOverridesToRemoveByPackageConfig.java
+++ b/core/java/com/android/internal/compat/CompatibilityOverridesToRemoveByPackageConfig.java
@@ -29,6 +29,7 @@
  * IDs.
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class CompatibilityOverridesToRemoveByPackageConfig implements Parcelable {
     public final Map<String, CompatibilityOverridesToRemoveConfig> packageNameToOverridesToRemove;
 
diff --git a/core/java/com/android/internal/compat/CompatibilityOverridesToRemoveConfig.java b/core/java/com/android/internal/compat/CompatibilityOverridesToRemoveConfig.java
index e85afef..10461ec 100644
--- a/core/java/com/android/internal/compat/CompatibilityOverridesToRemoveConfig.java
+++ b/core/java/com/android/internal/compat/CompatibilityOverridesToRemoveConfig.java
@@ -30,6 +30,7 @@
  * <p>This class is separate from CompatibilityOverrideConfig since we only need change IDs.
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class CompatibilityOverridesToRemoveConfig implements Parcelable {
     public final Set<Long> changeIds;
 
diff --git a/core/java/com/android/internal/compat/OverrideAllowedState.java b/core/java/com/android/internal/compat/OverrideAllowedState.java
index e408be2..f018c3a 100644
--- a/core/java/com/android/internal/compat/OverrideAllowedState.java
+++ b/core/java/com/android/internal/compat/OverrideAllowedState.java
@@ -27,6 +27,7 @@
 /**
  * This class contains all the possible override allowed states.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class OverrideAllowedState implements Parcelable {
     @IntDef({
             ALLOWED,
diff --git a/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java
index a69d2e4..3303d87 100644
--- a/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java
+++ b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java
@@ -15,6 +15,12 @@
  */
 package com.android.internal.ravenwood;
 
+import static android.os.Build.VERSION_CODES.S;
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
+
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
+import android.compat.annotation.EnabledAfter;
 import android.ravenwood.annotation.RavenwoodKeepWholeClass;
 import android.ravenwood.annotation.RavenwoodRedirect;
 import android.ravenwood.annotation.RavenwoodRedirectionClass;
@@ -78,4 +84,20 @@
     public String getRavenwoodRuntimePath() {
         throw notSupportedOnDevice();
     }
+
+    /** @hide */
+    public static class CompatIdsForTest {
+        // Enabled by default
+        @ChangeId
+        public static final long TEST_COMPAT_ID_1 = 368131859L;
+
+        @Disabled
+        @ChangeId public static final long TEST_COMPAT_ID_2 = 368131701L;
+
+        @EnabledAfter(targetSdkVersion = S)
+        @ChangeId public static final long TEST_COMPAT_ID_3 = 368131659L;
+
+        @EnabledAfter(targetSdkVersion = UPSIDE_DOWN_CAKE)
+        @ChangeId public static final long TEST_COMPAT_ID_4 = 368132057L;
+    }
 }
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_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/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/ic_zen_mode_type_unknown.xml b/core/res/res/drawable/ic_zen_mode_icon_star_badge.xml
similarity index 100%
rename from core/res/res/drawable/ic_zen_mode_type_unknown.xml
rename to core/res/res/drawable/ic_zen_mode_icon_star_badge.xml
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/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 e23e665..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. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index fec8bbb..aa08d5e 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5647,7 +5647,6 @@
   <java-symbol type="drawable" name="ic_zen_mode_type_schedule_calendar" />
   <java-symbol type="drawable" name="ic_zen_mode_type_schedule_time" />
   <java-symbol type="drawable" name="ic_zen_mode_type_theater" />
-  <java-symbol type="drawable" name="ic_zen_mode_type_unknown" />
   <java-symbol type="drawable" name="ic_zen_mode_type_special_dnd" />
 
   <!-- System notification for background user sound -->
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/os/PowerManagerTest.java b/core/tests/coretests/src/android/os/PowerManagerTest.java
index 3b27fc0..e4e965f 100644
--- a/core/tests/coretests/src/android/os/PowerManagerTest.java
+++ b/core/tests/coretests/src/android/os/PowerManagerTest.java
@@ -21,6 +21,8 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.timeout;
@@ -61,9 +63,13 @@
     private UiDevice mUiDevice;
     private Executor mExec = Executors.newSingleThreadExecutor();
     @Mock
-    private PowerManager.OnThermalStatusChangedListener mListener1;
+    private PowerManager.OnThermalStatusChangedListener mStatusListener1;
     @Mock
-    private PowerManager.OnThermalStatusChangedListener mListener2;
+    private PowerManager.OnThermalStatusChangedListener mStatusListener2;
+    @Mock
+    private PowerManager.OnThermalHeadroomChangedListener mHeadroomListener1;
+    @Mock
+    private PowerManager.OnThermalHeadroomChangedListener mHeadroomListener2;
     private static final long CALLBACK_TIMEOUT_MILLI_SEC = 5000;
     private native Parcel nativeObtainPowerSaveStateParcel(boolean batterySaverEnabled,
             boolean globalBatterySaverEnabled, int locationMode, int soundTriggerMode,
@@ -245,53 +251,90 @@
         // Initial override status is THERMAL_STATUS_NONE
         int status = PowerManager.THERMAL_STATUS_NONE;
         // Add listener1
-        mPm.addThermalStatusListener(mExec, mListener1);
-        verify(mListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+        mPm.addThermalStatusListener(mExec, mStatusListener1);
+        verify(mStatusListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
                 .times(1)).onThermalStatusChanged(status);
-        reset(mListener1);
+        reset(mStatusListener1);
         status = PowerManager.THERMAL_STATUS_SEVERE;
         mUiDevice.executeShellCommand("cmd thermalservice override-status "
                 + Integer.toString(status));
-        verify(mListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+        verify(mStatusListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
                 .times(1)).onThermalStatusChanged(status);
-        reset(mListener1);
+        reset(mStatusListener1);
         // Add listener1 again
         try {
-            mPm.addThermalStatusListener(mListener1);
+            mPm.addThermalStatusListener(mStatusListener1);
             fail("Expected exception not thrown");
         } catch (IllegalArgumentException expectedException) {
         }
         // Add listener2 on main thread.
-        mPm.addThermalStatusListener(mListener2);
-        verify(mListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+        mPm.addThermalStatusListener(mStatusListener2);
+        verify(mStatusListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
             .times(1)).onThermalStatusChanged(status);
-        reset(mListener2);
+        reset(mStatusListener2);
         status = PowerManager.THERMAL_STATUS_MODERATE;
         mUiDevice.executeShellCommand("cmd thermalservice override-status "
                 + Integer.toString(status));
-        verify(mListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+        verify(mStatusListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
                 .times(1)).onThermalStatusChanged(status);
-        verify(mListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+        verify(mStatusListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
                 .times(1)).onThermalStatusChanged(status);
-        reset(mListener1);
-        reset(mListener2);
+        reset(mStatusListener1);
+        reset(mStatusListener2);
         // Remove listener1
-        mPm.removeThermalStatusListener(mListener1);
+        mPm.removeThermalStatusListener(mStatusListener1);
         // Remove listener1 again
         try {
-            mPm.removeThermalStatusListener(mListener1);
+            mPm.removeThermalStatusListener(mStatusListener1);
             fail("Expected exception not thrown");
         } catch (IllegalArgumentException expectedException) {
         }
         status = PowerManager.THERMAL_STATUS_LIGHT;
         mUiDevice.executeShellCommand("cmd thermalservice override-status "
                 + Integer.toString(status));
-        verify(mListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+        verify(mStatusListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
                 .times(0)).onThermalStatusChanged(status);
-        verify(mListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+        verify(mStatusListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
                 .times(1)).onThermalStatusChanged(status);
     }
 
+    /**
+     * Confirm that we can add/remove thermal headroom listener.
+     */
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ALLOW_THERMAL_THRESHOLDS_CALLBACK)
+    public void testThermalHeadroomCallback() throws Exception {
+        float headroom = mPm.getThermalHeadroom(0);
+        // If the device doesn't support thermal headroom, return early
+        if (Float.isNaN(headroom)) {
+            return;
+        }
+        // Add listener1
+        mPm.addThermalHeadroomListener(mExec, mHeadroomListener1);
+        verify(mHeadroomListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+                .times(1)).onThermalHeadroomChanged(anyInt(), anyInt(), anyInt(), any());
+        reset(mHeadroomListener1);
+        // Add listener1 again
+        try {
+            mPm.addThermalHeadroomListener(mHeadroomListener1);
+            fail("Expected exception not thrown");
+        } catch (IllegalArgumentException expectedException) {
+        }
+        // Add listener2 on main thread.
+        mPm.addThermalHeadroomListener(mHeadroomListener2);
+        verify(mHeadroomListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+                .times(1)).onThermalHeadroomChanged(anyInt(), anyInt(), anyInt(), any());
+        reset(mHeadroomListener2);
+        // Remove listener1
+        mPm.removeThermalHeadroomListener(mHeadroomListener1);
+        // Remove listener1 again
+        try {
+            mPm.removeThermalHeadroomListener(mHeadroomListener1);
+            fail("Expected exception not thrown");
+        } catch (IllegalArgumentException expectedException) {
+        }
+    }
+
     @Test
     public void testGetThermalHeadroom() throws Exception {
         float headroom = mPm.getThermalHeadroom(0);
diff --git a/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java b/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java
index 00ffda8..a47a3e0 100644
--- a/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java
+++ b/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java
@@ -161,7 +161,7 @@
         mBackAnimationController.onBackInvoked();
         // verify that InputMethodManager#notifyImeHidden is called (which is the case whenever
         // getInputMethodManager is called from ImeBackAnimationController)
-        verify(mViewRootInsetsControllerHost, times(1)).getInputMethodManager();
+        verify(mViewRootInsetsControllerHost, times(2)).getInputMethodManager();
         // verify that ImeBackAnimationController does not take control over IME insets
         verify(mInsetsController, never()).controlWindowInsetsAnimation(anyInt(), any(), any(),
                 anyBoolean(), anyLong(), any(), anyInt(), anyBoolean());
@@ -180,7 +180,7 @@
         mBackAnimationController.onBackInvoked();
         // verify that InputMethodManager#notifyImeHidden is called (which is the case whenever
         // getInputMethodManager is called from ImeBackAnimationController)
-        verify(mViewRootInsetsControllerHost, times(1)).getInputMethodManager();
+        verify(mViewRootInsetsControllerHost, times(2)).getInputMethodManager();
         // verify that ImeBackAnimationController does not take control over IME insets
         verify(mInsetsController, never()).controlWindowInsetsAnimation(anyInt(), any(), any(),
                 anyBoolean(), anyLong(), any(), anyInt(), anyBoolean());
@@ -300,7 +300,7 @@
             mBackAnimationController.onBackInvoked();
             // verify that InputMethodManager#notifyImeHidden is called (which is the case whenever
             // getInputMethodManager is called from ImeBackAnimationController)
-            verify(mViewRootInsetsControllerHost, times(1)).getInputMethodManager();
+            verify(mViewRootInsetsControllerHost, times(2)).getInputMethodManager();
         });
     }
 
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/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/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/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/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/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 706a678..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
@@ -779,7 +779,8 @@
             ShellTaskOrganizer shellTaskOrganizer,
             ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
             ReturnToDragStartAnimator returnToDragStartAnimator,
-            @DynamicOverride DesktopRepository desktopRepository) {
+            @DynamicOverride DesktopRepository desktopRepository,
+            DesktopModeEventLogger desktopModeEventLogger) {
         return new DesktopTilingDecorViewModel(
                 context,
                 displayController,
@@ -789,7 +790,8 @@
                 shellTaskOrganizer,
                 toggleResizeDesktopTaskTransitionHandler,
                 returnToDragStartAnimator,
-                desktopRepository
+                desktopRepository,
+                desktopModeEventLogger
         );
     }
 
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/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/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 6bf92f6..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,7 +347,7 @@
                 : startRotation - endRotation;
         if (delta != ROTATION_0) {
             mPipTransitionState.setInFixedRotation(true);
-            handleBoundsTypeFixedRotation(pipChange, pipActivityChange, endRotation);
+            handleBoundsEnterFixedRotation(pipChange, pipActivityChange, endRotation);
         }
 
         prepareConfigAtEndActivity(startTransaction, finishTransaction, pipChange,
@@ -399,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
@@ -414,15 +412,15 @@
         }
 
         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());
         }
 
@@ -459,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();
@@ -492,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,
@@ -503,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.
@@ -544,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;
     }
@@ -717,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
     //
@@ -754,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/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 17e3dd2..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;
@@ -468,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));
     }
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/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/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/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 5626717..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
@@ -1307,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/Android.bp b/libs/hwui/Android.bp
index b71abdc..fcb7efc 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -580,6 +580,7 @@
         "utils/Color.cpp",
         "utils/LinearAllocator.cpp",
         "utils/StringUtils.cpp",
+        "utils/StatsUtils.cpp",
         "utils/TypefaceUtils.cpp",
         "utils/VectorDrawableUtils.cpp",
         "AnimationContext.cpp",
diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp
index e074a27..a9a5db8 100644
--- a/libs/hwui/hwui/ImageDecoder.cpp
+++ b/libs/hwui/hwui/ImageDecoder.cpp
@@ -27,8 +27,8 @@
 #include <SkColorSpace.h>
 #include <SkColorType.h>
 #include <SkEncodedOrigin.h>
-#include <SkImageInfo.h>
 #include <SkGainmapInfo.h>
+#include <SkImageInfo.h>
 #include <SkMatrix.h>
 #include <SkPaint.h>
 #include <SkPngChunkReader.h>
@@ -43,6 +43,8 @@
 
 #include <memory>
 
+#include "modules/skcms/src/skcms_public.h"
+
 using namespace android;
 
 sk_sp<SkColorSpace> ImageDecoder::getDefaultColorSpace() const {
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/libs/hwui/jni/BitmapFactory.cpp b/libs/hwui/jni/BitmapFactory.cpp
index 49a7f73..8b43f1d 100644
--- a/libs/hwui/jni/BitmapFactory.cpp
+++ b/libs/hwui/jni/BitmapFactory.cpp
@@ -10,6 +10,7 @@
 #include <stdint.h>
 #include <stdio.h>
 #include <sys/stat.h>
+#include <utils/StatsUtils.h>
 
 #include <memory>
 
@@ -630,6 +631,7 @@
         }
         bitmap::reinitBitmap(env, javaBitmap, outputBitmap.info(), isPremultiplied);
         outputBitmap.notifyPixelsChanged();
+        uirenderer::logBitmapDecode(*reuseBitmap);
         // If a java bitmap was passed in for reuse, pass it back
         return javaBitmap;
     }
@@ -650,6 +652,7 @@
             }
         }
 
+        uirenderer::logBitmapDecode(*hardwareBitmap);
         return bitmap::createBitmap(env, hardwareBitmap.release(), bitmapCreateFlags,
                 ninePatchChunk, ninePatchInsets, -1);
     }
@@ -659,6 +662,7 @@
         heapBitmap->setGainmap(std::move(gainmap));
     }
 
+    uirenderer::logBitmapDecode(*heapBitmap);
     // now create the java bitmap
     return bitmap::createBitmap(env, heapBitmap, bitmapCreateFlags, ninePatchChunk, ninePatchInsets,
                                 -1);
diff --git a/libs/hwui/jni/BitmapRegionDecoder.cpp b/libs/hwui/jni/BitmapRegionDecoder.cpp
index f7e8e07..5ffd5b9 100644
--- a/libs/hwui/jni/BitmapRegionDecoder.cpp
+++ b/libs/hwui/jni/BitmapRegionDecoder.cpp
@@ -19,6 +19,7 @@
 #include <HardwareBitmapUploader.h>
 #include <androidfw/Asset.h>
 #include <sys/stat.h>
+#include <utils/StatsUtils.h>
 
 #include <memory>
 
@@ -376,6 +377,7 @@
             recycledBitmap->setGainmap(std::move(gainmap));
         }
         bitmap::reinitBitmap(env, javaBitmap, recycledBitmap->info(), !requireUnpremul);
+        uirenderer::logBitmapDecode(*recycledBitmap);
         return javaBitmap;
     }
 
@@ -392,12 +394,14 @@
                 hardwareBitmap->setGainmap(std::move(gm));
             }
         }
+        uirenderer::logBitmapDecode(*hardwareBitmap);
         return bitmap::createBitmap(env, hardwareBitmap.release(), bitmapCreateFlags);
     }
     Bitmap* heapBitmap = heapAlloc.getStorageObjAndReset();
     if (hasGainmap && heapBitmap != nullptr) {
         heapBitmap->setGainmap(std::move(gainmap));
     }
+    uirenderer::logBitmapDecode(*heapBitmap);
     return android::bitmap::createBitmap(env, heapBitmap, bitmapCreateFlags);
 }
 
diff --git a/libs/hwui/jni/ImageDecoder.cpp b/libs/hwui/jni/ImageDecoder.cpp
index aebc4db..90fd3d8 100644
--- a/libs/hwui/jni/ImageDecoder.cpp
+++ b/libs/hwui/jni/ImageDecoder.cpp
@@ -37,6 +37,7 @@
 #include <hwui/Bitmap.h>
 #include <hwui/ImageDecoder.h>
 #include <sys/stat.h>
+#include <utils/StatsUtils.h>
 
 #include "Bitmap.h"
 #include "BitmapFactory.h"
@@ -485,6 +486,7 @@
                         hwBitmap->setGainmap(std::move(gm));
                     }
                 }
+                uirenderer::logBitmapDecode(*hwBitmap);
                 return bitmap::createBitmap(env, hwBitmap.release(), bitmapCreateFlags,
                                             ninePatchChunk, ninePatchInsets);
             }
@@ -498,6 +500,8 @@
 
         nativeBitmap->setImmutable();
     }
+
+    uirenderer::logBitmapDecode(*nativeBitmap);
     return bitmap::createBitmap(env, nativeBitmap.release(), bitmapCreateFlags, ninePatchChunk,
                                 ninePatchInsets);
 }
diff --git a/libs/hwui/libhwui.map.txt b/libs/hwui/libhwui.map.txt
index 2414299..b559194 100644
--- a/libs/hwui/libhwui.map.txt
+++ b/libs/hwui/libhwui.map.txt
@@ -67,6 +67,7 @@
       SkFILEStream::SkFILEStream*;
       SkImageInfo::*;
       SkMemoryStream::SkMemoryStream*;
+      android::uirenderer::logBitmapDecode*;
     };
   local:
     *;
diff --git a/libs/hwui/utils/StatsUtils.cpp b/libs/hwui/utils/StatsUtils.cpp
new file mode 100644
index 0000000..5c4027e
--- /dev/null
+++ b/libs/hwui/utils/StatsUtils.cpp
@@ -0,0 +1,102 @@
+/*
+ * 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.
+ */
+
+#ifdef __ANDROID__
+#include <dlfcn.h>
+#include <log/log.h>
+#include <statslog_hwui.h>
+#include <statssocket_lazy.h>
+#include <utils/Errors.h>
+
+#include <mutex>
+#endif
+
+#include <unistd.h>
+
+#include "StatsUtils.h"
+
+namespace android {
+namespace uirenderer {
+
+#ifdef __ANDROID__
+
+namespace {
+
+int32_t toStatsColorSpaceTransfer(skcms_TFType transferType) {
+    switch (transferType) {
+        case skcms_TFType_sRGBish:
+            return stats::IMAGE_DECODED__COLOR_SPACE_TRANSFER__COLOR_SPACE_TRANSFER_SRGBISH;
+        case skcms_TFType_PQish:
+            return stats::IMAGE_DECODED__COLOR_SPACE_TRANSFER__COLOR_SPACE_TRANSFER_PQISH;
+        case skcms_TFType_HLGish:
+            return stats::IMAGE_DECODED__COLOR_SPACE_TRANSFER__COLOR_SPACE_TRANSFER_HLGISH;
+        default:
+            return stats::IMAGE_DECODED__COLOR_SPACE_TRANSFER__COLOR_SPACE_TRANSFER_UNKNOWN;
+    }
+}
+
+int32_t toStatsBitmapFormat(SkColorType type) {
+    switch (type) {
+        case kAlpha_8_SkColorType:
+            return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_A_8;
+        case kRGB_565_SkColorType:
+            return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_RGB_565;
+        case kN32_SkColorType:
+            return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_ARGB_8888;
+        case kRGBA_F16_SkColorType:
+            return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_RGBA_F16;
+        case kRGBA_1010102_SkColorType:
+            return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_RGBA_1010102;
+        default:
+            return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_UNKNOWN;
+    }
+}
+
+}  // namespace
+
+#endif
+
+void logBitmapDecode(const SkImageInfo& info, bool hasGainmap) {
+#ifdef __ANDROID__
+
+    if (!statssocket::lazy::IsAvailable()) {
+        std::once_flag once;
+        std::call_once(once, []() { ALOGD("libstatssocket not available, dropping stats"); });
+        return;
+    }
+
+    skcms_TFType tfnType = skcms_TFType_Invalid;
+
+    if (info.colorSpace()) {
+        skcms_TransferFunction tfn;
+        info.colorSpace()->transferFn(&tfn);
+        tfnType = skcms_TransferFunction_getType(&tfn);
+    }
+
+    auto status =
+            stats::stats_write(uirenderer::stats::IMAGE_DECODED, static_cast<int32_t>(getuid()),
+                               uirenderer::toStatsColorSpaceTransfer(tfnType), hasGainmap,
+                               uirenderer::toStatsBitmapFormat(info.colorType()));
+    ALOGW_IF(status != OK, "Image decoding logging dropped!");
+#endif
+}
+
+void logBitmapDecode(const Bitmap& bitmap) {
+    logBitmapDecode(bitmap.info(), bitmap.hasGainmap());
+}
+
+}  // namespace uirenderer
+}  // namespace android
diff --git a/libs/hwui/utils/StatsUtils.h b/libs/hwui/utils/StatsUtils.h
new file mode 100644
index 0000000..0c247014
--- /dev/null
+++ b/libs/hwui/utils/StatsUtils.h
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <SkBitmap.h>
+#include <SkColorSpace.h>
+#include <SkColorType.h>
+#include <cutils/compiler.h>
+#include <hwui/Bitmap.h>
+
+namespace android {
+namespace uirenderer {
+
+ANDROID_API void logBitmapDecode(const SkImageInfo& info, bool hasGainmap);
+
+ANDROID_API void logBitmapDecode(const Bitmap& bitmap);
+
+}  // namespace uirenderer
+}  // namespace android
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/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/TvInputServiceExtensionManager.java b/media/java/android/media/tv/TvInputServiceExtensionManager.java
index 0e98488..c514f6e 100644
--- a/media/java/android/media/tv/TvInputServiceExtensionManager.java
+++ b/media/java/android/media/tv/TvInputServiceExtensionManager.java
@@ -17,13 +17,17 @@
 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;
@@ -33,10 +37,14 @@
 
 
 /**
+ * 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 class TvInputServiceExtensionManager {
+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.";
@@ -54,404 +62,608 @@
     private static final String ANALOG_PACKAGE = "android.media.tv.extension.analog.";
     private static final String TUNE_PACKAGE = "android.media.tv.extension.tune.";
 
-    /** Register binder returns success when it abides standardized interface structure */
-    public static final int REGISTER_SUCCESS = 0;
-    /** Register binder returns fail when the extension name is not in the standardization list */
-    public static final int REGISTER_FAIL_NAME_NOT_STANDARDIZED = 1;
-    /** Register binder returns fail when the IBinder does not implement standardized interface */
-    public static final int REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED = 2;
-    /** Register binder returns fail when remote server not available */
-    public static final int REGISTER_FAIL_REMOTE_EXCEPTION = 3;
+    /** @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 BROADCAST_TIME = TIME_PACKAGE + "BroadcastTime";
+    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";
 
@@ -506,7 +718,7 @@
             IVBI_RATING_LISTENER,
             IPROGRAM_INFO,
             IPROGRAM_INFO_LISTENER,
-            BROADCAST_TIME,
+            IBROADCAST_TIME,
             IDATA_SERVICE_SIGNAL_INFO,
             IDATA_SERVICE_SIGNAL_INFO_LISTENER,
             ITELETEXT_PAGE_SUB_CODE,
@@ -586,7 +798,8 @@
      *
      * @hide
      */
-    public int registerExtensionIBinder(@NonNull String extensionName,
+    @RegisterResult
+    public int registerExtensionIBinder(@StandardizedExtensionName @NonNull String extensionName,
             @NonNull IBinder binder) {
         if (!checkIsStandardizedInterfaces(extensionName)) {
             return REGISTER_FAIL_NAME_NOT_STANDARDIZED;
diff --git a/native/android/tests/thermal/NativeThermalUnitTest.cpp b/native/android/tests/thermal/NativeThermalUnitTest.cpp
index 6d6861a..4e319fc 100644
--- a/native/android/tests/thermal/NativeThermalUnitTest.cpp
+++ b/native/android/tests/thermal/NativeThermalUnitTest.cpp
@@ -67,6 +67,14 @@
     MOCK_METHOD(Status, getThermalHeadroomThresholds, (::std::vector<float> * _aidl_return),
                 (override));
     MOCK_METHOD(IBinder*, onAsBinder, (), (override));
+    MOCK_METHOD(Status, registerThermalHeadroomListener,
+                (const ::android::sp<::android::os::IThermalHeadroomListener>& listener,
+                 bool* _aidl_return),
+                (override));
+    MOCK_METHOD(Status, unregisterThermalHeadroomListener,
+                (const ::android::sp<::android::os::IThermalHeadroomListener>& listener,
+                 bool* _aidl_return),
+                (override));
 };
 
 class NativeThermalUnitTest : public Test {
diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp
index 0fb3049..23dd9b7 100644
--- a/native/graphics/jni/Android.bp
+++ b/native/graphics/jni/Android.bp
@@ -48,7 +48,9 @@
         "libhwui_internal_headers",
     ],
 
-    static_libs: ["libarect"],
+    static_libs: [
+        "libarect",
+    ],
 
     host_supported: true,
     target: {
@@ -60,6 +62,11 @@
             shared_libs: [
                 "libandroid",
             ],
+            static_libs: [
+                "libstatslog_hwui",
+                "libstatspull_lazy",
+                "libstatssocket_lazy",
+            ],
             version_script: "libjnigraphics.map.txt",
         },
         host: {
diff --git a/native/graphics/jni/imagedecoder.cpp b/native/graphics/jni/imagedecoder.cpp
index e18b4a9..cb95bcf 100644
--- a/native/graphics/jni/imagedecoder.cpp
+++ b/native/graphics/jni/imagedecoder.cpp
@@ -14,18 +14,9 @@
  * limitations under the License.
  */
 
-#include "aassetstreamadaptor.h"
-
-#include <android/asset_manager.h>
-#include <android/bitmap.h>
-#include <android/data_space.h>
-#include <android/imagedecoder.h>
 #include <MimeType.h>
-#include <android/rect.h>
-#include <hwui/ImageDecoder.h>
-#include <log/log.h>
-#include <SkAndroidCodec.h>
 #include <SkAlphaType.h>
+#include <SkAndroidCodec.h>
 #include <SkCodec.h>
 #include <SkCodecAnimation.h>
 #include <SkColorSpace.h>
@@ -35,14 +26,24 @@
 #include <SkRefCnt.h>
 #include <SkSize.h>
 #include <SkStream.h>
-#include <utils/Color.h>
-
+#include <android/asset_manager.h>
+#include <android/bitmap.h>
+#include <android/data_space.h>
+#include <android/imagedecoder.h>
+#include <android/rect.h>
 #include <fcntl.h>
-#include <limits>
-#include <optional>
+#include <hwui/ImageDecoder.h>
+#include <log/log.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
+#include <utils/Color.h>
+#include <utils/StatsUtils.h>
+
+#include <limits>
+#include <optional>
+
+#include "aassetstreamadaptor.h"
 
 using namespace android;
 
@@ -400,9 +401,7 @@
     return info.minRowBytes();
 }
 
-int AImageDecoder_decodeImage(AImageDecoder* decoder,
-                              void* pixels, size_t stride,
-                              size_t size) {
+int AImageDecoder_decodeImage(AImageDecoder* decoder, void* pixels, size_t stride, size_t size) {
     if (!decoder || !pixels || !stride) {
         return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
     }
@@ -419,7 +418,13 @@
         return ANDROID_IMAGE_DECODER_FINISHED;
     }
 
-    return ResultToErrorCode(imageDecoder->decode(pixels, stride));
+    const auto result = ResultToErrorCode(imageDecoder->decode(pixels, stride));
+
+    if (result == ANDROID_IMAGE_DECODER_SUCCESS) {
+        uirenderer::logBitmapDecode(imageDecoder->getOutputInfo(), false);
+    }
+
+    return result;
 }
 
 void AImageDecoder_delete(AImageDecoder* decoder) {
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..129e47f 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) {
@@ -358,7 +383,7 @@
      *
      * <p>If monitoring a package supporting explicit health check, at the end of the monitoring
      * duration if {@link #onHealthCheckPassed} was never called,
-     * {@link PackageHealthObserver#execute} will be called as if the package failed.
+     * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} will be called as if the package failed.
      *
      * <p>If {@code observer} is already monitoring a package in {@code packageNames},
      * the monitoring window of that package will be reset to {@code durationMs} and the health
@@ -521,8 +546,8 @@
                                 maybeExecute(currentObserverToNotify, versionedPackage,
                                         failureReason, currentObserverImpact, mitigationCount);
                             } else {
-                                currentObserverToNotify.execute(versionedPackage,
-                                        failureReason, mitigationCount);
+                                currentObserverToNotify.onExecuteHealthCheckMitigation(
+                                        versionedPackage, failureReason, mitigationCount);
                             }
                         }
                     }
@@ -557,7 +582,8 @@
                 maybeExecute(currentObserverToNotify, failingPackage, failureReason,
                         currentObserverImpact, /*mitigationCount=*/ 1);
             } else {
-                currentObserverToNotify.execute(failingPackage,  failureReason, 1);
+                currentObserverToNotify.onExecuteHealthCheckMitigation(failingPackage,
+                        failureReason, 1);
             }
         }
     }
@@ -571,7 +597,8 @@
             synchronized (mLock) {
                 mLastMitigation = mSystemClock.uptimeMillis();
             }
-            currentObserverToNotify.execute(versionedPackage, failureReason, mitigationCount);
+            currentObserverToNotify.onExecuteHealthCheckMitigation(versionedPackage, failureReason,
+                    mitigationCount);
         }
     }
 
@@ -633,12 +660,12 @@
                         currentObserverInternal.setBootMitigationCount(
                                 currentObserverMitigationCount);
                         saveAllObserversBootMitigationCountToMetadata(METADATA_FILE);
-                        currentObserverToNotify.executeBootLoopMitigation(
+                        currentObserverToNotify.onExecuteBootLoopMitigation(
                                 currentObserverMitigationCount);
                     } else {
                         mBootThreshold.setMitigationCount(mitigationCount);
                         mBootThreshold.saveMitigationCountToMetadata();
-                        currentObserverToNotify.executeBootLoopMitigation(mitigationCount);
+                        currentObserverToNotify.onExecuteBootLoopMitigation(mitigationCount);
                     }
                 }
             }
@@ -724,7 +751,8 @@
         return mPackagesExemptFromImpactLevelThreshold;
     }
 
-    /** Possible severity values of the user impact of a {@link PackageHealthObserver#execute}.
+    /** Possible severity values of the user impact of a
+     * {@link PackageHealthObserver#onExecuteHealthCheckMitigation}.
      * @hide
      */
     @Retention(SOURCE)
@@ -772,7 +800,7 @@
          *
          *
          * @return any one of {@link PackageHealthObserverImpact} to express the impact
-         * to the user on {@link #execute}
+         * to the user on {@link #onExecuteHealthCheckMitigation}
          */
         @PackageHealthObserverImpact int onHealthCheckFailed(
                 @Nullable VersionedPackage versionedPackage,
@@ -789,7 +817,7 @@
          *                        (including this time).
          * @return {@code true} if action was executed successfully, {@code false} otherwise
          */
-        boolean execute(@Nullable VersionedPackage versionedPackage,
+        boolean onExecuteHealthCheckMitigation(@Nullable VersionedPackage versionedPackage,
                 @FailureReasons int failureReason, int mitigationCount);
 
 
@@ -809,7 +837,7 @@
          * @param mitigationCount the number of times mitigation has been attempted for this
          *                        boot loop (including this time).
          */
-        default boolean executeBootLoopMitigation(int mitigationCount) {
+        default boolean onExecuteBootLoopMitigation(int mitigationCount) {
             return false;
         }
 
@@ -1090,7 +1118,7 @@
                         if (versionedPkg != null) {
                             Slog.i(TAG,
                                     "Explicit health check failed for package " + versionedPkg);
-                            registeredObserver.execute(versionedPkg,
+                            registeredObserver.onExecuteHealthCheckMitigation(versionedPkg,
                                     PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK, 1);
                         }
                     }
diff --git a/packages/CrashRecovery/services/platform/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/platform/java/com/android/server/RescueParty.java
index feb5775..f757236 100644
--- a/packages/CrashRecovery/services/platform/java/com/android/server/RescueParty.java
+++ b/packages/CrashRecovery/services/platform/java/com/android/server/RescueParty.java
@@ -859,7 +859,7 @@
         }
 
         @Override
-        public boolean execute(@Nullable VersionedPackage failedPackage,
+        public boolean onExecuteHealthCheckMitigation(@Nullable VersionedPackage failedPackage,
                 @FailureReasons int failureReason, int mitigationCount) {
             if (isDisabled()) {
                 return false;
@@ -927,7 +927,7 @@
         }
 
         @Override
-        public boolean executeBootLoopMitigation(int mitigationCount) {
+        public boolean onExecuteBootLoopMitigation(int mitigationCount) {
             if (isDisabled()) {
                 return false;
             }
diff --git a/packages/CrashRecovery/services/platform/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/packages/CrashRecovery/services/platform/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index d206c66..7445534 100644
--- a/packages/CrashRecovery/services/platform/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/packages/CrashRecovery/services/platform/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -158,7 +158,7 @@
                 // Note: For non-native crashes the rollback-all step has higher impact
                 impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
             } else if (getAvailableRollback(failedPackage) != null) {
-                // Rollback is available, we may get a callback into #execute
+                // Rollback is available, we may get a callback into #onExecuteHealthCheckMitigation
                 impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
             } else if (anyRollbackAvailable) {
                 // If any rollbacks are available, we will commit them
@@ -175,7 +175,7 @@
     }
 
     @Override
-    public boolean execute(@Nullable VersionedPackage failedPackage,
+    public boolean onExecuteHealthCheckMitigation(@Nullable VersionedPackage failedPackage,
             @FailureReasons int rollbackReason, int mitigationCount) {
         Slog.i(TAG, "Executing remediation."
                 + " failedPackage: "
@@ -229,7 +229,7 @@
     }
 
     @Override
-    public boolean executeBootLoopMitigation(int mitigationCount) {
+    public boolean onExecuteBootLoopMitigation(int mitigationCount) {
         if (Flags.recoverabilityDetection()) {
             List<RollbackInfo> availableRollbacks = getAvailableRollbacks();
 
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/EdgeToEdgeUtils.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/EdgeToEdgeUtils.java
index 062e9b8..42ffa67 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/EdgeToEdgeUtils.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/EdgeToEdgeUtils.java
@@ -17,6 +17,7 @@
 package com.android.settingslib.collapsingtoolbar;
 
 import android.os.Build;
+import android.view.ViewGroup;
 
 import androidx.activity.ComponentActivity;
 import androidx.activity.EdgeToEdge;
@@ -53,6 +54,8 @@
                             .getInsets(WindowInsetsCompat.Type.statusBars()).top;
                     // Apply the insets paddings to the view.
                     v.setPadding(insets.left, statusBarHeight, insets.right, insets.bottom);
+                    ((ViewGroup)v).setClipToPadding(false);
+                    ((ViewGroup)v).setClipChildren(false);
 
                     // Return CONSUMED if you don't want the window insets to keep being
                     // passed down to descendant views.
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/src/com/android/settingslib/preference/PreferenceFragment.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt
index 5f4b88f..41a626f 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt
@@ -21,16 +21,16 @@
 import android.os.Bundle
 import android.util.Log
 import androidx.annotation.XmlRes
-import androidx.preference.PreferenceFragmentCompat
 import androidx.preference.PreferenceScreen
 import com.android.settingslib.metadata.EXTRA_BINDING_SCREEN_KEY
 import com.android.settingslib.metadata.PreferenceScreenBindingKeyProvider
 import com.android.settingslib.metadata.PreferenceScreenRegistry
 import com.android.settingslib.preference.PreferenceScreenBindingHelper.Companion.bindRecursively
+import com.android.settingslib.widget.SettingsBasePreferenceFragment
 
 /** Fragment to display a preference screen. */
 open class PreferenceFragment :
-    PreferenceFragmentCompat(), PreferenceScreenProvider, PreferenceScreenBindingKeyProvider {
+    SettingsBasePreferenceFragment(), PreferenceScreenProvider, PreferenceScreenBindingKeyProvider {
 
     private var preferenceScreenBindingHelper: PreferenceScreenBindingHelper? = null
 
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/drawable-v35/settingslib_round_background_bottom_highlighted.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom_highlighted.xml
new file mode 100644
index 0000000..c0c0869
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom_highlighted.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  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.
+  -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="?android:colorControlHighlight">
+    <item
+        android:bottom="16dp"
+        android:end="?android:attr/listPreferredItemPaddingEnd"
+        android:start="?android:attr/listPreferredItemPaddingStart"
+        android:top="2dp">
+        <shape
+            android:shape="rectangle"
+            android:tint="?android:attr/colorAccent">
+            <corners
+                android:bottomLeftRadius="@dimen/settingslib_preference_corner_radius"
+                android:bottomRightRadius="@dimen/settingslib_preference_corner_radius"
+                android:topLeftRadius="4dp"
+                android:topRightRadius="4dp" />
+            <padding android:bottom="16dp" />
+            <solid android:color="#42000000" />
+        </shape>
+    </item>
+</ripple>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center_highlighted.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center_highlighted.xml
new file mode 100644
index 0000000..8099d9b
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center_highlighted.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  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.
+  -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="?android:colorControlHighlight">
+    <item
+        android:end="?android:attr/listPreferredItemPaddingEnd"
+        android:start="?android:attr/listPreferredItemPaddingStart"
+        android:top="2dp">
+        <shape
+            android:shape="rectangle"
+            android:tint="?android:attr/colorAccent">
+            <corners android:radius="4dp" />
+            <solid android:color="#42000000" />
+        </shape>
+    </item>
+</ripple>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_highlighted.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_highlighted.xml
new file mode 100644
index 0000000..a119a4a
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_highlighted.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  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.
+  -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="?android:colorControlHighlight">
+    <item
+        android:bottom="16dp"
+        android:end="?android:attr/listPreferredItemPaddingEnd"
+        android:start="?android:attr/listPreferredItemPaddingStart"
+        android:top="2dp">
+        <shape
+            android:shape="rectangle"
+            android:tint="?android:attr/colorAccent">
+            <corners android:radius="@dimen/settingslib_preference_corner_radius" />
+            <padding android:bottom="16dp" />
+            <solid android:color="#42000000" />
+        </shape>
+    </item>
+</ripple>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top_highlighted.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top_highlighted.xml
new file mode 100644
index 0000000..052eb01
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top_highlighted.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  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.
+  -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="?android:colorControlHighlight">
+    <item
+        android:color="?android:attr/colorAccent"
+        android:end="?android:attr/listPreferredItemPaddingEnd"
+        android:start="?android:attr/listPreferredItemPaddingStart"
+        android:top="2dp">
+        <shape
+            android:shape="rectangle"
+            android:tint="?android:attr/colorAccent">
+            <corners
+                android:bottomLeftRadius="4dp"
+                android:bottomRightRadius="4dp"
+                android:topLeftRadius="@dimen/settingslib_preference_corner_radius"
+                android:topRightRadius="@dimen/settingslib_preference_corner_radius" />
+            <solid android:color="#42000000" />
+        </shape>
+    </item>
+</ripple>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsBasePreferenceFragment.kt b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsBasePreferenceFragment.kt
index 535d80f..265c065 100644
--- a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsBasePreferenceFragment.kt
+++ b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsBasePreferenceFragment.kt
@@ -21,7 +21,7 @@
 import androidx.recyclerview.widget.RecyclerView
 
 /** Base class for Settings to use PreferenceFragmentCompat */
-open abstract class SettingsBasePreferenceFragment : PreferenceFragmentCompat() {
+abstract class SettingsBasePreferenceFragment : PreferenceFragmentCompat() {
 
     override fun onCreateAdapter(preferenceScreen: PreferenceScreen): RecyclerView.Adapter<*> {
         if (SettingsThemeHelper.isExpressiveTheme(requireContext()))
diff --git a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsPreferenceGroupAdapter.kt b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsPreferenceGroupAdapter.kt
index 98b7f76..6a06320 100644
--- a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsPreferenceGroupAdapter.kt
+++ b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsPreferenceGroupAdapter.kt
@@ -16,8 +16,10 @@
 
 package com.android.settingslib.widget
 
+import android.annotation.SuppressLint
 import android.os.Handler
 import android.os.Looper
+import android.util.TypedValue
 import androidx.annotation.DrawableRes
 import androidx.preference.Preference
 import androidx.preference.PreferenceCategory
@@ -27,12 +29,12 @@
 import com.android.settingslib.widget.theme.R
 
 /**
- * A custom adapter for displaying settings preferences in a list, handling rounded corners
- * for preference items within a group.
+ * A custom adapter for displaying settings preferences in a list, handling rounded corners for
+ * preference items within a group.
  */
-open class SettingsPreferenceGroupAdapter @JvmOverloads constructor(
-    preferenceGroup: PreferenceGroup
-) : PreferenceGroupAdapter(preferenceGroup) {
+@SuppressLint("RestrictedApi")
+open class SettingsPreferenceGroupAdapter(preferenceGroup: PreferenceGroup) :
+    PreferenceGroupAdapter(preferenceGroup) {
 
     private val mPreferenceGroup = preferenceGroup
     private var mRoundCornerMappingList: ArrayList<Int> = ArrayList()
@@ -41,6 +43,7 @@
     private var mGroupPaddingStart = 0
     private var mNormalPaddingEnd = 0
     private var mGroupPaddingEnd = 0
+    @DrawableRes private var mLegacyBackgroundRes: Int
 
     private val mHandler = Handler(Looper.getMainLooper())
 
@@ -54,9 +57,17 @@
         mNormalPaddingEnd =
             context.resources.getDimensionPixelSize(R.dimen.settingslib_expressive_space_small1)
         mGroupPaddingEnd = mNormalPaddingEnd * 2
+        val outValue = TypedValue()
+        context.theme.resolveAttribute(
+            android.R.attr.selectableItemBackground,
+            outValue,
+            true, /* resolveRefs */
+        )
+        mLegacyBackgroundRes = outValue.resourceId
         updatePreferences()
     }
 
+    @SuppressLint("RestrictedApi")
     override fun onPreferenceHierarchyChange(preference: Preference) {
         super.onPreferenceHierarchyChange(preference)
 
@@ -65,6 +76,7 @@
         mHandler.post(syncRunnable)
     }
 
+    @SuppressLint("RestrictedApi")
     override fun onBindViewHolder(holder: PreferenceViewHolder, position: Int) {
         super.onBindViewHolder(holder, position)
         updateBackground(holder, position)
@@ -79,6 +91,7 @@
         }
     }
 
+    @SuppressLint("RestrictedApi")
     private fun mappingPreferenceGroup(cornerStyles: MutableList<Int>, group: PreferenceGroup) {
         cornerStyles.clear()
         cornerStyles.addAll(MutableList(itemCount) { 0 })
@@ -151,20 +164,38 @@
         }
     }
 
-    /** handle roundCorner background  */
+    /** handle roundCorner background */
     private fun updateBackground(holder: PreferenceViewHolder, position: Int) {
-        @DrawableRes val backgroundRes = getRoundCornerDrawableRes(position, false /* isSelected*/)
+        val context = holder.itemView.context
+        @DrawableRes
+        val backgroundRes =
+            when (SettingsThemeHelper.isExpressiveTheme(context)) {
+                true -> getRoundCornerDrawableRes(position, isSelected = false)
+                else -> mLegacyBackgroundRes
+            }
 
         val v = holder.itemView
-        val paddingStart = if (backgroundRes == 0) mNormalPaddingStart else mGroupPaddingStart
-        val paddingEnd = if (backgroundRes == 0) mNormalPaddingEnd else mGroupPaddingEnd
-
-        v.setPaddingRelative(paddingStart, v.paddingTop, paddingEnd, v.paddingBottom)
+        // Update padding
+        if (SettingsThemeHelper.isExpressiveTheme(context)) {
+            val paddingStart = if (backgroundRes == 0) mNormalPaddingStart else mGroupPaddingStart
+            val paddingEnd = if (backgroundRes == 0) mNormalPaddingEnd else mGroupPaddingEnd
+            v.setPaddingRelative(paddingStart, v.paddingTop, paddingEnd, v.paddingBottom)
+        }
+        // Update background
         v.setBackgroundResource(backgroundRes)
     }
 
     @DrawableRes
     protected fun getRoundCornerDrawableRes(position: Int, isSelected: Boolean): Int {
+        return getRoundCornerDrawableRes(position, isSelected, false)
+    }
+
+    @DrawableRes
+    protected fun getRoundCornerDrawableRes(
+        position: Int,
+        isSelected: Boolean,
+        isHighlighted: Boolean,
+    ): Int {
         val cornerType = mRoundCornerMappingList[position]
 
         if ((cornerType and ROUND_CORNER_CENTER) == 0) {
@@ -175,24 +206,28 @@
             (cornerType and ROUND_CORNER_TOP) != 0 && (cornerType and ROUND_CORNER_BOTTOM) == 0 -> {
                 // the first
                 if (isSelected) R.drawable.settingslib_round_background_top_selected
+                else if (isHighlighted) R.drawable.settingslib_round_background_top_highlighted
                 else R.drawable.settingslib_round_background_top
             }
 
             (cornerType and ROUND_CORNER_BOTTOM) != 0 && (cornerType and ROUND_CORNER_TOP) == 0 -> {
                 // the last
                 if (isSelected) R.drawable.settingslib_round_background_bottom_selected
+                else if (isHighlighted) R.drawable.settingslib_round_background_bottom_highlighted
                 else R.drawable.settingslib_round_background_bottom
             }
 
             (cornerType and ROUND_CORNER_TOP) != 0 && (cornerType and ROUND_CORNER_BOTTOM) != 0 -> {
                 // the only one preference
                 if (isSelected) R.drawable.settingslib_round_background_selected
+                else if (isHighlighted) R.drawable.settingslib_round_background_highlighted
                 else R.drawable.settingslib_round_background
             }
 
             else -> {
                 // in the center
                 if (isSelected) R.drawable.settingslib_round_background_center_selected
+                else if (isHighlighted) R.drawable.settingslib_round_background_center_highlighted
                 else R.drawable.settingslib_round_background_center
             }
         }
@@ -203,4 +238,4 @@
         private const val ROUND_CORNER_TOP: Int = 1 shl 1
         private const val ROUND_CORNER_BOTTOM: Int = 1 shl 2
     }
-}
\ No newline at end of file
+}
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/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelperProvider.kt
similarity index 68%
copy from libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
copy to packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelperProvider.kt
index e21bf8f..f286084 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelperProvider.kt
@@ -14,6 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.shared;
+package com.android.settingslib
 
-parcelable GroupedRecentTaskInfo;
\ No newline at end of file
+/** Provider of [RestrictedPreferenceHelper]. */
+interface RestrictedPreferenceHelperProvider {
+
+    /** 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/notification/modes/ZenIconKeys.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconKeys.java
index 79dabf0..5d2a166 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconKeys.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconKeys.java
@@ -41,7 +41,7 @@
 
     private static final ImmutableMap<Integer, ZenIcon.Key> TYPE_DEFAULTS = ImmutableMap.of(
             AutomaticZenRule.TYPE_UNKNOWN,
-            ZenIcon.Key.forSystemResource(R.drawable.ic_zen_mode_type_unknown),
+            ZenIcon.Key.forSystemResource(R.drawable.ic_zen_mode_type_special_dnd),
             AutomaticZenRule.TYPE_OTHER,
             ZenIcon.Key.forSystemResource(R.drawable.ic_zen_mode_type_other),
             AutomaticZenRule.TYPE_SCHEDULE_TIME,
@@ -61,7 +61,7 @@
     );
 
     private static final ZenIcon.Key FOR_UNEXPECTED_TYPE =
-            ZenIcon.Key.forSystemResource(R.drawable.ic_zen_mode_type_unknown);
+            ZenIcon.Key.forSystemResource(R.drawable.ic_zen_mode_type_special_dnd);
 
     /** Default icon descriptors per mode {@link AutomaticZenRule.Type}. */
     static ZenIcon.Key forType(@AutomaticZenRule.Type int ruleType) {
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/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 0938167..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."
@@ -1578,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"
@@ -1726,10 +1743,3 @@
     description: "An implementation of shortcut customizations through shortcut helper."
     bug: "365064144"
 }
-
-flag {
-   name: "touchpad_three_finger_tap_customization"
-   namespace: "systemui"
-   description: "Customize touchpad three finger tap"
-   bug: "365063048"
-}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java
index 0b15d23..cbe11a3 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java
@@ -268,7 +268,8 @@
             // skip changes that we didn't wrap
             if (!leashMap.containsKey(change.getLeash())) continue;
             // Only make the update if we are closing Desktop tasks.
-            if (change.getTaskInfo().isFreeform() && isClosingMode(change.getMode())) {
+            if (change.getTaskInfo() != null && change.getTaskInfo().isFreeform()
+                    && isClosingMode(change.getMode())) {
                 startTransaction.setAlpha(leashMap.get(launcherChange.getLeash()), 0f);
                 return;
             }
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/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index 6d30398..87e9c42 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -42,7 +42,6 @@
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.animation.scene.SceneTransitionLayout
 import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
 import com.android.compose.animation.scene.observableTransitionState
 import com.android.compose.animation.scene.transitions
 import com.android.systemui.communal.shared.model.CommunalBackgroundType
@@ -124,7 +123,7 @@
         }
         timestampRange(
             startMillis = TransitionDuration.EDIT_MODE_TO_HUB_GRID_DELAY_MS,
-            endMillis = TransitionDuration.EDIT_MODE_TO_HUB_GRID_END_MS
+            endMillis = TransitionDuration.EDIT_MODE_TO_HUB_GRID_END_MS,
         ) {
             fade(Communal.Elements.Grid)
         }
@@ -187,14 +186,13 @@
     ) {
         scene(
             CommunalScenes.Blank,
-            userActions =
-                mapOf(Swipe(SwipeDirection.Start, fromSource = Edge.End) to CommunalScenes.Communal)
+            userActions = mapOf(Swipe.Start(fromSource = Edge.End) to CommunalScenes.Communal),
         ) {
             // This scene shows nothing only allowing for transitions to the communal scene.
             Box(modifier = Modifier.fillMaxSize())
         }
 
-        val userActions = mapOf(Swipe(SwipeDirection.End) to CommunalScenes.Blank)
+        val userActions = mapOf(Swipe.End to CommunalScenes.Blank)
 
         scene(CommunalScenes.Communal, userActions = userActions) {
             CommunalScene(
@@ -257,13 +255,9 @@
 
 /** Default background of the hub, a single color */
 @Composable
-private fun BoxScope.DefaultBackground(
-    colors: CommunalColors,
-) {
+private fun BoxScope.DefaultBackground(colors: CommunalColors) {
     val backgroundColor by colors.backgroundColor.collectAsStateWithLifecycle()
-    Box(
-        modifier = Modifier.matchParentSize().background(Color(backgroundColor.toArgb())),
-    )
+    Box(modifier = Modifier.matchParentSize().background(Color(backgroundColor.toArgb())))
 }
 
 /** Experimental hub background, static linear gradient */
@@ -273,7 +267,7 @@
     Box(
         Modifier.matchParentSize()
             .background(
-                Brush.linearGradient(colors = listOf(colors.primary, colors.primaryContainer)),
+                Brush.linearGradient(colors = listOf(colors.primary, colors.primaryContainer))
             )
     )
     BackgroundTopScrim()
@@ -288,7 +282,7 @@
             .background(colors.primary)
             .animatedRadialGradientBackground(
                 toColor = colors.primary,
-                fromColor = colors.primaryContainer.copy(alpha = 0.6f)
+                fromColor = colors.primaryContainer.copy(alpha = 0.6f),
             )
     )
     BackgroundTopScrim()
@@ -324,9 +318,9 @@
                             durationMillis = ANIMATION_DURATION_MS,
                             easing = CubicBezierEasing(0.33f, 0f, 0.67f, 1f),
                         ),
-                    repeatMode = RepeatMode.Reverse
+                    repeatMode = RepeatMode.Reverse,
                 ),
-            label = "radial gradient center fraction"
+            label = "radial gradient center fraction",
         )
 
     // Offset to place the center of the gradients offscreen. This is applied to both the
@@ -337,16 +331,9 @@
         val gradientRadius = (size.width / 2) + offsetPx
         val totalHeight = size.height + 2 * offsetPx
 
-        val leftCenter =
-            Offset(
-                x = -offsetPx,
-                y = totalHeight * centerFraction - offsetPx,
-            )
+        val leftCenter = Offset(x = -offsetPx, y = totalHeight * centerFraction - offsetPx)
         val rightCenter =
-            Offset(
-                x = offsetPx + size.width,
-                y = totalHeight * (1f - centerFraction) - offsetPx,
-            )
+            Offset(x = offsetPx + size.width, y = totalHeight * (1f - centerFraction) - offsetPx)
 
         // Right gradient
         drawCircle(
@@ -354,7 +341,7 @@
                 Brush.radialGradient(
                     colors = listOf(fromColor, toColor),
                     center = rightCenter,
-                    radius = gradientRadius
+                    radius = gradientRadius,
                 ),
             center = rightCenter,
             radius = gradientRadius,
@@ -367,7 +354,7 @@
                 Brush.radialGradient(
                     colors = listOf(fromColor, toColor),
                     center = leftCenter,
-                    radius = gradientRadius
+                    radius = gradientRadius,
                 ),
             center = leftCenter,
             radius = gradientRadius,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt
index 5b99670..2af5ffa 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt
@@ -18,23 +18,27 @@
 
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
+import androidx.compose.ui.layout.layoutId
 import com.android.compose.animation.scene.ContentScope
+import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.battery.BatteryMeterViewController
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
+import com.android.systemui.keyguard.ui.composable.blueprint.rememberBurnIn
+import com.android.systemui.keyguard.ui.composable.section.DefaultClockSection
 import com.android.systemui.lifecycle.rememberViewModel
 import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeOverlayActionsViewModel
 import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeOverlayContentViewModel
 import com.android.systemui.scene.session.ui.composable.SaveableSession
 import com.android.systemui.scene.shared.model.Overlays
 import com.android.systemui.scene.ui.composable.Overlay
-import com.android.systemui.shade.ui.composable.ExpandedShadeHeader
+import com.android.systemui.shade.ui.composable.CollapsedShadeHeader
 import com.android.systemui.shade.ui.composable.OverlayShade
+import com.android.systemui.shade.ui.composable.SingleShadeMeasurePolicy
 import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
 import com.android.systemui.statusbar.phone.ui.StatusBarIconController
 import com.android.systemui.statusbar.phone.ui.TintedIconManager
@@ -53,6 +57,8 @@
     private val statusBarIconController: StatusBarIconController,
     private val shadeSession: SaveableSession,
     private val stackScrollView: Lazy<NotificationScrollView>,
+    private val clockSection: DefaultClockSection,
+    private val clockInteractor: KeyguardClockInteractor,
 ) : Overlay {
 
     override val key = Overlays.NotificationsShade
@@ -80,13 +86,28 @@
 
         OverlayShade(modifier = modifier, onScrimClicked = viewModel::onScrimClicked) {
             Column {
-                ExpandedShadeHeader(
-                    viewModelFactory = viewModel.shadeHeaderViewModelFactory,
-                    createTintedIconManager = tintedIconManagerFactory::create,
-                    createBatteryMeterViewController = batteryMeterViewControllerFactory::create,
-                    statusBarIconController = statusBarIconController,
-                    modifier = Modifier.padding(horizontal = 16.dp),
-                )
+                if (viewModel.showHeader) {
+                    val burnIn = rememberBurnIn(clockInteractor)
+
+                    CollapsedShadeHeader(
+                        viewModelFactory = viewModel.shadeHeaderViewModelFactory,
+                        createTintedIconManager = tintedIconManagerFactory::create,
+                        createBatteryMeterViewController =
+                            batteryMeterViewControllerFactory::create,
+                        statusBarIconController = statusBarIconController,
+                        modifier =
+                            Modifier.element(NotificationsShade.Elements.StatusBar)
+                                .layoutId(SingleShadeMeasurePolicy.LayoutId.ShadeHeader),
+                    )
+
+                    with(clockSection) {
+                        SmallClock(
+                            burnInParams = burnIn.parameters,
+                            onTopChanged = burnIn.onSmallClockTopChanged,
+                            modifier = Modifier.fillMaxWidth(),
+                        )
+                    }
+                }
 
                 NotificationScrollingStack(
                     shadeSession = shadeSession,
@@ -110,3 +131,9 @@
         }
     }
 }
+
+object NotificationsShade {
+    object Elements {
+        val StatusBar = ElementKey("NotificationsShadeStatusBar")
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
index 67f412e..2cde678 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
@@ -105,13 +105,17 @@
 
     // Overlay transitions
 
+    // TODO(b/376659778): Remove this transition once nested STLs are supported.
+    from(Scenes.Gone, to = Overlays.NotificationsShade) {
+        toNotificationsShadeTransition(translateClock = true)
+    }
     to(Overlays.NotificationsShade) { toNotificationsShadeTransition() }
     to(Overlays.QuickSettingsShade) { toQuickSettingsShadeTransition() }
     from(Overlays.NotificationsShade, to = Overlays.QuickSettingsShade) {
         notificationsShadeToQuickSettingsShadeTransition()
     }
     from(Scenes.Gone, to = Overlays.NotificationsShade, key = SlightlyFasterShadeCollapse) {
-        toNotificationsShadeTransition(durationScale = 0.9)
+        toNotificationsShadeTransition(translateClock = true, durationScale = 0.9)
     }
     from(Scenes.Gone, to = Overlays.QuickSettingsShade, key = SlightlyFasterShadeCollapse) {
         toQuickSettingsShadeTransition(durationScale = 0.9)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
index 23c4f12..6bdb363 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
@@ -21,30 +21,42 @@
 import androidx.compose.animation.core.tween
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys
 import com.android.systemui.notifications.ui.composable.Notifications
+import com.android.systemui.notifications.ui.composable.NotificationsShade
+import com.android.systemui.scene.shared.model.Overlays
 import com.android.systemui.shade.ui.composable.OverlayShade
 import com.android.systemui.shade.ui.composable.Shade
-import com.android.systemui.shade.ui.composable.ShadeHeader
 import kotlin.time.Duration.Companion.milliseconds
 
-fun TransitionBuilder.toNotificationsShadeTransition(durationScale: Double = 1.0) {
+fun TransitionBuilder.toNotificationsShadeTransition(
+    translateClock: Boolean = false,
+    durationScale: Double = 1.0,
+) {
     spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
     swipeSpec =
         spring(
             stiffness = Spring.StiffnessMediumLow,
             visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
         )
+    // Ensure the clock isn't clipped by the shade outline during the transition from lockscreen.
+    sharedElement(
+        ClockElementKeys.smallClockElementKey,
+        elevateInContent = Overlays.NotificationsShade,
+    )
     scaleSize(OverlayShade.Elements.Panel, height = 0f)
+    // TODO(b/376659778): This is a temporary hack to have a shared element transition with the
+    //  lockscreen clock. Remove once nested STLs are supported.
+    if (!translateClock) {
+        translate(ClockElementKeys.smallClockElementKey)
+    }
+    // Avoid translating the status bar with the shade panel.
+    translate(NotificationsShade.Elements.StatusBar)
+    // Slide in the shade panel from the top edge.
     translate(OverlayShade.Elements.Panel, Edge.Top)
 
     fractionRange(end = .5f) { fade(OverlayShade.Elements.Scrim) }
-
-    fractionRange(start = .5f) {
-        fade(ShadeHeader.Elements.Clock)
-        fade(ShadeHeader.Elements.ExpandedContent)
-        fade(ShadeHeader.Elements.PrivacyChip)
-        fade(Notifications.Elements.NotificationScrim)
-    }
+    fractionRange(start = .5f) { fade(Notifications.Elements.NotificationScrim) }
 }
 
 private val DefaultDuration = 300.milliseconds
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/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index 5042403..dbf7d7b 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
@@ -404,9 +405,11 @@
 }
 
 /** The user swiped on the container. */
-data class Swipe(
+data class Swipe
+private constructor(
     val direction: SwipeDirection,
     val pointerCount: Int = 1,
+    val pointersType: PointerType? = null,
     val fromSource: SwipeSource? = null,
 ) : UserAction() {
     companion object {
@@ -416,12 +419,49 @@
         val Down = Swipe(SwipeDirection.Down)
         val Start = Swipe(SwipeDirection.Start)
         val End = Swipe(SwipeDirection.End)
+
+        fun Left(
+            pointerCount: Int = 1,
+            pointersType: PointerType? = null,
+            fromSource: SwipeSource? = null,
+        ) = Swipe(SwipeDirection.Left, pointerCount, pointersType, fromSource)
+
+        fun Up(
+            pointerCount: Int = 1,
+            pointersType: PointerType? = null,
+            fromSource: SwipeSource? = null,
+        ) = Swipe(SwipeDirection.Up, pointerCount, pointersType, fromSource)
+
+        fun Right(
+            pointerCount: Int = 1,
+            pointersType: PointerType? = null,
+            fromSource: SwipeSource? = null,
+        ) = Swipe(SwipeDirection.Right, pointerCount, pointersType, fromSource)
+
+        fun Down(
+            pointerCount: Int = 1,
+            pointersType: PointerType? = null,
+            fromSource: SwipeSource? = null,
+        ) = Swipe(SwipeDirection.Down, pointerCount, pointersType, fromSource)
+
+        fun Start(
+            pointerCount: Int = 1,
+            pointersType: PointerType? = null,
+            fromSource: SwipeSource? = null,
+        ) = Swipe(SwipeDirection.Start, pointerCount, pointersType, fromSource)
+
+        fun End(
+            pointerCount: Int = 1,
+            pointersType: PointerType? = null,
+            fromSource: SwipeSource? = null,
+        ) = Swipe(SwipeDirection.End, pointerCount, pointersType, fromSource)
     }
 
     override fun resolve(layoutDirection: LayoutDirection): UserAction.Resolved {
         return Resolved(
             direction = direction.resolve(layoutDirection),
             pointerCount = pointerCount,
+            pointersType = pointersType,
             fromSource = fromSource?.resolve(layoutDirection),
         )
     }
@@ -431,6 +471,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/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
index e924ebf..20a0b39 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
@@ -207,6 +207,9 @@
     }
 
     override suspend fun onPreFling(available: Velocity): Velocity {
+        // Note: This method may be called multiple times. Due to NestedScrollDispatcher, the order
+        // of method calls (pre/post scroll/fling) cannot be guaranteed.
+        if (isStopping) return Velocity.Zero
         val controller = currentController ?: return Velocity.Zero
 
         // If in priority mode and can stop on pre-fling phase, stop the scroll.
@@ -219,6 +222,9 @@
     }
 
     override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
+        // Note: This method may be called multiple times. Due to NestedScrollDispatcher, the order
+        // of method calls (pre/post scroll/fling) cannot be guaranteed.
+        if (isStopping) return Velocity.Zero
         val availableFloat = available.toFloat()
         val controller = currentController
 
@@ -315,6 +321,7 @@
      * @return The consumed velocity.
      */
     suspend fun stop(velocity: Float): Velocity {
+        if (isStopping) return Velocity.Zero
         val controller = requireController(isStopping = false)
         return coroutineScope {
             try {
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..098673e 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) {
@@ -86,10 +101,7 @@
             scene(
                 key = SceneC,
                 userActions =
-                    mapOf(
-                        Swipe.Up to SceneB,
-                        Swipe(SwipeDirection.Up, fromSource = Edge.Bottom) to SceneA,
-                    ),
+                    mapOf(Swipe.Up to SceneB, Swipe.Up(fromSource = Edge.Bottom) to SceneA),
             ) {
                 Text("SceneC")
             }
@@ -126,9 +138,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 +221,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 +528,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 +555,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 +1052,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 +1068,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 +1084,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 +1103,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 +1120,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 +1160,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 +1204,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,20 +1221,14 @@
         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))
         assertTransition(
             currentScene = SceneC,
             fromScene = SceneC,
-            // userAction: Swipe(SwipeDirection.Up, fromSource = Edge.Bottom) to SceneA
+            // userAction: Swipe.Up(fromSource = Edge.Bottom) to SceneA
             toScene = SceneA,
             progress = 0.1f,
         )
@@ -1248,9 +1241,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 +1251,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 +1265,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 +1296,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 +1342,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 +1374,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 +1405,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 +1437,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 +1471,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 +1504,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/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index ee807e6..4a90515 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -869,10 +869,7 @@
                 state = state,
                 modifier = Modifier.size(layoutWidth, layoutHeight),
             ) {
-                scene(
-                    SceneA,
-                    userActions = mapOf(Swipe(SwipeDirection.Down, pointerCount = 2) to SceneB),
-                ) {
+                scene(SceneA, userActions = mapOf(Swipe.Down(pointerCount = 2) to SceneB)) {
                     Box(
                         Modifier
                             // A scrollable that does not consume the scroll gesture
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..3b2ee98 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
@@ -126,14 +128,21 @@
                     if (swipesEnabled())
                         mapOf(
                             Swipe.Down to SceneA,
-                            Swipe(SwipeDirection.Down, pointerCount = 2) to SceneB,
-                            Swipe(SwipeDirection.Right, fromSource = Edge.Left) to SceneB,
-                            Swipe(SwipeDirection.Down, fromSource = Edge.Top) to SceneB,
+                            Swipe.Down(pointerCount = 2) to SceneB,
+                            Swipe.Down(pointersType = PointerType.Mouse) to SceneD,
+                            Swipe.Down(fromSource = Edge.Top) to SceneB,
+                            Swipe.Right(fromSource = Edge.Left) to SceneB,
                         )
                     else emptyMap(),
             ) {
                 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/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
index 5442840..91079b8 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
@@ -29,6 +29,8 @@
 import com.android.compose.test.runMonotonicClockTest
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -262,4 +264,16 @@
         scrollConnection.onPostFling(consumed = Velocity.Zero, available = Velocity.Zero)
         assertThat(isStarted).isEqualTo(true)
     }
+
+    @Test
+    fun handleMultipleOnPreFlingCalls() = runTest {
+        startPriorityModePostScroll()
+
+        coroutineScope {
+            launch { scrollConnection.onPreFling(available = Velocity.Zero) }
+            launch { scrollConnection.onPreFling(available = Velocity.Zero) }
+        }
+
+        assertThat(lastStop).isEqualTo(0f)
+    }
 }
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/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/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/bouncer/ui/viewmodel/BouncerUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerUserActionsViewModelTest.kt
index f58bbc3..d371592 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerUserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerUserActionsViewModelTest.kt
@@ -20,7 +20,6 @@
 import androidx.test.filters.SmallTest
 import com.android.compose.animation.scene.Back
 import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
 import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
@@ -71,7 +70,7 @@
             assertThat(actions)
                 .containsEntriesExactly(
                     Back to UserActionResult(Scenes.QuickSettings),
-                    Swipe(SwipeDirection.Down) to UserActionResult(Scenes.QuickSettings),
+                    Swipe.Down to UserActionResult(Scenes.QuickSettings),
                 )
         }
 }
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/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
index 7f31356..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
@@ -193,7 +193,7 @@
 
     @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)
@@ -208,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,
@@ -220,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,
@@ -234,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))
@@ -245,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)
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/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt
index 6397979..5c4b743 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt
@@ -25,7 +25,6 @@
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
 import com.android.compose.animation.scene.TransitionKey
 import com.android.compose.animation.scene.UserActionResult
 import com.android.compose.animation.scene.UserActionResult.ShowOverlay
@@ -201,8 +200,7 @@
             val userActions by collectLastValue(underTest.actions)
             val downDestination =
                 userActions?.get(
-                    Swipe(
-                        SwipeDirection.Down,
+                    Swipe.Down(
                         fromSource = Edge.Top.takeIf { downFromEdge },
                         pointerCount = if (downWithTwoPointers) 2 else 1,
                     )
@@ -292,8 +290,7 @@
 
             val downDestination =
                 userActions?.get(
-                    Swipe(
-                        SwipeDirection.Down,
+                    Swipe.Down(
                         fromSource = Edge.Top.takeIf { downFromEdge },
                         pointerCount = if (downWithTwoPointers) 2 else 1,
                     )
@@ -310,8 +307,7 @@
 
             val downFromTopRightDestination =
                 userActions?.get(
-                    Swipe(
-                        SwipeDirection.Down,
+                    Swipe.Down(
                         fromSource = SceneContainerEdge.TopRight,
                         pointerCount = if (downWithTwoPointers) 2 else 1,
                     )
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
index 9e3fdf3..8e67e60 100644
--- 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
@@ -20,7 +20,6 @@
 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
@@ -69,10 +68,9 @@
     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 commandCaptor = argumentCaptor<SessionCommand>()
+    private val runnableCaptor = argumentCaptor<Runnable>()
 
     private val legacyToken = MediaSession.Token(1, null)
     private val token = mock<SessionToken>()
@@ -97,8 +95,6 @@
 
     @Before
     fun setup() {
-        testableLooper = TestableLooper.get(this)
-
         underTest =
             Media3ActionFactory(
                 context,
@@ -246,7 +242,6 @@
             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()
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 1a7265b..cf503bb 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
@@ -82,7 +82,7 @@
     private val fakeFeatureFlags = kosmos.fakeFeatureFlagsClassic
     private val mediaFlags = kosmos.mediaFlags
     private val mediaControllerFactory = kosmos.fakeMediaControllerFactory
-    private val media3ActionFactory = kosmos.media3ActionFactory
+    private lateinit var media3ActionFactory: Media3ActionFactory
     private val session = MediaSession(context, "MediaDataLoaderTestSession")
     private val metadataBuilder =
         MediaMetadata.Builder().apply {
@@ -94,6 +94,7 @@
 
     @Before
     fun setUp() {
+        media3ActionFactory = kosmos.media3ActionFactory
         mediaControllerFactory.setControllerForToken(session.sessionToken, mediaController)
         whenever(mediaController.sessionToken).thenReturn(session.sessionToken)
         whenever(mediaController.metadata).then { metadataBuilder.build() }
@@ -311,7 +312,6 @@
                     }
                     build()
                 }
-
             val result = underTest.loadMediaData(KEY, mediaNotification)
 
             assertThat(result).isNotNull()
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/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModelTest.kt
index 642d9a0..855931c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModelTest.kt
@@ -21,7 +21,6 @@
 import androidx.test.filters.SmallTest
 import com.android.compose.animation.scene.Back
 import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
 import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
@@ -76,12 +75,8 @@
             underTest.activateIn(this)
 
             assertThat(
-                    (actions?.get(
-                            Swipe(
-                                direction = SwipeDirection.Down,
-                                fromSource = SceneContainerEdge.TopRight,
-                            )
-                        ) as? UserActionResult.ReplaceByOverlay)
+                    (actions?.get(Swipe.Down(fromSource = SceneContainerEdge.TopRight))
+                            as? UserActionResult.ReplaceByOverlay)
                         ?.overlay
                 )
                 .isEqualTo(Overlays.QuickSettingsShade)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt
index ada2138..b30313e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt
@@ -35,9 +35,12 @@
 import com.android.systemui.scene.domain.startable.sceneContainerStartable
 import com.android.systemui.scene.shared.model.Overlays
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.data.repository.shadeRepository
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.shade.shared.flag.DualShade
 import com.android.systemui.shade.ui.viewmodel.notificationsShadeOverlayContentViewModel
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -121,6 +124,38 @@
             assertThat(currentOverlays).doesNotContain(Overlays.NotificationsShade)
         }
 
+    @Test
+    fun showHeader_showsOnNarrowScreen() =
+        testScope.runTest {
+            kosmos.shadeRepository.setShadeLayoutWide(false)
+
+            // Shown when notifications are present.
+            kosmos.activeNotificationListRepository.setActiveNotifs(1)
+            runCurrent()
+            assertThat(underTest.showHeader).isTrue()
+
+            // Hidden when notifications are not present.
+            kosmos.activeNotificationListRepository.setActiveNotifs(0)
+            runCurrent()
+            assertThat(underTest.showHeader).isFalse()
+        }
+
+    @Test
+    fun showHeader_hidesOnWideScreen() =
+        testScope.runTest {
+            kosmos.shadeRepository.setShadeLayoutWide(true)
+
+            // Hidden when notifications are present.
+            kosmos.activeNotificationListRepository.setActiveNotifs(1)
+            runCurrent()
+            assertThat(underTest.showHeader).isFalse()
+
+            // Hidden when notifications are not present.
+            kosmos.activeNotificationListRepository.setActiveNotifs(0)
+            runCurrent()
+            assertThat(underTest.showHeader).isFalse()
+        }
+
     private fun TestScope.lockDevice() {
         val currentScene by collectLastValue(sceneInteractor.currentScene)
         kosmos.powerInteractor.setAsleepForTest()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
index 9fe9ed2..5cba325 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
@@ -36,6 +36,7 @@
 import com.android.systemui.qs.composefragment.viewmodel.MediaState.NO_MEDIA
 import com.android.systemui.qs.fgsManagerController
 import com.android.systemui.qs.panels.domain.interactor.tileSquishinessInteractor
+import com.android.systemui.qs.panels.ui.viewmodel.setConfigurationForMediaInRow
 import com.android.systemui.res.R
 import com.android.systemui.shade.largeScreenHeaderHelper
 import com.android.systemui.statusbar.StatusBarState
@@ -269,6 +270,54 @@
             }
         }
 
+    @Test
+    fun mediaNotInRow() =
+        with(kosmos) {
+            testScope.testWithinLifecycle {
+                setConfigurationForMediaInRow(mediaInRow = false)
+                setMediaState(ACTIVE_MEDIA)
+
+                assertThat(underTest.qqsMediaInRow).isFalse()
+                assertThat(underTest.qsMediaInRow).isFalse()
+            }
+        }
+
+    @Test
+    fun mediaInRow_mediaActive_bothInRow() =
+        with(kosmos) {
+            testScope.testWithinLifecycle {
+                setConfigurationForMediaInRow(mediaInRow = true)
+                setMediaState(ACTIVE_MEDIA)
+
+                assertThat(underTest.qqsMediaInRow).isTrue()
+                assertThat(underTest.qsMediaInRow).isTrue()
+            }
+        }
+
+    @Test
+    fun mediaInRow_mediaRecommendation_onlyQSInRow() =
+        with(kosmos) {
+            testScope.testWithinLifecycle {
+                setConfigurationForMediaInRow(mediaInRow = true)
+                setMediaState(ANY_MEDIA)
+
+                assertThat(underTest.qqsMediaInRow).isFalse()
+                assertThat(underTest.qsMediaInRow).isTrue()
+            }
+        }
+
+    @Test
+    fun mediaInRow_correctConfig_noMediaVisible_noMediaInRow() =
+        with(kosmos) {
+            testScope.testWithinLifecycle {
+                setConfigurationForMediaInRow(mediaInRow = true)
+                setMediaState(NO_MEDIA)
+
+                assertThat(underTest.qqsMediaInRow).isFalse()
+                assertThat(underTest.qsMediaInRow).isFalse()
+            }
+        }
+
     private fun TestScope.setMediaState(state: MediaState) {
         with(kosmos) {
             val activeMedia = state == ACTIVE_MEDIA
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/QSFragmentComposeTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/QSFragmentComposeTest.kt
new file mode 100644
index 0000000..ab78029
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/QSFragmentComposeTest.kt
@@ -0,0 +1,182 @@
+/*
+ * 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.qs.panels.ui
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.getBoundsInRoot
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.DpRect
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.width
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.composefragment.QuickQuickSettingsLayout
+import com.android.systemui.qs.composefragment.QuickSettingsLayout
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class QSFragmentComposeTest : SysuiTestCase() {
+
+    @get:Rule val composeTestRule = createComposeRule()
+
+    @Test
+    fun portraitLayout_qqs() {
+        composeTestRule.setContent {
+            QuickQuickSettingsLayout(
+                tiles = { Tiles(TILES_HEIGHT_PORTRAIT) },
+                media = { Media() },
+                mediaInRow = false,
+            )
+        }
+
+        composeTestRule.waitForIdle()
+
+        val tilesBounds = composeTestRule.onNodeWithTag(TILES).getBoundsInRoot()
+        val mediaBounds = composeTestRule.onNodeWithTag(MEDIA).getBoundsInRoot()
+
+        // All nodes aligned in a column
+        assertThat(tilesBounds.left).isEqualTo(mediaBounds.left)
+        assertThat(tilesBounds.right).isEqualTo(mediaBounds.right)
+        assertThat(tilesBounds.bottom).isLessThan(mediaBounds.top)
+    }
+
+    @Test
+    fun landscapeLayout_qqs() {
+        composeTestRule.setContent {
+            QuickQuickSettingsLayout(
+                tiles = { Tiles(TILES_HEIGHT_LANDSCAPE) },
+                media = { Media() },
+                mediaInRow = true,
+            )
+        }
+
+        composeTestRule.waitForIdle()
+
+        val tilesBounds = composeTestRule.onNodeWithTag(TILES).getBoundsInRoot()
+        val mediaBounds = composeTestRule.onNodeWithTag(MEDIA).getBoundsInRoot()
+
+        // Media to the right of tiles
+        assertThat(tilesBounds.right).isLessThan(mediaBounds.left)
+        // "Same" width
+        assertThat((tilesBounds.width - mediaBounds.width).abs()).isAtMost(1.dp)
+        // Vertically centered
+        assertThat((tilesBounds.centerY - mediaBounds.centerY).abs()).isAtMost(1.dp)
+    }
+
+    @Test
+    fun portraitLayout_qs() {
+        composeTestRule.setContent {
+            QuickSettingsLayout(
+                brightness = { Brightness() },
+                tiles = { Tiles(TILES_HEIGHT_PORTRAIT) },
+                media = { Media() },
+                mediaInRow = false,
+            )
+        }
+
+        composeTestRule.waitForIdle()
+
+        val brightnessBounds = composeTestRule.onNodeWithTag(BRIGHTNESS).getBoundsInRoot()
+        val tilesBounds = composeTestRule.onNodeWithTag(TILES).getBoundsInRoot()
+        val mediaBounds = composeTestRule.onNodeWithTag(MEDIA).getBoundsInRoot()
+
+        assertThat(brightnessBounds.left).isEqualTo(tilesBounds.left)
+        assertThat(tilesBounds.left).isEqualTo(mediaBounds.left)
+
+        assertThat(brightnessBounds.right).isEqualTo(tilesBounds.right)
+        assertThat(tilesBounds.right).isEqualTo(mediaBounds.right)
+
+        assertThat(brightnessBounds.bottom).isLessThan(tilesBounds.top)
+        assertThat(tilesBounds.bottom).isLessThan(mediaBounds.top)
+    }
+
+    @Test
+    fun landscapeLayout_qs() {
+        composeTestRule.setContent {
+            QuickSettingsLayout(
+                brightness = { Brightness() },
+                tiles = { Tiles(TILES_HEIGHT_PORTRAIT) },
+                media = { Media() },
+                mediaInRow = true,
+            )
+        }
+
+        composeTestRule.waitForIdle()
+
+        val brightnessBounds = composeTestRule.onNodeWithTag(BRIGHTNESS).getBoundsInRoot()
+        val tilesBounds = composeTestRule.onNodeWithTag(TILES).getBoundsInRoot()
+        val mediaBounds = composeTestRule.onNodeWithTag(MEDIA).getBoundsInRoot()
+
+        // Brightness takes full width, with left end aligned with tiles and right end aligned with
+        // media
+        assertThat(brightnessBounds.left).isEqualTo(tilesBounds.left)
+        assertThat(brightnessBounds.right).isEqualTo(mediaBounds.right)
+
+        // Brightness above tiles and media
+        assertThat(brightnessBounds.bottom).isLessThan(tilesBounds.top)
+        assertThat(brightnessBounds.bottom).isLessThan(mediaBounds.top)
+
+        // Media to the right of tiles
+        assertThat(tilesBounds.right).isLessThan(mediaBounds.left)
+        // "Same" width
+        assertThat((tilesBounds.width - mediaBounds.width).abs()).isAtMost(1.dp)
+        // Vertically centered
+        assertThat((tilesBounds.centerY - mediaBounds.centerY).abs()).isAtMost(1.dp)
+    }
+
+    private companion object {
+        const val BRIGHTNESS = "brightness"
+        const val TILES = "tiles"
+        const val MEDIA = "media"
+        val TILES_HEIGHT_PORTRAIT = 300.dp
+        val TILES_HEIGHT_LANDSCAPE = 150.dp
+        val MEDIA_HEIGHT = 100.dp
+        val BRIGHTNESS_HEIGHT = 64.dp
+
+        @Composable
+        fun Brightness() {
+            Box(modifier = Modifier.testTag(BRIGHTNESS).height(BRIGHTNESS_HEIGHT).fillMaxWidth())
+        }
+
+        @Composable
+        fun Tiles(height: Dp) {
+            Box(modifier = Modifier.testTag(TILES).height(height).fillMaxWidth())
+        }
+
+        @Composable
+        fun Media() {
+            Box(modifier = Modifier.testTag(MEDIA).height(MEDIA_HEIGHT).fillMaxWidth())
+        }
+
+        val DpRect.centerY: Dp
+            get() = (top + bottom) / 2
+
+        fun Dp.abs() = if (this > 0.dp) this else -this
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelTest.kt
new file mode 100644
index 0000000..635bada
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelTest.kt
@@ -0,0 +1,142 @@
+/*
+ * 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.qs.panels.ui.viewmodel
+
+import android.content.res.Configuration
+import android.content.res.mainResources
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.flags.setFlagValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QQS
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QS
+import com.android.systemui.media.controls.ui.controller.MediaLocation
+import com.android.systemui.media.controls.ui.controller.mediaHostStatesManager
+import com.android.systemui.media.controls.ui.view.MediaHost
+import com.android.systemui.qs.composefragment.dagger.usingMediaInComposeFragment
+import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shade.shared.flag.DualShade
+import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(ParameterizedAndroidJunit4::class)
+@SmallTest
+class MediaInRowInLandscapeViewModelTest(private val testData: TestData) : SysuiTestCase() {
+
+    private val kosmos = testKosmos().apply { usingMediaInComposeFragment = testData.usingMedia }
+
+    private val underTest by lazy {
+        kosmos.mediaInRowInLandscapeViewModelFactory.create(TESTED_MEDIA_LOCATION)
+    }
+
+    @Before
+    fun setUp() {
+        mSetFlagsRule.setFlagValue(DualShade.FLAG_NAME, testData.shadeMode == ShadeMode.Dual)
+    }
+
+    @Test
+    fun shouldMediaShowInRow() =
+        with(kosmos) {
+            testScope.runTest {
+                underTest.activateIn(testScope)
+
+                shadeRepository.setShadeLayoutWide(testData.shadeMode != ShadeMode.Single)
+                val config =
+                    Configuration(mainResources.configuration).apply {
+                        orientation = testData.orientation
+                        screenLayout = testData.screenLayoutLong
+                    }
+                fakeConfigurationRepository.onConfigurationChange(config)
+                mainResources.configuration.updateFrom(config)
+                mediaHostStatesManager.updateHostState(
+                    testData.mediaLocation,
+                    MediaHost.MediaHostStateHolder().apply { visible = testData.mediaVisible },
+                )
+                runCurrent()
+
+                assertThat(underTest.shouldMediaShowInRow).isEqualTo(testData.mediaInRowExpected)
+            }
+        }
+
+    data class TestData(
+        val usingMedia: Boolean,
+        val shadeMode: ShadeMode,
+        val orientation: Int,
+        val screenLayoutLong: Int,
+        val mediaVisible: Boolean,
+        @MediaLocation val mediaLocation: Int,
+    ) {
+        val mediaInRowExpected: Boolean
+            get() =
+                usingMedia &&
+                    shadeMode == ShadeMode.Single &&
+                    orientation == Configuration.ORIENTATION_LANDSCAPE &&
+                    screenLayoutLong == Configuration.SCREENLAYOUT_LONG_YES &&
+                    mediaVisible &&
+                    mediaLocation == TESTED_MEDIA_LOCATION
+    }
+
+    companion object {
+        private const val TESTED_MEDIA_LOCATION = LOCATION_QS
+
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun data(): Collection<TestData> {
+            val usingMediaValues = setOf(true, false)
+            val shadeModeValues = setOf(ShadeMode.Single, ShadeMode.Split, ShadeMode.Dual)
+            val orientationValues =
+                setOf(Configuration.ORIENTATION_LANDSCAPE, Configuration.ORIENTATION_PORTRAIT)
+            val screenLayoutLongValues =
+                setOf(Configuration.SCREENLAYOUT_LONG_YES, Configuration.SCREENLAYOUT_LONG_NO)
+            val mediaVisibleValues = setOf(true, false)
+            val mediaLocationsValues = setOf(LOCATION_QS, LOCATION_QQS)
+
+            return usingMediaValues.flatMap { usingMedia ->
+                shadeModeValues.flatMap { shadeMode ->
+                    orientationValues.flatMap { orientation ->
+                        screenLayoutLongValues.flatMap { screenLayoutLong ->
+                            mediaVisibleValues.flatMap { mediaVisible ->
+                                mediaLocationsValues.map { mediaLocation ->
+                                    TestData(
+                                        usingMedia,
+                                        shadeMode,
+                                        orientation,
+                                        screenLayoutLong,
+                                        mediaVisible,
+                                        mediaLocation,
+                                    )
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModelTest.kt
new file mode 100644
index 0000000..4ae8589
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModelTest.kt
@@ -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.systemui.qs.panels.ui.viewmodel
+
+import android.content.res.mainResources
+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.common.ui.data.repository.configurationRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.runCurrent
+import com.android.systemui.kosmos.testCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QQS
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QS
+import com.android.systemui.media.controls.ui.controller.MediaLocation
+import com.android.systemui.media.controls.ui.controller.mediaHostStatesManager
+import com.android.systemui.media.controls.ui.view.MediaHost
+import com.android.systemui.qs.composefragment.dagger.usingMediaInComposeFragment
+import com.android.systemui.qs.panels.data.repository.QSColumnsRepository
+import com.android.systemui.qs.panels.data.repository.qsColumnsRepository
+import com.android.systemui.res.R
+import com.android.systemui.shade.shared.flag.DualShade
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class QSColumnsViewModelTest : SysuiTestCase() {
+    private val kosmos =
+        testKosmos().apply {
+            usingMediaInComposeFragment = true
+            testCase.context.orCreateTestableResources.addOverride(
+                R.integer.quick_settings_infinite_grid_num_columns,
+                SINGLE_SPLIT_SHADE_COLUMNS,
+            )
+            testCase.context.orCreateTestableResources.addOverride(
+                R.integer.quick_settings_dual_shade_num_columns,
+                DUAL_SHADE_COLUMNS,
+            )
+            testCase.context.orCreateTestableResources.addOverride(
+                R.integer.quick_settings_split_shade_num_columns,
+                SINGLE_SPLIT_SHADE_COLUMNS,
+            )
+            qsColumnsRepository = QSColumnsRepository(mainResources, configurationRepository)
+        }
+
+    @Test
+    @DisableFlags(DualShade.FLAG_NAME)
+    fun mediaLocationNull_singleOrSplit_alwaysSingleShadeColumns() =
+        with(kosmos) {
+            testScope.runTest {
+                val underTest = qsColumnsViewModelFactory.create(null)
+                underTest.activateIn(testScope)
+
+                setConfigurationForMediaInRow(mediaInRow = false)
+
+                makeMediaVisible(LOCATION_QQS, visible = true)
+                makeMediaVisible(LOCATION_QS, visible = true)
+                runCurrent()
+
+                assertThat(underTest.columns).isEqualTo(SINGLE_SPLIT_SHADE_COLUMNS)
+
+                setConfigurationForMediaInRow(mediaInRow = true)
+                runCurrent()
+
+                assertThat(underTest.columns).isEqualTo(SINGLE_SPLIT_SHADE_COLUMNS)
+            }
+        }
+
+    @Test
+    @EnableFlags(DualShade.FLAG_NAME)
+    fun mediaLocationNull_dualShade_alwaysDualShadeColumns() =
+        with(kosmos) {
+            testScope.runTest {
+                val underTest = qsColumnsViewModelFactory.create(null)
+                underTest.activateIn(testScope)
+
+                setConfigurationForMediaInRow(mediaInRow = false)
+
+                makeMediaVisible(LOCATION_QQS, visible = true)
+                makeMediaVisible(LOCATION_QS, visible = true)
+                runCurrent()
+
+                assertThat(underTest.columns).isEqualTo(DUAL_SHADE_COLUMNS)
+
+                setConfigurationForMediaInRow(mediaInRow = true)
+                runCurrent()
+
+                assertThat(underTest.columns).isEqualTo(DUAL_SHADE_COLUMNS)
+            }
+        }
+
+    @Test
+    @EnableFlags(DualShade.FLAG_NAME)
+    fun mediaLocationQS_dualShade_alwaysDualShadeColumns() =
+        with(kosmos) {
+            testScope.runTest {
+                val underTest = qsColumnsViewModelFactory.create(LOCATION_QS)
+                underTest.activateIn(testScope)
+
+                setConfigurationForMediaInRow(mediaInRow = false)
+
+                makeMediaVisible(LOCATION_QS, visible = true)
+                runCurrent()
+
+                assertThat(underTest.columns).isEqualTo(DUAL_SHADE_COLUMNS)
+
+                setConfigurationForMediaInRow(mediaInRow = true)
+                runCurrent()
+
+                assertThat(underTest.columns).isEqualTo(DUAL_SHADE_COLUMNS)
+            }
+        }
+
+    @Test
+    @EnableFlags(DualShade.FLAG_NAME)
+    fun mediaLocationQQS_dualShade_alwaysDualShadeColumns() =
+        with(kosmos) {
+            testScope.runTest {
+                val underTest = qsColumnsViewModelFactory.create(LOCATION_QQS)
+                underTest.activateIn(testScope)
+
+                setConfigurationForMediaInRow(mediaInRow = false)
+
+                makeMediaVisible(LOCATION_QQS, visible = true)
+                runCurrent()
+
+                assertThat(underTest.columns).isEqualTo(DUAL_SHADE_COLUMNS)
+
+                setConfigurationForMediaInRow(mediaInRow = true)
+                runCurrent()
+
+                assertThat(underTest.columns).isEqualTo(DUAL_SHADE_COLUMNS)
+            }
+        }
+
+    @Test
+    @DisableFlags(DualShade.FLAG_NAME)
+    fun mediaLocationQS_singleOrSplit_halfColumnsOnCorrectConfigurationAndVisible() =
+        with(kosmos) {
+            testScope.runTest {
+                val underTest = qsColumnsViewModelFactory.create(LOCATION_QS)
+                underTest.activateIn(testScope)
+
+                setConfigurationForMediaInRow(mediaInRow = false)
+                runCurrent()
+
+                assertThat(underTest.columns).isEqualTo(SINGLE_SPLIT_SHADE_COLUMNS)
+
+                setConfigurationForMediaInRow(mediaInRow = true)
+                runCurrent()
+
+                assertThat(underTest.columns).isEqualTo(SINGLE_SPLIT_SHADE_COLUMNS)
+
+                makeMediaVisible(LOCATION_QS, visible = true)
+                runCurrent()
+
+                assertThat(underTest.columns).isEqualTo(SINGLE_SPLIT_SHADE_COLUMNS / 2)
+            }
+        }
+
+    @Test
+    @DisableFlags(DualShade.FLAG_NAME)
+    fun mediaLocationQQS_singleOrSplit_halfColumnsOnCorrectConfigurationAndVisible() =
+        with(kosmos) {
+            testScope.runTest {
+                val underTest = qsColumnsViewModelFactory.create(LOCATION_QQS)
+                underTest.activateIn(testScope)
+
+                setConfigurationForMediaInRow(mediaInRow = false)
+                runCurrent()
+
+                assertThat(underTest.columns).isEqualTo(SINGLE_SPLIT_SHADE_COLUMNS)
+
+                setConfigurationForMediaInRow(mediaInRow = true)
+                runCurrent()
+
+                assertThat(underTest.columns).isEqualTo(SINGLE_SPLIT_SHADE_COLUMNS)
+
+                makeMediaVisible(LOCATION_QQS, visible = true)
+                runCurrent()
+
+                assertThat(underTest.columns).isEqualTo(SINGLE_SPLIT_SHADE_COLUMNS / 2)
+            }
+        }
+
+    companion object {
+        private const val SINGLE_SPLIT_SHADE_COLUMNS = 4
+        private const val DUAL_SHADE_COLUMNS = 2
+
+        private fun Kosmos.makeMediaVisible(@MediaLocation location: Int, visible: Boolean) {
+            mediaHostStatesManager.updateHostState(
+                location,
+                MediaHost.MediaHostStateHolder().apply { this.visible = visible },
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt
index ab5a049..3d1265a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt
@@ -24,6 +24,11 @@
 import com.android.systemui.kosmos.testCase
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QQS
+import com.android.systemui.media.controls.ui.controller.MediaLocation
+import com.android.systemui.media.controls.ui.controller.mediaHostStatesManager
+import com.android.systemui.media.controls.ui.view.MediaHost
+import com.android.systemui.qs.composefragment.dagger.usingMediaInComposeFragment
 import com.android.systemui.qs.panels.domain.interactor.qsPreferencesInteractor
 import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
 import com.android.systemui.qs.pipeline.shared.TileSpec
@@ -67,6 +72,7 @@
                 4,
             )
             fakeConfigurationRepository.onConfigurationChange()
+            usingMediaInComposeFragment = true
         }
 
     private val underTest =
@@ -145,6 +151,33 @@
             }
         }
 
+    @Test
+    fun mediaVisibleInLandscape_doubleRows_halfColumns() =
+        with(kosmos) {
+            testScope.runTest {
+                setRows(1)
+                assertThat(underTest.columns).isEqualTo(4)
+                // All tiles in 4 columns (but we only show the first 3 tiles)
+                // [1] [2] [3 3]
+                // [4] [5 5]
+                // [6 6] [7] [8]
+                // [9 9]
+
+                runCurrent()
+                assertThat(underTest.tileViewModels.map { it.tile.spec }).isEqualTo(tiles.take(3))
+
+                makeMediaVisible(LOCATION_QQS, visible = true)
+                setConfigurationForMediaInRow(mediaInRow = true)
+                runCurrent()
+
+                assertThat(underTest.columns).isEqualTo(2)
+                // Tiles in 4 columns
+                // [1] [2]
+                // [3 3]
+                assertThat(underTest.tileViewModels.map { it.tile.spec }).isEqualTo(tiles.take(3))
+            }
+        }
+
     private fun Kosmos.setTiles(tiles: List<TileSpec>) {
         currentTilesInteractor.setTiles(tiles)
     }
@@ -163,5 +196,12 @@
     private companion object {
         const val PREFIX_SMALL = "small"
         const val PREFIX_LARGE = "large"
+
+        private fun Kosmos.makeMediaVisible(@MediaLocation location: Int, visible: Boolean) {
+            mediaHostStatesManager.updateHostState(
+                location,
+                MediaHost.MediaHostStateHolder().apply { this.visible = visible },
+            )
+        }
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
index 0d12483..53708fd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
@@ -23,7 +23,6 @@
 import static org.junit.Assert.assertFalse;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -160,9 +159,7 @@
         mTestableLooper.processAllMessages();
 
         ArgumentCaptor<Runnable> onStartRecordingClicked = ArgumentCaptor.forClass(Runnable.class);
-        verify(mController).createScreenRecordDialog(any(), eq(mFeatureFlags),
-                eq(mDialogTransitionAnimator), eq(mActivityStarter),
-                onStartRecordingClicked.capture());
+        verify(mController).createScreenRecordDialog(onStartRecordingClicked.capture());
 
         // When starting the recording, we collapse the shade and disable the dialog animation.
         assertNotNull(onStartRecordingClicked.getValue());
@@ -298,14 +295,13 @@
     public void showingDialogPrompt_logsMediaProjectionPermissionRequested() {
         when(mController.isStarting()).thenReturn(false);
         when(mController.isRecording()).thenReturn(false);
-        when(mController.createScreenRecordDialog(any(), any(), any(), any(), any()))
+        when(mController.createScreenRecordDialog(any()))
                 .thenReturn(mPermissionDialogPrompt);
 
         mTile.handleClick(null /* view */);
         mTestableLooper.processAllMessages();
 
-        verify(mController).createScreenRecordDialog(any(), eq(mFeatureFlags),
-                eq(mDialogTransitionAnimator), eq(mActivityStarter), any());
+        verify(mController).createScreenRecordDialog(any());
         var onDismissAction = ArgumentCaptor.forClass(ActivityStarter.OnDismissAction.class);
         verify(mKeyguardDismissUtil).executeWhenUnlocked(
                 onDismissAction.capture(), anyBoolean(), anyBoolean());
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapperTest.kt
new file mode 100644
index 0000000..2ac3e08
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapperTest.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.qs.tiles.impl.notes.domain
+
+import android.graphics.drawable.TestStubDrawable
+import android.widget.Button
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.impl.notes.domain.model.NotesTileModel
+import com.android.systemui.qs.tiles.impl.notes.qsNotesTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import kotlin.test.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NotesTileMapperTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val qsTileConfig = kosmos.qsNotesTileConfig
+
+    private val mapper by lazy {
+        NotesTileMapper(
+            context.orCreateTestableResources
+                .apply { addOverride(R.drawable.ic_qs_notes, TestStubDrawable()) }
+                .resources,
+            context.theme,
+        )
+    }
+
+    @Test
+    fun mappedStateMatchesModel() {
+        val inputModel = NotesTileModel
+
+        val outputState = mapper.map(qsTileConfig, inputModel)
+
+        val expectedState = createNotesTileState()
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    private fun createNotesTileState(): QSTileState =
+        QSTileState(
+            Icon.Loaded(context.getDrawable(R.drawable.ic_qs_notes)!!, null),
+            R.drawable.ic_qs_notes,
+            context.getString(R.string.quick_settings_notes_label),
+            QSTileState.ActivationState.INACTIVE,
+            /* secondaryLabel= */ null,
+            setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
+            context.getString(R.string.quick_settings_notes_label),
+            /* stateDescription= */ null,
+            QSTileState.SideViewIcon.Chevron,
+            QSTileState.EnabledState.ENABLED,
+            Button::class.qualifiedName,
+        )
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/notes/domain/interactor/NotesTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/notes/domain/interactor/NotesTileDataInteractorTest.kt
new file mode 100644
index 0000000..35d6d5a
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/notes/domain/interactor/NotesTileDataInteractorTest.kt
@@ -0,0 +1,103 @@
+/*
+ * 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.qs.tiles.impl.notes.domain.interactor
+
+import android.os.UserHandle
+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.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.toCollection
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NotesTileDataInteractorTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val testScope = kosmos.testScope
+    private val testUser = UserHandle.of(1)
+    private lateinit var underTest: NotesTileDataInteractor
+
+
+    @EnableFlags(Flags.FLAG_NOTES_ROLE_QS_TILE)
+    @Test
+    fun availability_qsFlagEnabled_notesRoleEnabled_returnTrue() =
+        testScope.runTest {
+            underTest = NotesTileDataInteractor(isNoteTaskEnabled = true)
+
+            val availability = underTest.availability(testUser).toCollection(mutableListOf())
+
+            assertThat(availability).containsExactly(true)
+        }
+
+    @DisableFlags(Flags.FLAG_NOTES_ROLE_QS_TILE)
+    @Test
+    fun availability_qsFlagDisabled_notesRoleEnabled_returnFalse() =
+        testScope.runTest {
+            underTest = NotesTileDataInteractor(isNoteTaskEnabled = true)
+
+            val availability = underTest.availability(testUser).toCollection(mutableListOf())
+
+            assertThat(availability).containsExactly(false)
+        }
+
+    @EnableFlags(Flags.FLAG_NOTES_ROLE_QS_TILE)
+    @Test
+    fun availability_qsFlagEnabled_notesRoleDisabled_returnFalse() =
+        testScope.runTest {
+            underTest = NotesTileDataInteractor(isNoteTaskEnabled = false)
+
+            val availability = underTest.availability(testUser).toCollection(mutableListOf())
+
+            assertThat(availability).containsExactly(false)
+        }
+
+    @DisableFlags(Flags.FLAG_NOTES_ROLE_QS_TILE)
+    @Test
+    fun availability_qsFlagDisabled_notesRoleDisabled_returnFalse() =
+        testScope.runTest {
+            underTest = NotesTileDataInteractor(isNoteTaskEnabled = false)
+
+            val availability = underTest.availability(testUser).toCollection(mutableListOf())
+
+            assertThat(availability).containsExactly(false)
+        }
+
+    @Test
+    fun tileData_notEmpty() = runTest {
+        underTest = NotesTileDataInteractor(isNoteTaskEnabled = true)
+        val flowValue by
+        collectLastValue(underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)))
+
+        runCurrent()
+
+        assertThat(flowValue).isNotNull()
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/notes/domain/interactor/NotesTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/notes/domain/interactor/NotesTileUserActionInteractorTest.kt
new file mode 100644
index 0000000..54911e6
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/notes/domain/interactor/NotesTileUserActionInteractorTest.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.qs.tiles.impl.notes.domain.interactor
+
+import android.content.Intent
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.notetask.NoteTaskController
+import com.android.systemui.notetask.NoteTaskEntryPoint
+import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.impl.notes.domain.model.NotesTileModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NotesTileUserActionInteractorTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val testScope = kosmos.testScope
+    private val inputHandler = FakeQSTileIntentUserInputHandler()
+    private val panelInteractor = mock<PanelInteractor>()
+    private val noteTaskController = mock<NoteTaskController>()
+
+    private lateinit var underTest: NotesTileUserActionInteractor
+
+    @Before
+    fun setUp() {
+        underTest = NotesTileUserActionInteractor(inputHandler, panelInteractor, noteTaskController)
+    }
+
+    @Test
+    fun handleClick_launchDefaultNotesApp() =
+        testScope.runTest {
+            underTest.handleInput(QSTileInputTestKtx.click(NotesTileModel))
+
+            verify(noteTaskController).showNoteTask(NoteTaskEntryPoint.QS_NOTES_TILE)
+        }
+
+    @Test
+    fun handleLongClick_launchSettings() =
+        testScope.runTest {
+            underTest.handleInput(QSTileInputTestKtx.longClick(NotesTileModel))
+
+            QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+                assertThat(it.intent.action).isEqualTo(Intent.ACTION_MANAGE_DEFAULT_APP)
+            }
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractorTest.kt
index 899122d..0b56d7b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractorTest.kt
@@ -23,29 +23,27 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.animation.Expandable
-import com.android.systemui.flags.featureFlagsClassic
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction
-import com.android.systemui.plugins.activityStarter
 import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
 import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
 import com.android.systemui.screenrecord.RecordingController
 import com.android.systemui.screenrecord.data.model.ScreenRecordModel
 import com.android.systemui.screenrecord.data.repository.ScreenRecordRepositoryImpl
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -54,24 +52,11 @@
     private val testScope = kosmos.testScope
     private val keyguardInteractor = kosmos.keyguardInteractor
     private val dialogTransitionAnimator = mock<DialogTransitionAnimator>()
-    private val featureFlags = kosmos.featureFlagsClassic
-    private val activityStarter = kosmos.activityStarter
     private val keyguardDismissUtil = mock<KeyguardDismissUtil>()
     private val panelInteractor = mock<PanelInteractor>()
     private val dialog = mock<Dialog>()
     private val recordingController =
-        mock<RecordingController> {
-            whenever(
-                    createScreenRecordDialog(
-                        eq(context),
-                        eq(featureFlags),
-                        eq(dialogTransitionAnimator),
-                        eq(activityStarter),
-                        any()
-                    )
-                )
-                .thenReturn(dialog)
-        }
+        mock<RecordingController> { on { createScreenRecordDialog(any()) } doReturn dialog }
 
     private val screenRecordRepository =
         ScreenRecordRepositoryImpl(
@@ -81,7 +66,6 @@
 
     private val underTest =
         ScreenRecordTileUserActionInteractor(
-            context,
             testScope.testScheduler,
             testScope.testScheduler,
             screenRecordRepository,
@@ -91,8 +75,6 @@
             dialogTransitionAnimator,
             panelInteractor,
             mock<MediaProjectionMetricsLogger>(),
-            featureFlags,
-            activityStarter,
         )
 
     @Test
@@ -120,22 +102,16 @@
         underTest.handleInput(QSTileInputTestKtx.click(recordingModel))
         val onStartRecordingClickedCaptor = argumentCaptor<Runnable>()
         verify(recordingController)
-            .createScreenRecordDialog(
-                eq(context),
-                eq(featureFlags),
-                eq(dialogTransitionAnimator),
-                eq(activityStarter),
-                onStartRecordingClickedCaptor.capture()
-            )
+            .createScreenRecordDialog(onStartRecordingClickedCaptor.capture())
 
         val onDismissActionCaptor = argumentCaptor<OnDismissAction>()
         verify(keyguardDismissUtil)
             .executeWhenUnlocked(onDismissActionCaptor.capture(), eq(false), eq(true))
-        onDismissActionCaptor.value.onDismiss()
+        onDismissActionCaptor.lastValue.onDismiss()
         verify(dialog).show() // because the view was null
 
         // When starting the recording, we collapse the shade and disable the dialog animation.
-        onStartRecordingClickedCaptor.value.run()
+        onStartRecordingClickedCaptor.lastValue.run()
         verify(dialogTransitionAnimator).disableAllCurrentDialogsExitAnimations()
         verify(panelInteractor).collapsePanels()
     }
@@ -145,9 +121,9 @@
      */
     @Test
     fun handleClickFromView_whenDoingNothing_whenKeyguardNotShowing_showDialogFromView() = runTest {
-        val expandable = mock<Expandable>()
         val controller = mock<DialogTransitionAnimator.Controller>()
-        whenever(expandable.dialogTransitionController(any())).thenReturn(controller)
+        val expandable =
+            mock<Expandable> { on { dialogTransitionController(any()) } doReturn controller }
 
         kosmos.fakeKeyguardRepository.setKeyguardShowing(false)
 
@@ -158,18 +134,12 @@
         )
         val onStartRecordingClickedCaptor = argumentCaptor<Runnable>()
         verify(recordingController)
-            .createScreenRecordDialog(
-                eq(context),
-                eq(featureFlags),
-                eq(dialogTransitionAnimator),
-                eq(activityStarter),
-                onStartRecordingClickedCaptor.capture()
-            )
+            .createScreenRecordDialog(onStartRecordingClickedCaptor.capture())
 
         val onDismissActionCaptor = argumentCaptor<OnDismissAction>()
         verify(keyguardDismissUtil)
             .executeWhenUnlocked(onDismissActionCaptor.capture(), eq(false), eq(true))
-        onDismissActionCaptor.value.onDismiss()
+        onDismissActionCaptor.lastValue.onDismiss()
         verify(dialogTransitionAnimator).show(eq(dialog), eq(controller), eq(true))
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelTest.kt
index c3a777c..9396445 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelTest.kt
@@ -21,7 +21,6 @@
 import androidx.test.filters.SmallTest
 import com.android.compose.animation.scene.Back
 import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
 import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
@@ -89,12 +88,8 @@
             underTest.activateIn(this)
 
             assertThat(
-                    (actions?.get(
-                            Swipe(
-                                direction = SwipeDirection.Down,
-                                fromSource = SceneContainerEdge.TopLeft,
-                            )
-                        ) as? UserActionResult.ReplaceByOverlay)
+                    (actions?.get(Swipe.Down(fromSource = SceneContainerEdge.TopLeft))
+                            as? UserActionResult.ReplaceByOverlay)
                         ?.overlay
                 )
                 .isEqualTo(Overlays.NotificationsShade)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt
index f32894d..24f6b6d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt
@@ -31,6 +31,7 @@
 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.qs.composefragment.dagger.usingMediaInComposeFragment
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.domain.startable.sceneContainerStartable
 import com.android.systemui.scene.shared.model.Overlays
@@ -55,7 +56,10 @@
 @EnableFlags(DualShade.FLAG_NAME)
 class QuickSettingsShadeOverlayContentViewModelTest : SysuiTestCase() {
 
-    private val kosmos = testKosmos()
+    private val kosmos =
+        testKosmos().apply {
+            usingMediaInComposeFragment = false // This is not for the compose fragment
+        }
     private val testScope = kosmos.testScope
     private val sceneInteractor = kosmos.sceneInteractor
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsUserActionsViewModelTest.kt
index 62b6391..d5fbe49 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsUserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsUserActionsViewModelTest.kt
@@ -22,7 +22,6 @@
 import com.android.compose.animation.scene.Back
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
 import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
@@ -98,9 +97,8 @@
                 .isEqualTo(
                     mapOf(
                         Back to UserActionResult(Scenes.Shade),
-                        Swipe(SwipeDirection.Up) to UserActionResult(Scenes.Shade),
-                        Swipe(fromSource = Edge.Bottom, direction = SwipeDirection.Up) to
-                            UserActionResult(SceneFamilies.Home),
+                        Swipe.Up to UserActionResult(Scenes.Shade),
+                        Swipe.Up(fromSource = Edge.Bottom) to UserActionResult(SceneFamilies.Home),
                     )
                 )
             assertThat(homeScene).isEqualTo(Scenes.Gone)
@@ -125,9 +123,8 @@
                 .isEqualTo(
                     mapOf(
                         Back to UserActionResult(Scenes.Lockscreen),
-                        Swipe(SwipeDirection.Up) to UserActionResult(Scenes.Lockscreen),
-                        Swipe(fromSource = Edge.Bottom, direction = SwipeDirection.Up) to
-                            UserActionResult(SceneFamilies.Home),
+                        Swipe.Up to UserActionResult(Scenes.Lockscreen),
+                        Swipe.Up(fromSource = Edge.Bottom) to UserActionResult(SceneFamilies.Home),
                     )
                 )
             assertThat(homeScene).isEqualTo(Scenes.Lockscreen)
@@ -154,9 +151,8 @@
                 .isEqualTo(
                     mapOf(
                         Back to UserActionResult(Scenes.Shade),
-                        Swipe(SwipeDirection.Up) to UserActionResult(Scenes.Shade),
-                        Swipe(fromSource = Edge.Bottom, direction = SwipeDirection.Up) to
-                            UserActionResult(SceneFamilies.Home),
+                        Swipe.Up to UserActionResult(Scenes.Shade),
+                        Swipe.Up(fromSource = Edge.Bottom) to UserActionResult(SceneFamilies.Home),
                     )
                 )
             assertThat(homeScene).isEqualTo(Scenes.Gone)
@@ -178,9 +174,8 @@
                 .isEqualTo(
                     mapOf(
                         Back to UserActionResult(Scenes.Shade),
-                        Swipe(SwipeDirection.Up) to UserActionResult(Scenes.Shade),
-                        Swipe(fromSource = Edge.Bottom, direction = SwipeDirection.Up) to
-                            UserActionResult(SceneFamilies.Home),
+                        Swipe.Up to UserActionResult(Scenes.Shade),
+                        Swipe.Up(fromSource = Edge.Bottom) to UserActionResult(SceneFamilies.Home),
                     )
                 )
             assertThat(homeScene).isEqualTo(Scenes.Lockscreen)
@@ -214,9 +209,8 @@
                 .isEqualTo(
                     mapOf(
                         Back to UserActionResult(Scenes.Shade),
-                        Swipe(SwipeDirection.Up) to UserActionResult(Scenes.Shade),
-                        Swipe(fromSource = Edge.Bottom, direction = SwipeDirection.Up) to
-                            UserActionResult(SceneFamilies.Home),
+                        Swipe.Up to UserActionResult(Scenes.Shade),
+                        Swipe.Up(fromSource = Edge.Bottom) to UserActionResult(SceneFamilies.Home),
                     )
                 )
             assertThat(homeScene).isEqualTo(Scenes.Gone)
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/scene/ui/viewmodel/GoneUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModelTest.kt
index 47fae9f..bb2e941 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModelTest.kt
@@ -23,7 +23,6 @@
 import androidx.test.filters.SmallTest
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.SysuiTestCase
@@ -71,8 +70,7 @@
             shadeRepository.setShadeLayoutWide(true)
             runCurrent()
 
-            assertThat(userActions?.get(Swipe(SwipeDirection.Down))?.transitionKey)
-                .isEqualTo(ToSplitShade)
+            assertThat(userActions?.get(Swipe.Down)?.transitionKey).isEqualTo(ToSplitShade)
         }
 
     @Test
@@ -83,7 +81,7 @@
             shadeRepository.setShadeLayoutWide(false)
             runCurrent()
 
-            assertThat(userActions?.get(Swipe(SwipeDirection.Down))?.transitionKey).isNull()
+            assertThat(userActions?.get(Swipe.Down)?.transitionKey).isNull()
         }
 
     @Test
@@ -94,7 +92,7 @@
             shadeRepository.setShadeLayoutWide(true)
             runCurrent()
 
-            assertThat(userActions?.get(Swipe(SwipeDirection.Down))?.transitionKey).isNull()
+            assertThat(userActions?.get(Swipe.Down)?.transitionKey).isNull()
         }
 
     @Test
@@ -132,6 +130,6 @@
         }
 
     private fun swipeDownFromTopWithTwoFingers(): UserAction {
-        return Swipe(direction = SwipeDirection.Down, pointerCount = 2, fromSource = Edge.Top)
+        return Swipe.Down(pointerCount = 2, fromSource = Edge.Top)
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/UserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/UserActionsViewModelTest.kt
index 972afb5..d5f57da 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/UserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/UserActionsViewModelTest.kt
@@ -22,7 +22,6 @@
 import androidx.test.filters.SmallTest
 import com.android.compose.animation.scene.Back
 import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.SysuiTestCase
@@ -77,7 +76,7 @@
             val expected1 =
                 mapOf(
                     Back to UserActionResult(toScene = Scenes.Gone),
-                    Swipe(SwipeDirection.Up) to UserActionResult(toScene = Scenes.Shade)
+                    Swipe.Up to UserActionResult(toScene = Scenes.Shade),
                 )
             underTest.upstream.value = expected1
             runCurrent()
@@ -86,7 +85,7 @@
             val expected2 =
                 mapOf(
                     Back to UserActionResult(toScene = Scenes.Lockscreen),
-                    Swipe(SwipeDirection.Down) to UserActionResult(toScene = Scenes.Shade)
+                    Swipe.Down to UserActionResult(toScene = Scenes.Shade),
                 )
             underTest.upstream.value = expected2
             runCurrent()
@@ -104,7 +103,7 @@
             val expected =
                 mapOf(
                     Back to UserActionResult(toScene = Scenes.Lockscreen),
-                    Swipe(SwipeDirection.Down) to UserActionResult(toScene = Scenes.Shade)
+                    Swipe.Down to UserActionResult(toScene = Scenes.Shade),
                 )
             underTest.upstream.value = expected
             runCurrent()
@@ -120,7 +119,7 @@
         val upstream = MutableStateFlow<Map<UserAction, UserActionResult>>(emptyMap())
 
         override suspend fun hydrateActions(
-            setActions: (Map<UserAction, UserActionResult>) -> Unit,
+            setActions: (Map<UserAction, UserActionResult>) -> Unit
         ) {
             upstream.collect { setActions(it) }
         }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModelTest.kt
index fcb366b..bbfc66a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModelTest.kt
@@ -92,10 +92,7 @@
                 AuthenticationMethodModel.Pin
             )
 
-            assertThat(
-                    (actions?.get(Swipe(SwipeDirection.Up)) as? UserActionResult.ChangeScene)
-                        ?.toScene
-                )
+            assertThat((actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene)
                 .isEqualTo(SceneFamilies.Home)
             assertThat(homeScene).isEqualTo(Scenes.Lockscreen)
         }
@@ -110,10 +107,7 @@
             )
             setDeviceEntered(true)
 
-            assertThat(
-                    (actions?.get(Swipe(SwipeDirection.Up)) as? UserActionResult.ChangeScene)
-                        ?.toScene
-                )
+            assertThat((actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene)
                 .isEqualTo(SceneFamilies.Home)
             assertThat(homeScene).isEqualTo(Scenes.Gone)
         }
@@ -128,10 +122,7 @@
             )
             kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)
 
-            assertThat(
-                    (actions?.get(Swipe(SwipeDirection.Up)) as? UserActionResult.ChangeScene)
-                        ?.toScene
-                )
+            assertThat((actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene)
                 .isEqualTo(SceneFamilies.Home)
             assertThat(homeScene).isEqualTo(Scenes.Gone)
         }
@@ -147,10 +138,7 @@
             )
             sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
 
-            assertThat(
-                    (actions?.get(Swipe(SwipeDirection.Up)) as? UserActionResult.ChangeScene)
-                        ?.toScene
-                )
+            assertThat((actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene)
                 .isEqualTo(SceneFamilies.Home)
             assertThat(homeScene).isEqualTo(Scenes.Lockscreen)
         }
@@ -167,10 +155,7 @@
             runCurrent()
             sceneInteractor.changeScene(Scenes.Gone, "reason")
 
-            assertThat(
-                    (actions?.get(Swipe(SwipeDirection.Up)) as? UserActionResult.ChangeScene)
-                        ?.toScene
-                )
+            assertThat((actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene)
                 .isEqualTo(SceneFamilies.Home)
             assertThat(homeScene).isEqualTo(Scenes.Gone)
         }
@@ -182,8 +167,7 @@
             shadeRepository.setShadeLayoutWide(true)
             runCurrent()
 
-            assertThat(actions?.get(Swipe(SwipeDirection.Up))?.transitionKey)
-                .isEqualTo(ToSplitShade)
+            assertThat(actions?.get(Swipe.Up)?.transitionKey).isEqualTo(ToSplitShade)
         }
 
     @Test
@@ -193,7 +177,7 @@
             shadeRepository.setShadeLayoutWide(false)
             runCurrent()
 
-            assertThat(actions?.get(Swipe(SwipeDirection.Up))?.transitionKey).isNull()
+            assertThat(actions?.get(Swipe.Up)?.transitionKey).isNull()
         }
 
     @Test
@@ -202,10 +186,7 @@
             overrideResource(R.bool.config_use_split_notification_shade, true)
             kosmos.shadeStartable.start()
             val actions by collectLastValue(underTest.actions)
-            assertThat(
-                    (actions?.get(Swipe(SwipeDirection.Down)) as? UserActionResult.ChangeScene)
-                        ?.toScene
-                )
+            assertThat((actions?.get(Swipe.Down) as? UserActionResult.ChangeScene)?.toScene)
                 .isNull()
         }
 
@@ -215,10 +196,7 @@
             overrideResource(R.bool.config_use_split_notification_shade, false)
             kosmos.shadeStartable.start()
             val actions by collectLastValue(underTest.actions)
-            assertThat(
-                    (actions?.get(Swipe(SwipeDirection.Down)) as? UserActionResult.ChangeScene)
-                        ?.toScene
-                )
+            assertThat((actions?.get(Swipe.Down) as? UserActionResult.ChangeScene)?.toScene)
                 .isEqualTo(Scenes.QuickSettings)
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/OperatorNameViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/OperatorNameViewControllerTest.kt
index d6b3b91..72a2ce5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/OperatorNameViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/OperatorNameViewControllerTest.kt
@@ -85,7 +85,6 @@
 
         underTest =
             OperatorNameViewController.Factory(
-                    darkIconDispatcher,
                     tunerService,
                     telephonyManager,
                     keyguardUpdateMonitor,
@@ -94,7 +93,7 @@
                     subscriptionManagerProxy,
                     javaAdapter,
                 )
-                .create(view)
+                .create(view, darkIconDispatcher)
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
index 60a1855..fb7252b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar;
 
+import static android.app.Notification.CATEGORY_CALL;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static junit.framework.Assert.assertEquals;
@@ -197,6 +199,19 @@
     }
 
     @Test
+    public void testContentDescForNotification_noNotifContent() {
+        Notification n = new Notification.Builder(mContext, "test")
+                .setSmallIcon(0)
+                .setContentTitle("hello")
+                .setCategory(CATEGORY_CALL)
+                .build();
+        assertThat(NotificationContentDescription.contentDescForNotification(mContext, n)
+                .toString()).startsWith("com.android.systemui.tests notification");
+        assertThat(NotificationContentDescription.contentDescForNotification(mContext, n)
+                .toString()).doesNotContain("hello");
+    }
+
+    @Test
     @EnableFlags({Flags.FLAG_MODES_UI, Flags.FLAG_MODES_UI_ICONS})
     public void setIcon_withPreloaded_usesPreloaded() {
         Icon mockIcon = mock(Icon.class);
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 32f4164..1b41329 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
@@ -97,7 +97,7 @@
 
             assertThat(latest).hasSize(1)
             val chip = latest!![0]
-            assertThat(chip).isInstanceOf(OngoingActivityChipModel.Shown.ShortTimeDelta::class.java)
+            assertThat(chip).isInstanceOf(OngoingActivityChipModel.Shown.IconOnly::class.java)
             assertThat(chip.icon).isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarView(icon))
         }
 
@@ -168,8 +168,7 @@
 
     companion object {
         fun assertIsNotifChip(latest: OngoingActivityChipModel?, expectedIcon: StatusBarIconView) {
-            assertThat(latest)
-                .isInstanceOf(OngoingActivityChipModel.Shown.ShortTimeDelta::class.java)
+            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.IconOnly::class.java)
             assertThat((latest as OngoingActivityChipModel.Shown).icon)
                 .isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarView(expectedIcon))
         }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt
index 5be5fb4..c06da4b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt
@@ -23,9 +23,11 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.display.data.repository.displayRepository
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.data.repository.fakeLightBarControllerStore
 import com.android.systemui.statusbar.data.repository.fakePrivacyDotWindowControllerStore
 import com.android.systemui.testKosmos
 import com.google.common.truth.Expect
+import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
@@ -48,6 +50,7 @@
     private val fakeOrchestratorFactory = kosmos.fakeStatusBarOrchestratorFactory
     private val fakeInitializerStore = kosmos.fakeStatusBarInitializerStore
     private val fakePrivacyDotStore = kosmos.fakePrivacyDotWindowControllerStore
+    private val fakeLightBarStore = kosmos.fakeLightBarControllerStore
     // Lazy, so that @EnableFlags is set before initializer is instantiated.
     private val underTest by lazy { kosmos.multiDisplayStatusBarStarter }
 
@@ -95,6 +98,31 @@
         }
 
     @Test
+    fun start_doesNotStartLightBarControllerForCurrentDisplays() =
+        testScope.runTest {
+            fakeDisplayRepository.addDisplay(displayId = 1)
+            fakeDisplayRepository.addDisplay(displayId = 2)
+
+            underTest.start()
+            runCurrent()
+
+            verify(fakeLightBarStore.forDisplay(displayId = 1), never()).start()
+            verify(fakeLightBarStore.forDisplay(displayId = 2), never()).start()
+        }
+
+    @Test
+    fun start_createsLightBarControllerForCurrentDisplays() =
+        testScope.runTest {
+            fakeDisplayRepository.addDisplay(displayId = 1)
+            fakeDisplayRepository.addDisplay(displayId = 2)
+
+            underTest.start()
+            runCurrent()
+
+            assertThat(fakeLightBarStore.perDisplayMocks.keys).containsExactly(1, 2)
+        }
+
+    @Test
     fun start_doesNotStartPrivacyDotForDefaultDisplay() =
         testScope.runTest {
             fakeDisplayRepository.addDisplay(displayId = Display.DEFAULT_DISPLAY)
@@ -145,6 +173,30 @@
         }
 
     @Test
+    fun displayAdded_lightBarForNewDisplayIsCreated() =
+        testScope.runTest {
+            underTest.start()
+            runCurrent()
+
+            fakeDisplayRepository.addDisplay(displayId = 3)
+            runCurrent()
+
+            assertThat(fakeLightBarStore.perDisplayMocks.keys).containsExactly(3)
+        }
+
+    @Test
+    fun displayAdded_lightBarForNewDisplayIsNotStarted() =
+        testScope.runTest {
+            underTest.start()
+            runCurrent()
+
+            fakeDisplayRepository.addDisplay(displayId = 3)
+            runCurrent()
+
+            verify(fakeLightBarStore.forDisplay(displayId = 3), never()).start()
+        }
+
+    @Test
     fun displayAddedDuringStart_initializerForNewDisplayIsStarted() =
         testScope.runTest {
             underTest.start()
@@ -178,4 +230,26 @@
 
             verify(fakePrivacyDotStore.forDisplay(displayId = 3)).start()
         }
+
+    @Test
+    fun displayAddedDuringStart_lightBarForNewDisplayIsCreated() =
+        testScope.runTest {
+            underTest.start()
+
+            fakeDisplayRepository.addDisplay(displayId = 3)
+            runCurrent()
+
+            assertThat(fakeLightBarStore.perDisplayMocks.keys).containsExactly(3)
+        }
+
+    @Test
+    fun displayAddedDuringStart_lightBarForNewDisplayIsNotStarted() =
+        testScope.runTest {
+            underTest.start()
+
+            fakeDisplayRepository.addDisplay(displayId = 3)
+            runCurrent()
+
+            verify(fakeLightBarStore.forDisplay(displayId = 3), never()).start()
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayDarkIconDispatcherStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayDarkIconDispatcherStoreTest.kt
new file mode 100644
index 0000000..a2c3c66
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayDarkIconDispatcherStoreTest.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.never
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+class MultiDisplayDarkIconDispatcherStoreTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+    private val testScope = kosmos.testScope
+    private val fakeDisplayRepository = kosmos.displayRepository
+
+    // Lazy so that @EnableFlags has time to run before underTest is instantiated.
+    private val underTest by lazy { kosmos.multiDisplayDarkIconDispatcherStore }
+
+    @Before
+    fun start() {
+        underTest.start()
+    }
+
+    @Before fun addDisplays() = runBlocking { fakeDisplayRepository.addDisplay(DEFAULT_DISPLAY) }
+
+    @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/notification/NotificationContentDescriptionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationContentDescriptionTest.kt
index 12473cb..896f940 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationContentDescriptionTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationContentDescriptionTest.kt
@@ -34,31 +34,9 @@
     private val TICKER = "this is a ticker"
 
     @Test
-    fun notificationWithAllDifferentFields_descriptionIsTitle() {
+    fun notificationWithAllDifferentFields_descriptionIsAppName() {
         val n = createNotification(TITLE, TEXT, TICKER)
         val description = contentDescForNotification(context, n)
-        assertThat(description).isEqualTo(createDescriptionText(n, TITLE))
-    }
-
-    @Test
-    fun notificationWithAllDifferentFields_titleMatchesAppName_descriptionIsText() {
-        val n = createNotification(getTestAppName(), TEXT, TICKER)
-        val description = contentDescForNotification(context, n)
-        assertThat(description).isEqualTo(createDescriptionText(n, TEXT))
-    }
-
-    @Test
-    fun notificationWithAllDifferentFields_titleMatchesAppNameNoText_descriptionIsTicker() {
-        val n = createNotification(getTestAppName(), null, TICKER)
-        val description = contentDescForNotification(context, n)
-        assertThat(description).isEqualTo(createDescriptionText(n, TICKER))
-    }
-
-    @Test
-    fun notificationWithAllDifferentFields_titleMatchesAppNameNoTextNoTicker_descriptionEmpty() {
-        val appName = getTestAppName()
-        val n = createNotification(appName, null, null)
-        val description = contentDescForNotification(context, n)
         assertThat(description).isEqualTo(createDescriptionText(n, ""))
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
index 9367a93..46c360a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
@@ -110,14 +110,10 @@
                 lastSleepReason = WakeSleepReason.OTHER,
             )
             keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(
-                    transitionState = TransitionState.STARTED,
-                )
+                TransitionStep(transitionState = TransitionState.STARTED)
             )
             keyguardRepository.setDozeTransitionModel(
-                DozeTransitionModel(
-                    to = DozeStateModel.DOZE_AOD,
-                )
+                DozeTransitionModel(to = DozeStateModel.DOZE_AOD)
             )
             val animationsEnabled by collectLastValue(underTest.animationsEnabled)
             runCurrent()
@@ -133,14 +129,10 @@
                 lastSleepReason = WakeSleepReason.OTHER,
             )
             keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(
-                    transitionState = TransitionState.STARTED,
-                )
+                TransitionStep(transitionState = TransitionState.STARTED)
             )
             keyguardRepository.setDozeTransitionModel(
-                DozeTransitionModel(
-                    to = DozeStateModel.DOZE_PULSING,
-                )
+                DozeTransitionModel(to = DozeStateModel.DOZE_PULSING)
             )
             val animationsEnabled by collectLastValue(underTest.animationsEnabled)
             runCurrent()
@@ -201,9 +193,7 @@
                 lastSleepReason = WakeSleepReason.OTHER,
             )
             keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(
-                    transitionState = TransitionState.STARTED,
-                )
+                TransitionStep(transitionState = TransitionState.STARTED)
             )
             val animationsEnabled by collectLastValue(underTest.animationsEnabled)
             runCurrent()
@@ -216,9 +206,7 @@
             val animationsEnabled by collectLastValue(underTest.animationsEnabled)
 
             keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(
-                    transitionState = TransitionState.STARTED,
-                )
+                TransitionStep(transitionState = TransitionState.STARTED)
             )
             keyguardRepository.setKeyguardShowing(true)
             runCurrent()
@@ -234,13 +222,10 @@
     @Test
     fun iconColors_testsDarkBounds() =
         testScope.runTest {
-            darkIconRepository.darkState.value =
-                SysuiDarkIconDispatcher.DarkChange(
-                    emptyList(),
-                    0f,
-                    0xAABBCC,
-                )
-            val iconColorsLookup by collectLastValue(underTest.iconColors)
+            val displayId = 123
+            darkIconRepository.darkState(displayId).value =
+                SysuiDarkIconDispatcher.DarkChange(emptyList(), 0f, 0xAABBCC)
+            val iconColorsLookup by collectLastValue(underTest.iconColors(displayId))
             assertThat(iconColorsLookup).isNotNull()
 
             val iconColors = iconColorsLookup?.iconColors(Rect())
@@ -257,13 +242,10 @@
     @Test
     fun iconColors_staticDrawableColor_notInDarkTintArea() =
         testScope.runTest {
-            darkIconRepository.darkState.value =
-                SysuiDarkIconDispatcher.DarkChange(
-                    listOf(Rect(0, 0, 5, 5)),
-                    0f,
-                    0xAABBCC,
-                )
-            val iconColorsLookup by collectLastValue(underTest.iconColors)
+            val displayId = 321
+            darkIconRepository.darkState(displayId).value =
+                SysuiDarkIconDispatcher.DarkChange(listOf(Rect(0, 0, 5, 5)), 0f, 0xAABBCC)
+            val iconColorsLookup by collectLastValue(underTest.iconColors(displayId))
             val iconColors = iconColorsLookup?.iconColors(Rect(1, 1, 4, 4))
             val staticDrawableColor = iconColors?.staticDrawableColor(Rect(6, 6, 7, 7))
             assertThat(staticDrawableColor).isEqualTo(DarkIconDispatcher.DEFAULT_ICON_TINT)
@@ -272,13 +254,10 @@
     @Test
     fun iconColors_notInDarkTintArea() =
         testScope.runTest {
-            darkIconRepository.darkState.value =
-                SysuiDarkIconDispatcher.DarkChange(
-                    listOf(Rect(0, 0, 5, 5)),
-                    0f,
-                    0xAABBCC,
-                )
-            val iconColorsLookup by collectLastValue(underTest.iconColors)
+            val displayId = 987
+            darkIconRepository.darkState(displayId).value =
+                SysuiDarkIconDispatcher.DarkChange(listOf(Rect(0, 0, 5, 5)), 0f, 0xAABBCC)
+            val iconColorsLookup by collectLastValue(underTest.iconColors(displayId))
             val iconColors = iconColorsLookup?.iconColors(Rect(6, 6, 7, 7))
             assertThat(iconColors).isNull()
         }
@@ -295,7 +274,7 @@
                             activeNotificationModel(
                                 key = "notif1",
                                 groupKey = "group",
-                                statusBarIcon = icon
+                                statusBarIcon = icon,
                             )
                         )
                     }
@@ -322,7 +301,7 @@
                             activeNotificationModel(
                                 key = "notif1",
                                 groupKey = "group",
-                                statusBarIcon = icon
+                                statusBarIcon = icon,
                             )
                         )
                     }
@@ -354,7 +333,7 @@
                             activeNotificationModel(
                                 key = "notif1",
                                 groupKey = "group",
-                                statusBarIcon = icon
+                                statusBarIcon = icon,
                             )
                         )
                     }
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/SharedNotificationContainerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt
index 327a07d6..4176d1c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt
@@ -15,35 +15,55 @@
  *
  */
 
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
 package com.android.systemui.statusbar.notification.stack.domain.interactor
 
-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.biometrics.data.repository.fingerprintPropertyRepository
 import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
 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 SharedNotificationContainerInteractorTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class SharedNotificationContainerInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
     private val keyguardRepository = kosmos.fakeKeyguardRepository
     private val configurationRepository = kosmos.fakeConfigurationRepository
     private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
-    private val underTest = kosmos.sharedNotificationContainerInteractor
+    private val underTest by lazy { kosmos.sharedNotificationContainerInteractor }
+
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf().andSceneContainer()
+        }
+    }
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags)
+    }
 
     @Test
+    @DisableSceneContainer
     fun validateConfigValues() =
         testScope.runTest {
             overrideResource(R.bool.config_use_split_notification_shade, true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListenerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListenerTest.kt
index 11dd587..cae9907 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListenerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListenerTest.kt
@@ -31,10 +31,13 @@
 import androidx.annotation.ColorInt
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.data.repository.statusBarConfigurationControllerStore
+import com.android.systemui.statusbar.data.repository.sysUiDarkIconDispatcherStore
 import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange
 import com.android.systemui.statusbar.policy.FakeConfigurationController
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
@@ -50,16 +53,18 @@
 @SmallTest
 class StatusOverlayHoverListenerTest : SysuiTestCase() {
 
+    private val kosmos = testKosmos()
     private val viewOverlay = mock<ViewGroupOverlay>()
     private val overlayCaptor = argumentCaptor<Drawable>()
-    private val darkDispatcher = mock<SysuiDarkIconDispatcher>()
     private val darkChange: MutableStateFlow<DarkChange> = MutableStateFlow(DarkChange.EMPTY)
+    private val darkDispatcher = kosmos.sysUiDarkIconDispatcherStore.forDisplay(context.displayId)
 
     private val factory =
         StatusOverlayHoverListenerFactory(
             context.resources,
             FakeConfigurationController(),
-            darkDispatcher
+            kosmos.sysUiDarkIconDispatcherStore,
+            kosmos.statusBarConfigurationControllerStore,
         )
     private val view = TestableStatusContainer(context, viewOverlay)
 
@@ -186,7 +191,7 @@
             /* action= */ action,
             /* x= */ 0f,
             /* y= */ 0f,
-            /* metaState= */ 0
+            /* metaState= */ 0,
         )
     }
 }
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java
index 403c7c5..43185fd 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java
@@ -36,6 +36,9 @@
 public interface DarkIconDispatcher {
     int VERSION = 2;
 
+    /** Called when work should stop and resources should be cleaned up. */
+    default void stop() {}
+
     /**
      * Sets the dark area so {@link #applyDark} only affects the icons in the specified area.
      *
diff --git a/packages/SystemUI/res/drawable/ic_qs_notes.xml b/packages/SystemUI/res/drawable/ic_qs_notes.xml
new file mode 100644
index 0000000..6c1d2e4
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_notes.xml
@@ -0,0 +1,23 @@
+<!--
+   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 Lice/packages/SystemUI/res/drawable/ic_qs_notes.xmlnse.
+  -->
+<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="M499,673L834,338Q834,338 834,338Q834,338 834,338L782,286Q782,286 782,286Q782,286 782,286L447,621L499,673ZM238,760Q138,755 89,718Q40,681 40,611Q40,546 93.5,505.5Q147,465 242,457Q281,454 300.5,444.5Q320,435 320,418Q320,392 290.5,379Q261,366 193,360L200,280Q303,288 351.5,321.5Q400,355 400,418Q400,471 361.5,501Q323,531 248,537Q184,542 152,560.5Q120,579 120,611Q120,646 148,661.5Q176,677 242,680L238,760ZM518,767L353,602L735,220Q755,200 782.5,200Q810,200 830,220L900,290Q920,310 920,337.5Q920,365 900,385L518,767ZM359,800Q342,804 329,791Q316,778 320,761L353,602L518,767L359,800Z"/>
+</vector>
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/screen_record_dialog.xml b/packages/SystemUI/res/layout/screen_record_dialog.xml
deleted file mode 100644
index dc560bf..0000000
--- a/packages/SystemUI/res/layout/screen_record_dialog.xml
+++ /dev/null
@@ -1,164 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  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.
-  -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:orientation="vertical">
-
-    <!-- Scrollview is necessary to fit everything in landscape layout -->
-    <ScrollView
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content">
-
-        <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:paddingStart="@dimen/dialog_side_padding"
-            android:paddingEnd="@dimen/dialog_side_padding"
-            android:paddingTop="@dimen/dialog_top_padding"
-            android:paddingBottom="@dimen/dialog_bottom_padding"
-            android:orientation="vertical">
-
-            <!-- Header -->
-            <LinearLayout
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:orientation="vertical"
-                android:gravity="center">
-                <ImageView
-                    android:layout_width="@dimen/screenrecord_logo_size"
-                    android:layout_height="@dimen/screenrecord_logo_size"
-                    android:src="@drawable/ic_screenrecord"
-                    android:tint="@color/screenrecord_icon_color"/>
-                <TextView
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:textAppearance="@style/TextAppearance.Dialog.Title"
-                    android:fontFamily="@*android:string/config_headlineFontFamily"
-                    android:text="@string/screenrecord_permission_dialog_title"
-                    android:layout_marginTop="22dp"
-                    android:layout_marginBottom="15dp"/>
-                <TextView
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:text="@string/screenrecord_permission_dialog_warning_entire_screen"
-                    android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
-                    android:gravity="center"
-                    android:layout_marginBottom="20dp"/>
-
-                <!-- Options -->
-                <LinearLayout
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:orientation="horizontal">
-                    <ImageView
-                        android:layout_width="@dimen/screenrecord_option_icon_size"
-                        android:layout_height="@dimen/screenrecord_option_icon_size"
-                        android:src="@drawable/ic_mic_26dp"
-                        android:tint="?android:attr/textColorSecondary"
-                        android:layout_gravity="center"
-                        android:layout_weight="0"
-                        android:layout_marginEnd="@dimen/screenrecord_option_padding"/>
-                    <Spinner
-                        android:id="@+id/screen_recording_options"
-                        android:layout_width="0dp"
-                        android:layout_height="wrap_content"
-                        android:minHeight="48dp"
-                        android:layout_weight="1"
-                        android:popupBackground="@drawable/screenrecord_spinner_background"
-                        android:textColor="?androidprv:attr/materialColorOnSurface"
-                        android:dropDownWidth="274dp"
-                        android:prompt="@string/screenrecord_audio_label"/>
-                    <Switch
-                        android:layout_width="wrap_content"
-                        android:minWidth="48dp"
-                        android:layout_height="48dp"
-                        android:layout_weight="0"
-                        android:layout_gravity="end"
-                        android:contentDescription="@string/screenrecord_audio_label"
-                        android:id="@+id/screenrecord_audio_switch"
-                        style="@style/ScreenRecord.Switch"/>
-                </LinearLayout>
-
-                <LinearLayout
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:orientation="horizontal"
-                    android:layout_marginTop="@dimen/screenrecord_option_padding">
-                    <ImageView
-                        android:layout_width="@dimen/screenrecord_option_icon_size"
-                        android:layout_height="@dimen/screenrecord_option_icon_size"
-                        android:layout_weight="0"
-                        android:src="@drawable/ic_touch"
-                        android:tint="?android:attr/textColorSecondary"
-                        android:layout_gravity="center"
-                        android:layout_marginEnd="@dimen/screenrecord_option_padding"/>
-                    <TextView
-                        android:layout_width="0dp"
-                        android:layout_height="wrap_content"
-                        android:minHeight="48dp"
-                        android:layout_weight="1"
-                        android:layout_gravity="fill_vertical"
-                        android:gravity="center_vertical"
-                        android:text="@string/screenrecord_taps_label"
-                        android:textAppearance="?android:attr/textAppearanceMedium"
-                        android:fontFamily="@*android:string/config_headlineFontFamily"
-                        android:textColor="?androidprv:attr/materialColorOnSurface"
-                        android:importantForAccessibility="no"/>
-                    <Switch
-                        android:layout_width="wrap_content"
-                        android:minWidth="48dp"
-                        android:layout_height="48dp"
-                        android:layout_weight="0"
-                        android:id="@+id/screenrecord_taps_switch"
-                        android:contentDescription="@string/screenrecord_taps_label"
-                        style="@style/ScreenRecord.Switch"/>
-                </LinearLayout>
-            </LinearLayout>
-
-            <!-- Buttons -->
-            <LinearLayout
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:orientation="horizontal"
-                android:layout_marginTop="36dp">
-                <TextView
-                    android:id="@+id/button_cancel"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_weight="0"
-                    android:layout_gravity="start"
-                    android:text="@string/cancel"
-                    style="@style/Widget.Dialog.Button.BorderButton" />
-                <Space
-                    android:layout_width="0dp"
-                    android:layout_height="match_parent"
-                    android:layout_weight="1"/>
-
-                <TextView
-                    android:id="@+id/button_start"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_weight="0"
-                    android:layout_gravity="end"
-                    android:text="@string/screenrecord_continue"
-                    style="@style/Widget.Dialog.Button" />
-            </LinearLayout>
-        </LinearLayout>
-    </ScrollView>
-</LinearLayout>
\ No newline at end of file
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 c8ef093..82c8c44 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -116,7 +116,7 @@
 
     <!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" -->
     <string name="quick_settings_tiles_stock" translatable="false">
-        internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded,color_correction,dream,font_scaling,record_issue,hearing_devices
+        internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded,color_correction,dream,font_scaling,record_issue,hearing_devices,notes
     </string>
 
     <!-- The tiles to display in QuickSettings -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 1766cdf..af6eacb 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1004,6 +1004,9 @@
     <!-- QuickSettings: Tool name for hearing devices dialog related tools [CHAR LIMIT=40] [BACKUP_MESSAGE_ID=8916875614623730005]-->
     <string name="quick_settings_hearing_devices_live_caption_title">Live Caption</string>
 
+    <!-- QuickSettings: Notes tile. The label of a quick settings tile for launching the default notes taking app. [CHAR LIMIT=NONE] -->
+    <string name="quick_settings_notes_label">Note</string>
+
     <!--- Title of dialog triggered if the microphone is disabled but an app tried to access it. [CHAR LIMIT=150] -->
     <string name="sensor_privacy_start_use_mic_dialog_title">Unblock device microphone?</string>
     <!--- Title of dialog triggered if the camera is disabled but an app tried to access it. [CHAR LIMIT=150] -->
@@ -1518,7 +1521,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/res/values/tiles_states_strings.xml b/packages/SystemUI/res/values/tiles_states_strings.xml
index ad09b46..d885e00 100644
--- a/packages/SystemUI/res/values/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values/tiles_states_strings.xml
@@ -348,4 +348,14 @@
         <item>Off</item>
         <item>On</item>
     </string-array>
+
+    <!-- State names for notes tile: unavailable, off, on.
+         This subtitle is shown when the tile is in that particular state but does not set its own
+         subtitle, so some of these may never appear on screen. They should still be translated as
+         if they could appear. [CHAR LIMIT=32] -->
+    <string-array name="tile_states_notes">
+        <item>Unavailable</item>
+        <item>Off</item>
+        <item>On</item>
+    </string-array>
 </resources>
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
index 283e455..83ca496 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
@@ -115,23 +115,23 @@
     /**
      * Sent when {@link TaskbarDelegate#checkNavBarModes} is called.
      */
-    void checkNavBarModes() = 30;
+    void checkNavBarModes(int displayId) = 30;
 
     /**
      * Sent when {@link TaskbarDelegate#finishBarAnimations} is called.
      */
-    void finishBarAnimations() = 31;
+    void finishBarAnimations(int displayId) = 31;
 
     /**
      * Sent when {@link TaskbarDelegate#touchAutoDim} is called. {@param reset} is true, when auto
      * dim is reset after a timeout.
      */
-    void touchAutoDim(boolean reset) = 32;
+    void touchAutoDim(int displayid, boolean reset) = 32;
 
     /**
      * Sent when {@link TaskbarDelegate#transitionTo} is called.
      */
-    void transitionTo(int barMode, boolean animate) = 33;
+    void transitionTo(int displayId, int barMode, boolean animate) = 33;
 
     /**
      * Sent when {@link TaskbarDelegate#appTransitionPending} is called.
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/domain/interactor/FingerprintPropertyInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
index b8ff3bb..178e111 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.shade.ShadeDisplayAware
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
@@ -42,7 +43,7 @@
     @Application private val applicationScope: CoroutineScope,
     @Application private val context: Context,
     repository: FingerprintPropertyRepository,
-    configurationInteractor: ConfigurationInteractor,
+    @ShadeDisplayAware configurationInteractor: ConfigurationInteractor,
     displayStateInteractor: DisplayStateInteractor,
     udfpsOverlayInteractor: UdfpsOverlayInteractor,
 ) {
@@ -73,15 +74,12 @@
      * - device's natural orientation
      */
     private val unscaledSensorLocation: Flow<SensorLocationInternal> =
-        combine(
-            repository.sensorLocations,
-            uniqueDisplayId,
-        ) { locations, displayId ->
+        combine(repository.sensorLocations, uniqueDisplayId) { locations, displayId ->
             // Devices without multiple physical displays do not use the display id as the key;
             // instead, the key is an empty string.
             locations.getOrDefault(
                 displayId,
-                locations.getOrDefault("", SensorLocationInternal.DEFAULT)
+                locations.getOrDefault("", SensorLocationInternal.DEFAULT),
             )
         }
 
@@ -92,16 +90,15 @@
      * - device's natural orientation
      */
     val sensorLocation: Flow<SensorLocation> =
-        combine(
+        combine(unscaledSensorLocation, configurationInteractor.scaleForResolution) {
             unscaledSensorLocation,
-            configurationInteractor.scaleForResolution,
-        ) { unscaledSensorLocation, scale ->
+            scale ->
             val sensorLocation =
                 SensorLocation(
                     naturalCenterX = unscaledSensorLocation.sensorLocationX,
                     naturalCenterY = unscaledSensorLocation.sensorLocationY,
                     naturalRadius = unscaledSensorLocation.sensorRadius,
-                    scale = scale
+                    scale = scale,
                 )
             sensorLocation
         }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index e178c09..7039d5e 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.bouncer.domain.interactor
 
 import android.app.StatusBarManager.SESSION_KEYGUARD
+import com.android.app.tracing.coroutines.asyncTraced as async
 import com.android.compose.animation.scene.SceneKey
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
@@ -39,9 +40,9 @@
 import com.android.systemui.scene.domain.interactor.SceneBackInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.ShadeDisplayAware
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
-import com.android.app.tracing.coroutines.asyncTraced as async
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.SharedFlow
@@ -65,7 +66,7 @@
     private val uiEventLogger: UiEventLogger,
     private val sessionTracker: SessionTracker,
     sceneBackInteractor: SceneBackInteractor,
-    private val configurationInteractor: ConfigurationInteractor,
+    @ShadeDisplayAware private val configurationInteractor: ConfigurationInteractor,
 ) {
     private val _onIncorrectBouncerInput = MutableSharedFlow<Unit>()
     val onIncorrectBouncerInput: SharedFlow<Unit> = _onIncorrectBouncerInput
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerUserActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerUserActionsViewModel.kt
index 4fe6fc6..f50a2ab 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerUserActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerUserActionsViewModel.kt
@@ -18,7 +18,6 @@
 
 import com.android.compose.animation.scene.Back
 import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
@@ -33,16 +32,14 @@
  */
 class BouncerUserActionsViewModel
 @AssistedInject
-constructor(
-    private val bouncerInteractor: BouncerInteractor,
-) : UserActionsViewModel() {
+constructor(private val bouncerInteractor: BouncerInteractor) : UserActionsViewModel() {
 
     override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
         bouncerInteractor.dismissDestination
             .map { prevScene ->
                 mapOf(
                     Back to UserActionResult(prevScene),
-                    Swipe(SwipeDirection.Down) to UserActionResult(prevScene),
+                    Swipe.Down to UserActionResult(prevScene),
                 )
             }
             .collect { actions -> setActions(actions) }
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationCallback.kt
similarity index 67%
copy from libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
copy to packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationCallback.kt
index e21bf8f..ddd6bc9 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationCallback.kt
@@ -14,6 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.shared;
+package com.android.systemui.clipboardoverlay
 
-parcelable GroupedRecentTaskInfo;
\ No newline at end of file
+/** Interface for listening to indication text changed from [ClipboardIndicationProvider]. */
+interface ClipboardIndicationCallback {
+
+    /** Notifies the indication text changed. */
+    fun onIndicationTextChanged(text: CharSequence)
+}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProvider.kt
similarity index 62%
copy from libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
copy to packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProvider.kt
index e21bf8f..be32723 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProvider.kt
@@ -14,6 +14,15 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.shared;
+package com.android.systemui.clipboardoverlay
 
-parcelable GroupedRecentTaskInfo;
\ No newline at end of file
+/** Interface to provide the clipboard indication to be shown under the overlay. */
+interface ClipboardIndicationProvider {
+
+    /**
+     * Gets the indication text async.
+     *
+     * @param callback callback for getting the indication text.
+     */
+    fun getIndicationText(callback: ClipboardIndicationCallback)
+}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProviderImpl.kt
similarity index 65%
copy from libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
copy to packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProviderImpl.kt
index e21bf8f..da94d5b 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProviderImpl.kt
@@ -14,6 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.shared;
+package com.android.systemui.clipboardoverlay
 
-parcelable GroupedRecentTaskInfo;
\ No newline at end of file
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+@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/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/dagger/PluginModule.java b/packages/SystemUI/src/com/android/systemui/dagger/PluginModule.java
index d727a70..769d7fa 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/PluginModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/PluginModule.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.dagger;
 
+
 import com.android.systemui.classifier.FalsingManagerProxy;
 import com.android.systemui.globalactions.GlobalActionsComponent;
 import com.android.systemui.globalactions.GlobalActionsImpl;
@@ -26,18 +27,20 @@
 import com.android.systemui.plugins.VolumeDialogController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
+import com.android.systemui.statusbar.data.repository.DarkIconDispatcherStore;
+import com.android.systemui.statusbar.data.repository.SysuiDarkIconDispatcherStore;
 import com.android.systemui.statusbar.phone.ActivityStarterImpl;
-import com.android.systemui.statusbar.phone.DarkIconDispatcherImpl;
 import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher;
 import com.android.systemui.volume.VolumeDialogControllerImpl;
 
 import dagger.Binds;
 import dagger.Module;
+import dagger.Provides;
 
 /**
  * Module for binding Plugin implementations.
  *
- * TODO(b/166258224): Many of these should be moved closer to their implementations.
+ * <p>TODO(b/166258224): Many of these should be moved closer to their implementations.
  */
 @Module
 public abstract class PluginModule {
@@ -47,12 +50,18 @@
     abstract ActivityStarter provideActivityStarter(ActivityStarterImpl activityStarterImpl);
 
     /** */
-    @Binds
-    abstract DarkIconDispatcher provideDarkIconDispatcher(DarkIconDispatcherImpl controllerImpl);
+    @Provides
+    @SysUISingleton
+    static DarkIconDispatcher provideDarkIconDispatcher(DarkIconDispatcherStore store) {
+        return store.getDefaultDisplay();
+    }
 
-    @Binds
-    abstract SysuiDarkIconDispatcher provideSysuiDarkIconDispatcher(
-            DarkIconDispatcherImpl controllerImpl);
+    @Provides
+    @SysUISingleton
+    static SysuiDarkIconDispatcher provideSysuiDarkIconDispatcher(
+            SysuiDarkIconDispatcherStore store) {
+        return store.getDefaultDisplay();
+    }
 
     /** */
     @Binds
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/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/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index b431636..c625930 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -15,7 +15,6 @@
  */
 package com.android.systemui.flags
 
-import android.provider.DeviceConfig
 import com.android.internal.annotations.Keep
 import com.android.systemui.flags.FlagsFactory.releasedFlag
 import com.android.systemui.flags.FlagsFactory.resourceBooleanFlag
@@ -220,10 +219,6 @@
     // TODO(b/293380347): Tracking Bug
     @JvmField val COLOR_FIDELITY = unreleasedFlag("color_fidelity")
 
-    // 900 - media
-    // TODO(b/254512697): Tracking Bug
-    val MEDIA_TAP_TO_TRANSFER = releasedFlag("media_tap_to_transfer")
-
     // TODO(b/254512654): Tracking Bug
     @JvmField val DREAM_MEDIA_COMPLICATION = unreleasedFlag("dream_media_complication")
 
@@ -255,15 +250,6 @@
     val WM_ENABLE_SHELL_TRANSITIONS =
         sysPropBooleanFlag("persist.wm.debug.shell_transit", default = true)
 
-    // TODO(b/254513207): Tracking Bug to delete
-    @Keep
-    @JvmField
-    val WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES =
-        releasedFlag(
-            name = "enable_screen_record_enterprise_policies",
-            namespace = DeviceConfig.NAMESPACE_WINDOW_MANAGER,
-        )
-
     // TODO(b/293252410) : Tracking Bug
     @JvmField val LOCKSCREEN_ENABLE_LANDSCAPE = unreleasedFlag("lockscreen.enable_landscape")
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModel.kt
index f0b2b86..38fc2a8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModel.kt
@@ -19,17 +19,18 @@
 import android.content.Context
 import android.view.Surface
 import android.view.WindowManager
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.settingslib.Utils
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.keyboard.docking.domain.interactor.KeyboardDockingIndicationInteractor
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.surfaceeffects.glowboxeffect.GlowBoxConfig
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 @SysUISingleton
 class KeyboardDockingIndicationViewModel
@@ -38,7 +39,7 @@
     private val windowManager: WindowManager,
     private val context: Context,
     keyboardDockingIndicationInteractor: KeyboardDockingIndicationInteractor,
-    configurationInteractor: ConfigurationInteractor,
+    @ShadeDisplayAware configurationInteractor: ConfigurationInteractor,
     @Background private val backgroundScope: CoroutineScope,
 ) {
 
@@ -128,7 +129,7 @@
             blurAmount = BLUR_AMOUNT,
             duration = DURATION,
             easeInDuration = EASE_DURATION,
-            easeOutDuration = EASE_DURATION
+            easeOutDuration = EASE_DURATION,
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index e79f590..2d05600 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -60,6 +60,7 @@
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.shade.NotificationShadeWindowView
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.KeyguardIndicationController
 import com.android.systemui.statusbar.LightRevealScrim
@@ -93,7 +94,7 @@
     private val chipbarCoordinator: ChipbarCoordinator,
     private val keyguardBlueprintViewModel: KeyguardBlueprintViewModel,
     private val keyguardStatusViewComponentFactory: KeyguardStatusViewComponent.Factory,
-    private val configuration: ConfigurationState,
+    @ShadeDisplayAware private val configuration: ConfigurationState,
     private val context: Context,
     private val keyguardIndicationController: KeyguardIndicationController,
     private val shadeInteractor: ShadeInteractor,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt
index ca86289..73a4cc3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.doze.util.BurnInHelperWrapper
 import com.android.systemui.keyguard.shared.model.BurnInModel
 import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -47,7 +48,7 @@
     private val context: Context,
     private val burnInHelperWrapper: BurnInHelperWrapper,
     @Application private val scope: CoroutineScope,
-    private val configurationInteractor: ConfigurationInteractor,
+    @ShadeDisplayAware private val configurationInteractor: ConfigurationInteractor,
     private val keyguardInteractor: KeyguardInteractor,
 ) {
     val deviceEntryIconXOffset: StateFlow<Int> =
@@ -62,7 +63,7 @@
             .stateIn(
                 scope,
                 SharingStarted.WhileSubscribed(),
-                burnInHelperWrapper.burnInProgressOffset()
+                burnInHelperWrapper.burnInProgressOffset(),
             )
 
     /** Given the max x,y dimens, determine the current translation shifts. */
@@ -71,7 +72,7 @@
                 burnInOffset(xDimenResourceId, isXAxis = true),
                 burnInOffset(yDimenResourceId, isXAxis = false).map {
                     it * 2 - context.resources.getDimensionPixelSize(yDimenResourceId)
-                }
+                },
             ) { translationX, translationY ->
                 BurnInModel(translationX, translationY, burnInHelperWrapper.burnInScale())
             }
@@ -117,7 +118,7 @@
     private fun calculateOffset(
         maxBurnInOffsetPixels: Int,
         isXAxis: Boolean,
-        scale: Float = 1f
+        scale: Float = 1f,
     ): Int {
         return (burnInHelperWrapper.burnInOffset(maxBurnInOffsetPixels, isXAxis) * scale).toInt()
     }
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/KeyguardBlueprintInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
index 6385b3c..2aaec87 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
@@ -19,6 +19,7 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.CoreStartable
 import com.android.systemui.biometrics.domain.interactor.FingerprintPropertyInteractor
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
@@ -32,6 +33,7 @@
 import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Type
 import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -39,7 +41,6 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 @SysUISingleton
 class KeyguardBlueprintInteractor
@@ -48,7 +49,7 @@
     private val keyguardBlueprintRepository: KeyguardBlueprintRepository,
     @Application private val applicationScope: CoroutineScope,
     shadeInteractor: ShadeInteractor,
-    private val configurationInteractor: ConfigurationInteractor,
+    @ShadeDisplayAware private val configurationInteractor: ConfigurationInteractor,
     private val fingerprintPropertyInteractor: FingerprintPropertyInteractor,
     private val smartspaceSection: SmartspaceSection,
 ) : CoreStartable {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 2e0a160..26c286d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -49,6 +49,7 @@
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.shade.data.repository.ShadeRepository
 import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
 import com.android.systemui.util.kotlin.sample
@@ -85,7 +86,7 @@
     private val repository: KeyguardRepository,
     powerInteractor: PowerInteractor,
     bouncerRepository: KeyguardBouncerRepository,
-    configurationInteractor: ConfigurationInteractor,
+    @ShadeDisplayAware configurationInteractor: ConfigurationInteractor,
     shadeRepository: ShadeRepository,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
     sceneInteractorProvider: Provider<SceneInteractor>,
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 2c9884a..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
@@ -126,14 +126,7 @@
                 sceneInteractor.get().transitionState.flatMapLatestConflated { state ->
                     when {
                         state.isTransitioning(from = Scenes.Lockscreen, to = Scenes.Gone) ->
-                            (state as Transition).isUserInputOngoing.flatMapLatestConflated {
-                                isUserInputOngoing ->
-                                if (isUserInputOngoing) {
-                                    isDeviceEntered
-                                } else {
-                                    flowOf(true)
-                                }
-                            }
+                            isDeviceEntered
                         state.isTransitioning(from = Scenes.Bouncer, to = Scenes.Gone) ->
                             (state as Transition).progress.map { progress ->
                                 progress >
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
index 36ef78e..faa4978 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
 import com.android.systemui.res.R
+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
@@ -47,7 +48,7 @@
 @Inject
 constructor(
     private val context: Context,
-    private val configurationState: ConfigurationState,
+    @ShadeDisplayAware private val configurationState: ConfigurationState,
     private val iconBindingFailureTracker: StatusBarIconViewBindingFailureTracker,
     private val nicAodViewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
     private val nicAodIconViewStore: AlwaysOnDisplayNotificationIconViewStore,
@@ -70,7 +71,7 @@
                     resources.getDimensionPixelSize(R.dimen.below_clock_padding_start_icons),
                     0,
                     0,
-                    0
+                    0,
                 )
                 setVisibility(View.INVISIBLE)
             }
@@ -113,18 +114,18 @@
                 START,
                 PARENT_ID,
                 START,
-                context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
+                context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal),
             )
             connect(
                 nicId,
                 END,
                 PARENT_ID,
                 END,
-                context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
+                context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal),
             )
             constrainHeight(
                 nicId,
-                context.resources.getDimensionPixelSize(R.dimen.notification_shelf_height)
+                context.resources.getDimensionPixelSize(R.dimen.notification_shelf_height),
             )
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
index 4908dbd..56e3125 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
 import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.shared.recents.utilities.Utilities.clamp
 import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -41,7 +42,7 @@
 @Inject
 constructor(
     val context: Context,
-    configurationInteractor: ConfigurationInteractor,
+    @ShadeDisplayAware configurationInteractor: ConfigurationInteractor,
     deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
     deviceEntryBackgroundViewModel: DeviceEntryBackgroundViewModel,
     fingerprintPropertyInteractor: FingerprintPropertyInteractor,
@@ -85,10 +86,7 @@
             }
     private val fgIconPadding: Flow<Int> = udfpsOverlayInteractor.iconPadding
     val fgViewModel: Flow<DeviceEntryForegroundViewModel.ForegroundIconViewModel> =
-        combine(
-            fgIconColor,
-            fgIconPadding,
-        ) { color, padding ->
+        combine(fgIconColor, fgIconPadding) { color, padding ->
             DeviceEntryForegroundViewModel.ForegroundIconViewModel(
                 type = DeviceEntryIconView.IconType.FINGERPRINT,
                 useAodVariant = false,
@@ -100,12 +98,7 @@
     val bgColor: Flow<Int> = deviceEntryBackgroundViewModel.color
     val bgAlpha: Flow<Float> = flowOf(1f)
 
-    data class IconLocation(
-        val left: Int,
-        val top: Int,
-        val right: Int,
-        val bottom: Int,
-    ) {
+    data class IconLocation(val left: Int, val top: Int, val right: Int, val bottom: Int) {
         val width = right - left
         val height = bottom - top
     }
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..1c89723 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,9 +30,11 @@
 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
+import com.android.systemui.shade.ShadeDisplayAware
 import javax.inject.Inject
 import kotlin.math.max
 import kotlinx.coroutines.CoroutineScope
@@ -42,8 +44,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
 
@@ -57,7 +61,7 @@
 constructor(
     @Application private val applicationScope: CoroutineScope,
     private val burnInInteractor: BurnInInteractor,
-    private val configurationInteractor: ConfigurationInteractor,
+    @ShadeDisplayAware private val configurationInteractor: ConfigurationInteractor,
     private val keyguardInteractor: KeyguardInteractor,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val goneToAodTransitionViewModel: GoneToAodTransitionViewModel,
@@ -164,9 +168,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/DeviceEntryBackgroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
index 4c667c1..12f9467 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.shade.ShadeDisplayAware
 import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
@@ -42,7 +43,7 @@
     val context: Context,
     val deviceEntryIconViewModel: DeviceEntryIconViewModel,
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
-    configurationInteractor: ConfigurationInteractor,
+    @ShadeDisplayAware configurationInteractor: ConfigurationInteractor,
     alternateBouncerToAodTransitionViewModel: AlternateBouncerToAodTransitionViewModel,
     alternateBouncerToDozingTransitionViewModel: AlternateBouncerToDozingTransitionViewModel,
     aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
index 87c32a5..749f193 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
 import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import javax.inject.Inject
 import kotlin.math.roundToInt
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -45,7 +46,7 @@
 @Inject
 constructor(
     val context: Context,
-    configurationInteractor: ConfigurationInteractor,
+    @ShadeDisplayAware configurationInteractor: ConfigurationInteractor,
     deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
     transitionInteractor: KeyguardTransitionInteractor,
     deviceEntryIconViewModel: DeviceEntryIconViewModel,
@@ -106,12 +107,11 @@
         }
 
     val viewModel: Flow<ForegroundIconViewModel> =
-        combine(
-            deviceEntryIconViewModel.iconType,
-            useAodIconVariant,
+        combine(deviceEntryIconViewModel.iconType, useAodIconVariant, color, padding) {
+            iconType,
+            useAodVariant,
             color,
-            padding,
-        ) { iconType, useAodVariant, color, padding ->
+            padding ->
             ForegroundIconViewModel(
                 type = iconType,
                 useAodVariant = useAodVariant,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt
index 11ed52a..c9fdf7a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.ShadeDisplayAware
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlin.time.Duration.Companion.seconds
@@ -41,7 +42,7 @@
 @Inject
 constructor(
     animationFlow: KeyguardTransitionAnimationFlow,
-    configurationInteractor: ConfigurationInteractor,
+    @ShadeDisplayAware configurationInteractor: ConfigurationInteractor,
 ) : DeviceEntryIconTransition {
     private val transitionAnimation =
         animationFlow
@@ -49,15 +50,13 @@
                 duration = TO_GLANCEABLE_HUB_DURATION,
                 edge = Edge.create(from = DREAMING, to = Scenes.Communal),
             )
-            .setupWithoutSceneContainer(
-                edge = Edge.create(from = DREAMING, to = GLANCEABLE_HUB),
-            )
+            .setupWithoutSceneContainer(edge = Edge.create(from = DREAMING, to = GLANCEABLE_HUB))
 
     val dreamOverlayTranslationX: Flow<Float> =
         configurationInteractor
             .directionalDimensionPixelSize(
                 LayoutDirection.LTR,
-                R.dimen.dreaming_to_hub_transition_dream_overlay_translation_x
+                R.dimen.dreaming_to_hub_transition_dream_overlay_translation_x,
             )
             .flatMapLatest { translatePx ->
                 transitionAnimation.sharedFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt
index f69f996..723fba6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.ShadeDisplayAware
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlin.time.Duration.Companion.seconds
@@ -41,7 +42,7 @@
 @Inject
 constructor(
     animationFlow: KeyguardTransitionAnimationFlow,
-    configurationInteractor: ConfigurationInteractor,
+    @ShadeDisplayAware configurationInteractor: ConfigurationInteractor,
 ) : DeviceEntryIconTransition {
 
     private val transitionAnimation =
@@ -50,9 +51,7 @@
                 duration = FROM_GLANCEABLE_HUB_DURATION,
                 edge = Edge.create(from = Scenes.Communal, to = DREAMING),
             )
-            .setupWithoutSceneContainer(
-                edge = Edge.create(from = GLANCEABLE_HUB, to = DREAMING),
-            )
+            .setupWithoutSceneContainer(edge = Edge.create(from = GLANCEABLE_HUB, to = DREAMING))
 
     val dreamOverlayAlpha: Flow<Float> =
         transitionAnimation.sharedFlow(
@@ -66,7 +65,7 @@
         configurationInteractor
             .directionalDimensionPixelSize(
                 LayoutDirection.LTR,
-                R.dimen.hub_to_dreaming_transition_dream_overlay_translation_x
+                R.dimen.hub_to_dreaming_transition_dream_overlay_translation_x,
             )
             .flatMapLatest { translatePx: Int ->
                 transitionAnimation.sharedFlow(
@@ -74,7 +73,7 @@
                     onStep = { value -> -translatePx + value * translatePx },
                     interpolator = Interpolators.EMPHASIZED,
                     onCancel = { -translatePx.toFloat() },
-                    name = "GLANCEABLE_HUB->LOCKSCREEN: dreamOverlayTranslationX"
+                    name = "GLANCEABLE_HUB->LOCKSCREEN: dreamOverlayTranslationX",
                 )
             }
 
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 7f3ef61..5a4d0689 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
@@ -28,6 +28,7 @@
 import com.android.systemui.keyguard.ui.StateToValue
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.ShadeDisplayAware
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -45,7 +46,7 @@
 class GlanceableHubToLockscreenTransitionViewModel
 @Inject
 constructor(
-    configurationInteractor: ConfigurationInteractor,
+    @ShadeDisplayAware configurationInteractor: ConfigurationInteractor,
     animationFlow: KeyguardTransitionAnimationFlow,
 ) {
     private val transitionAnimation =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
index 36f684e..5c79c0b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.keyguard.shared.model.ClockSizeSetting
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel
 import com.android.systemui.statusbar.ui.SystemBarUtilsProxy
@@ -50,7 +51,8 @@
     aodNotificationIconViewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
     @get:VisibleForTesting val shadeInteractor: ShadeInteractor,
     private val systemBarUtils: SystemBarUtilsProxy,
-    configurationInteractor: ConfigurationInteractor,
+    @ShadeDisplayAware configurationInteractor: ConfigurationInteractor,
+    // TODO: b/374267505 - Use ShadeDisplayAware resources here.
     @Main private val resources: Resources,
 ) {
     var burnInLayer: Layer? = null
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt
index ceae1b5..bc3ef02 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
 import javax.inject.Inject
 import javax.inject.Named
@@ -53,7 +54,7 @@
     burnInInteractor: BurnInInteractor,
     @Named(KeyguardQuickAffordancesCombinedViewModelModule.Companion.LOCKSCREEN_INSTANCE)
     shortcutsCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel,
-    configurationInteractor: ConfigurationInteractor,
+    @ShadeDisplayAware configurationInteractor: ConfigurationInteractor,
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
     communalSceneInteractor: CommunalSceneInteractor,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
@@ -70,7 +71,7 @@
     val visible: Flow<Boolean> =
         anyOf(
             keyguardInteractor.statusBarState.map { state -> state == StatusBarState.KEYGUARD },
-            communalSceneInteractor.isCommunalVisible
+            communalSceneInteractor.isCommunalVisible,
         )
 
     /** An observable for whether the indication area should be padded. */
@@ -85,7 +86,7 @@
         } else {
             combine(
                     keyguardBottomAreaViewModel.startButton,
-                    keyguardBottomAreaViewModel.endButton
+                    keyguardBottomAreaViewModel.endButton,
                 ) { startButtonModel, endButtonModel ->
                     startButtonModel.isVisible || endButtonModel.isVisible
                 }
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 dd8ff8c..acaa9b9 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
@@ -28,6 +28,7 @@
 import com.android.systemui.keyguard.ui.StateToValue
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.ShadeDisplayAware
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -45,7 +46,7 @@
 class LockscreenToGlanceableHubTransitionViewModel
 @Inject
 constructor(
-    configurationInteractor: ConfigurationInteractor,
+    @ShadeDisplayAware configurationInteractor: ConfigurationInteractor,
     animationFlow: KeyguardTransitionAnimationFlow,
 ) {
     private val transitionAnimation =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
index 88e8968..6565e31 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.flow.Flow
@@ -40,7 +41,7 @@
 @Inject
 constructor(
     shadeDependentFlows: ShadeDependentFlows,
-    configurationInteractor: ConfigurationInteractor,
+    @ShadeDisplayAware configurationInteractor: ConfigurationInteractor,
     animationFlow: KeyguardTransitionAnimationFlow,
 ) : DeviceEntryIconTransition {
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
index 737bd7a..d10970f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.util.kotlin.pairwise
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
@@ -49,7 +50,7 @@
 @Inject
 constructor(
     deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
-    configurationInteractor: ConfigurationInteractor,
+    @ShadeDisplayAware configurationInteractor: ConfigurationInteractor,
     animationFlow: KeyguardTransitionAnimationFlow,
     keyguardInteractor: KeyguardInteractor,
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
@@ -104,7 +105,7 @@
                         !isOccluded &&
                         keyguardTransitionInteractor.getCurrentState() == OCCLUDED
                 }
-                .map { 0f }
+                .map { 0f },
         )
 
     val deviceEntryBackgroundViewAlpha: Flow<Float> =
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
index a33685b..d38e507 100644
--- 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
@@ -28,6 +28,7 @@
 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 com.android.systemui.dagger.SysUISingleton
@@ -82,10 +83,19 @@
         // 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))
+            val runnable = Runnable {
+                try {
+                    val result = getMedia3Actions(packageName, m3controller, token)
+                    continuation.resumeWith(Result.success(result))
+                } finally {
+                    m3controller.release()
+                }
+            }
+            handler.post(runnable)
+            continuation.invokeOnCancellation {
+                // Ensure controller is released, even if loading was cancelled partway through
+                handler.post(m3controller::release)
+                handler.removeCallbacks(runnable)
             }
         }
         return buttons
@@ -95,7 +105,7 @@
     @WorkerThread
     private fun getMedia3Actions(
         packageName: String,
-        m3controller: androidx.media3.session.MediaController,
+        m3controller: Media3Controller,
         token: SessionToken,
     ): MediaButton? {
         Assert.isNotMainThread()
@@ -197,7 +207,7 @@
      * @return A [MediaAction] representing the first supported command, or null if not supported
      */
     private fun getStandardAction(
-        controller: androidx.media3.session.MediaController,
+        controller: Media3Controller,
         token: SessionToken,
         vararg commands: @Player.Command Int,
     ): MediaAction? {
@@ -304,37 +314,40 @@
         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")
+                try {
+                    when (command) {
+                        Player.COMMAND_PLAY_PAUSE -> {
+                            if (controller.isPlaying) controller.pause() else controller.play()
                         }
-                    }
 
-                    else -> logger.logMedia3UnsupportedCommand(command.toString())
+                        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?.sessionCommand != null) {
+                                val sessionCommand = customAction.sessionCommand!!
+                                if (controller.isSessionCommandAvailable(sessionCommand)) {
+                                    controller.sendCustomCommand(
+                                        sessionCommand,
+                                        customAction.extras,
+                                    )
+                                } else {
+                                    logger.logMedia3UnsupportedCommand(
+                                        "$sessionCommand, action $customAction"
+                                    )
+                                }
+                            } else {
+                                logger.logMedia3UnsupportedCommand("$command, action $customAction")
+                            }
+                        }
+                        else -> logger.logMedia3UnsupportedCommand(command.toString())
+                    }
+                } finally {
+                    controller.release()
                 }
-                controller.release()
             }
         }
     }
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
index 741f529..d815852 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.kt
@@ -35,7 +35,12 @@
         return MediaController(context, token)
     }
 
-    /** Creates a new [Media3Controller] from a [SessionToken] */
+    /**
+     * Creates a new [Media3Controller] from the media3 [SessionToken].
+     *
+     * @param token The token for the session
+     * @param looper The looper that will be used for this controller's operations
+     */
     open suspend fun create(token: SessionToken, looper: Looper): Media3Controller {
         return Media3Controller.Builder(context, token)
             .setApplicationLooper(looper)
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
index 36a9fb3..45a3a8c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
@@ -27,17 +27,12 @@
 import com.android.systemui.media.controls.ui.controller.MediaHostStatesManager;
 import com.android.systemui.media.controls.ui.view.MediaHost;
 import com.android.systemui.media.dream.dagger.MediaComplicationComponent;
-import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper;
-import com.android.systemui.media.taptotransfer.MediaTttFlags;
 import com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogBuffer;
 import com.android.systemui.media.taptotransfer.sender.MediaTttSenderLogBuffer;
 
-import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
 
-import java.util.Optional;
-
 import javax.inject.Named;
 
 /** Dagger module for the media package. */
@@ -132,16 +127,4 @@
     static LogBuffer provideMediaTttReceiverLogBuffer(LogBufferFactory factory) {
         return factory.create("MediaTttReceiver", 20);
     }
-
-    /** */
-    @Provides
-    @SysUISingleton
-    static Optional<MediaTttCommandLineHelper> providesMediaTttCommandLineHelper(
-            MediaTttFlags mediaTttFlags,
-            Lazy<MediaTttCommandLineHelper> helperLazy) {
-        if (!mediaTttFlags.isMediaTttEnabled()) {
-            return Optional.empty();
-        }
-        return Optional.of(helperLazy.get());
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt
deleted file mode 100644
index 03bc935..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt
+++ /dev/null
@@ -1,29 +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.media.taptotransfer
-
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import javax.inject.Inject
-
-/** Flags related to media tap-to-transfer. */
-@SysUISingleton
-class MediaTttFlags @Inject constructor(private val featureFlags: FeatureFlags) {
-    /** */
-    fun isMediaTttEnabled(): Boolean = featureFlags.isEnabled(Flags.MEDIA_TAP_TO_TRANSFER)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 92db804..1204cde 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -43,7 +43,6 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.media.taptotransfer.MediaTttFlags
 import com.android.systemui.media.taptotransfer.common.MediaTttIcon
 import com.android.systemui.media.taptotransfer.common.MediaTttUtils
 import com.android.systemui.res.R
@@ -68,25 +67,27 @@
  * TODO(b/245610654): Re-name this to be MediaTttReceiverCoordinator.
  */
 @SysUISingleton
-open class MediaTttChipControllerReceiver @Inject constructor(
-        private val commandQueue: CommandQueue,
-        context: Context,
-        logger: MediaTttReceiverLogger,
-        viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager,
-        @Main mainExecutor: DelayableExecutor,
-        accessibilityManager: AccessibilityManager,
-        configurationController: ConfigurationController,
-        dumpManager: DumpManager,
-        powerManager: PowerManager,
-        @Main private val mainHandler: Handler,
-        private val mediaTttFlags: MediaTttFlags,
-        private val uiEventLogger: MediaTttReceiverUiEventLogger,
-        private val viewUtil: ViewUtil,
-        wakeLockBuilder: WakeLock.Builder,
-        systemClock: SystemClock,
-        private val rippleController: MediaTttReceiverRippleController,
-        private val temporaryViewUiEventLogger: TemporaryViewUiEventLogger,
-) : TemporaryViewDisplayController<ChipReceiverInfo, MediaTttReceiverLogger>(
+open class MediaTttChipControllerReceiver
+@Inject
+constructor(
+    private val commandQueue: CommandQueue,
+    context: Context,
+    logger: MediaTttReceiverLogger,
+    viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager,
+    @Main mainExecutor: DelayableExecutor,
+    accessibilityManager: AccessibilityManager,
+    configurationController: ConfigurationController,
+    dumpManager: DumpManager,
+    powerManager: PowerManager,
+    @Main private val mainHandler: Handler,
+    private val uiEventLogger: MediaTttReceiverUiEventLogger,
+    private val viewUtil: ViewUtil,
+    wakeLockBuilder: WakeLock.Builder,
+    systemClock: SystemClock,
+    private val rippleController: MediaTttReceiverRippleController,
+    private val temporaryViewUiEventLogger: TemporaryViewUiEventLogger,
+) :
+    TemporaryViewDisplayController<ChipReceiverInfo, MediaTttReceiverLogger>(
         context,
         logger,
         viewCaptureAwareWindowManager,
@@ -99,36 +100,43 @@
         wakeLockBuilder,
         systemClock,
         temporaryViewUiEventLogger,
-) {
+    ) {
     @SuppressLint("WrongConstant") // We're allowed to use LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
-    override val windowLayoutParams = commonWindowLayoutParams.apply {
-        gravity = Gravity.BOTTOM.or(Gravity.CENTER_HORIZONTAL)
-        // Params below are needed for the ripple to work correctly
-        width = WindowManager.LayoutParams.MATCH_PARENT
-        height = WindowManager.LayoutParams.MATCH_PARENT
-        layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
-        fitInsetsTypes = 0 // Ignore insets from all system bars
-    }
+    override val windowLayoutParams =
+        commonWindowLayoutParams.apply {
+            gravity = Gravity.BOTTOM.or(Gravity.CENTER_HORIZONTAL)
+            // Params below are needed for the ripple to work correctly
+            width = WindowManager.LayoutParams.MATCH_PARENT
+            height = WindowManager.LayoutParams.MATCH_PARENT
+            layoutInDisplayCutoutMode =
+                WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+            fitInsetsTypes = 0 // Ignore insets from all system bars
+        }
 
     // Value animator that controls the bouncing animation of views.
-    private val bounceAnimator = ValueAnimator.ofFloat(0f, 1f).apply {
-        repeatCount = ValueAnimator.INFINITE
-        repeatMode = ValueAnimator.REVERSE
-        duration = ICON_BOUNCE_ANIM_DURATION
-    }
-
-    private val commandQueueCallbacks = object : CommandQueue.Callbacks {
-        override fun updateMediaTapToTransferReceiverDisplay(
-            @StatusBarManager.MediaTransferReceiverState displayState: Int,
-            routeInfo: MediaRoute2Info,
-            appIcon: Icon?,
-            appName: CharSequence?
-        ) {
-            this@MediaTttChipControllerReceiver.updateMediaTapToTransferReceiverDisplay(
-                displayState, routeInfo, appIcon, appName
-            )
+    private val bounceAnimator =
+        ValueAnimator.ofFloat(0f, 1f).apply {
+            repeatCount = ValueAnimator.INFINITE
+            repeatMode = ValueAnimator.REVERSE
+            duration = ICON_BOUNCE_ANIM_DURATION
         }
-    }
+
+    private val commandQueueCallbacks =
+        object : CommandQueue.Callbacks {
+            override fun updateMediaTapToTransferReceiverDisplay(
+                @StatusBarManager.MediaTransferReceiverState displayState: Int,
+                routeInfo: MediaRoute2Info,
+                appIcon: Icon?,
+                appName: CharSequence?,
+            ) {
+                this@MediaTttChipControllerReceiver.updateMediaTapToTransferReceiverDisplay(
+                    displayState,
+                    routeInfo,
+                    appIcon,
+                    appName,
+                )
+            }
+        }
 
     // A map to store instance id per route info id.
     private var instanceMap: MutableMap<String, InstanceId> = mutableMapOf()
@@ -139,7 +147,7 @@
         @StatusBarManager.MediaTransferReceiverState displayState: Int,
         routeInfo: MediaRoute2Info,
         appIcon: Icon?,
-        appName: CharSequence?
+        appName: CharSequence?,
     ) {
         val chipState: ChipStateReceiver? = ChipStateReceiver.getReceiverStateFromId(displayState)
         val stateName = chipState?.name ?: "Invalid"
@@ -150,8 +158,8 @@
             return
         }
 
-        val instanceId: InstanceId = instanceMap[routeInfo.id]
-                ?: temporaryViewUiEventLogger.getNewInstanceId()
+        val instanceId: InstanceId =
+            instanceMap[routeInfo.id] ?: temporaryViewUiEventLogger.getNewInstanceId()
         uiEventLogger.logReceiverStateChange(chipState, instanceId)
 
         if (chipState != ChipStateReceiver.CLOSE_TO_SENDER) {
@@ -175,53 +183,51 @@
         }
 
         appIcon.loadDrawableAsync(
-                context,
-                Icon.OnDrawableLoadedListener { drawable ->
-                    displayView(
-                        ChipReceiverInfo(
-                            routeInfo,
-                            drawable,
-                            appName,
-                            id = routeInfo.id,
-                            instanceId = instanceId,
-                        )
+            context,
+            Icon.OnDrawableLoadedListener { drawable ->
+                displayView(
+                    ChipReceiverInfo(
+                        routeInfo,
+                        drawable,
+                        appName,
+                        id = routeInfo.id,
+                        instanceId = instanceId,
                     )
-                },
-                // Notify the listener on the main handler since the listener will update
-                // the UI.
-                mainHandler
+                )
+            },
+            // Notify the listener on the main handler since the listener will update
+            // the UI.
+            mainHandler,
         )
     }
 
     override fun start() {
         super.start()
-        if (mediaTttFlags.isMediaTttEnabled()) {
-            commandQueue.addCallback(commandQueueCallbacks)
-        }
+        commandQueue.addCallback(commandQueueCallbacks)
         registerListener(displayListener)
     }
 
     override fun updateView(newInfo: ChipReceiverInfo, currentView: ViewGroup) {
         val packageName: String? = newInfo.routeInfo.clientPackageName
-        var iconInfo = MediaTttUtils.getIconInfoFromPackageName(
-            context,
-            packageName,
-            isReceiver = true,
-        ) {
-            packageName?.let { logger.logPackageNotFound(it) }
-        }
+        var iconInfo =
+            MediaTttUtils.getIconInfoFromPackageName(context, packageName, isReceiver = true) {
+                packageName?.let { logger.logPackageNotFound(it) }
+            }
 
         if (newInfo.appNameOverride != null) {
-            iconInfo = iconInfo.copy(
-                contentDescription = ContentDescription.Loaded(newInfo.appNameOverride.toString())
-            )
+            iconInfo =
+                iconInfo.copy(
+                    contentDescription =
+                        ContentDescription.Loaded(newInfo.appNameOverride.toString())
+                )
         }
 
         if (newInfo.appIconDrawableOverride != null) {
-            iconInfo = iconInfo.copy(
-                icon = MediaTttIcon.Loaded(newInfo.appIconDrawableOverride),
-                isAppIcon = true,
-            )
+            iconInfo =
+                iconInfo.copy(
+                    icon = MediaTttIcon.Loaded(newInfo.appIconDrawableOverride),
+                    isAppIcon = true,
+                )
         }
 
         val iconPadding =
@@ -298,16 +304,14 @@
         alphaDuration: Long = ICON_ALPHA_ANIM_DURATION,
         onAnimationEnd: Runnable? = null,
     ) {
-        view.animate()
+        view
+            .animate()
             .translationYBy(translationYBy)
             .setInterpolator(interpolator)
             .setDuration(translationDuration)
             .withEndAction { onAnimationEnd?.run() }
             .start()
-        view.animate()
-            .alpha(alphaEndValue)
-            .setDuration(alphaDuration)
-            .start()
+        view.animate().alpha(alphaEndValue).setDuration(alphaDuration).start()
     }
 
     /** Returns the amount that the chip will be translated by in its intro animation. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
index 3e6d46c..6ca0471 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
@@ -28,7 +28,6 @@
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.media.taptotransfer.MediaTttFlags
 import com.android.systemui.media.taptotransfer.common.MediaTttUtils
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.CommandQueue
@@ -53,7 +52,6 @@
     private val context: Context,
     private val dumpManager: DumpManager,
     private val logger: MediaTttSenderLogger,
-    private val mediaTttFlags: MediaTttFlags,
     private val uiEventLogger: MediaTttSenderUiEventLogger,
 ) : CoreStartable, Dumpable {
 
@@ -68,27 +66,25 @@
             override fun updateMediaTapToTransferSenderDisplay(
                 @StatusBarManager.MediaTransferSenderState displayState: Int,
                 routeInfo: MediaRoute2Info,
-                undoCallback: IUndoMediaTransferCallback?
+                undoCallback: IUndoMediaTransferCallback?,
             ) {
                 this@MediaTttSenderCoordinator.updateMediaTapToTransferSenderDisplay(
                     displayState,
                     routeInfo,
-                    undoCallback
+                    undoCallback,
                 )
             }
         }
 
     override fun start() {
-        if (mediaTttFlags.isMediaTttEnabled()) {
-            commandQueue.addCallback(commandQueueCallbacks)
-            dumpManager.registerNormalDumpable(this)
-        }
+        commandQueue.addCallback(commandQueueCallbacks)
+        dumpManager.registerNormalDumpable(this)
     }
 
     private fun updateMediaTapToTransferSenderDisplay(
         @StatusBarManager.MediaTransferSenderState displayState: Int,
         routeInfo: MediaRoute2Info,
-        undoCallback: IUndoMediaTransferCallback?
+        undoCallback: IUndoMediaTransferCallback?,
     ) {
         val chipState: ChipStateSender? = ChipStateSender.getSenderStateFromId(displayState)
         val stateName = chipState?.name ?: "Invalid"
@@ -107,7 +103,7 @@
             // ChipStateSender.FAR_FROM_RECEIVER is the default state when there is no state.
             logger.logInvalidStateTransitionError(
                 currentState = currentStateForId?.name ?: ChipStateSender.FAR_FROM_RECEIVER.name,
-                chipState.name
+                chipState.name,
             )
             return
         }
@@ -126,7 +122,7 @@
                 // still be able to see the status of the transfer.
                 logger.logRemovalBypass(
                     removalReason,
-                    bypassReason = "transferStatus=${currentStateForId.transferStatus.name}"
+                    bypassReason = "transferStatus=${currentStateForId.transferStatus.name}",
                 )
                 return
             }
@@ -139,14 +135,7 @@
             logger.logStateMap(stateMap)
             chipbarCoordinator.registerListener(displayListener)
             chipbarCoordinator.displayView(
-                createChipbarInfo(
-                    chipState,
-                    routeInfo,
-                    undoCallback,
-                    context,
-                    logger,
-                    instanceId,
-                )
+                createChipbarInfo(chipState, routeInfo, undoCallback, context, logger, instanceId)
             )
         }
     }
@@ -245,10 +234,7 @@
                 )
             }
 
-        return ChipbarEndItem.Button(
-            Text.Resource(R.string.media_transfer_undo),
-            onClickListener,
-        )
+        return ChipbarEndItem.Button(Text.Resource(R.string.media_transfer_undo), onClickListener)
     }
 
     private val displayListener =
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/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
index 47dacae..2fda201 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -57,7 +57,6 @@
 import android.view.Window;
 
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger;
 import com.android.systemui.mediaprojection.MediaProjectionServiceHelper;
 import com.android.systemui.mediaprojection.MediaProjectionUtils;
@@ -187,11 +186,9 @@
             return;
         }
 
-        if (mFeatureFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES)) {
-            if (showScreenCaptureDisabledDialogIfNeeded()) {
-                finishAsCancelled();
-                return;
-            }
+        if (showScreenCaptureDisabledDialogIfNeeded()) {
+            finishAsCancelled();
+            return;
         }
 
         final String appName = extractAppName(aInfo, packageManager);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
index b019c13..a3b7590 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
@@ -406,7 +406,7 @@
         if (navBar != null) {
             navBar.checkNavBarModes();
         } else {
-            mTaskbarDelegate.checkNavBarModes();
+            mTaskbarDelegate.checkNavBarModes(displayId);
         }
     }
 
@@ -416,7 +416,7 @@
         if (navBar != null) {
             navBar.finishBarAnimations();
         } else {
-            mTaskbarDelegate.finishBarAnimations();
+            mTaskbarDelegate.finishBarAnimations(displayId);
         }
     }
 
@@ -426,7 +426,7 @@
         if (navBar != null) {
             navBar.touchAutoDim();
         } else {
-            mTaskbarDelegate.touchAutoDim();
+            mTaskbarDelegate.touchAutoDim(displayId);
         }
     }
 
@@ -436,7 +436,7 @@
         if (navBar != null) {
             navBar.transitionTo(barMode, animate);
         } else {
-            mTaskbarDelegate.transitionTo(barMode, animate);
+            mTaskbarDelegate.transitionTo(displayId, barMode, animate);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 1216a88..2a3aeae 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -159,7 +159,7 @@
     private final AutoHideUiElement mAutoHideUiElement = new AutoHideUiElement() {
         @Override
         public void synchronizeState() {
-            checkNavBarModes();
+            checkNavBarModes(mDisplayId);
         }
 
         @Override
@@ -220,6 +220,16 @@
         mEdgeBackGestureHandler = navBarHelper.getEdgeBackGestureHandler();
     }
 
+    @Override
+    public void onDisplayReady(int displayId) {
+        CommandQueue.Callbacks.super.onDisplayReady(displayId);
+    }
+
+    @Override
+    public void onDisplayRemoved(int displayId) {
+        CommandQueue.Callbacks.super.onDisplayRemoved(displayId);
+    }
+
     // Separated into a method to keep setDependencies() clean/readable.
     private LightBarTransitionsController createLightBarTransitionsController() {
 
@@ -349,31 +359,31 @@
         }
     }
 
-    void checkNavBarModes() {
+    void checkNavBarModes(int displayId) {
         if (mOverviewProxyService.getProxy() == null) {
             return;
         }
 
         try {
-            mOverviewProxyService.getProxy().checkNavBarModes();
+            mOverviewProxyService.getProxy().checkNavBarModes(displayId);
         } catch (RemoteException e) {
             Log.e(TAG, "checkNavBarModes() failed", e);
         }
     }
 
-    void finishBarAnimations() {
+    void finishBarAnimations(int displayId) {
         if (mOverviewProxyService.getProxy() == null) {
             return;
         }
 
         try {
-            mOverviewProxyService.getProxy().finishBarAnimations();
+            mOverviewProxyService.getProxy().finishBarAnimations(displayId);
         } catch (RemoteException e) {
             Log.e(TAG, "finishBarAnimations() failed", e);
         }
     }
 
-    void touchAutoDim() {
+    void touchAutoDim(int displayId) {
         if (mOverviewProxyService.getProxy() == null) {
             return;
         }
@@ -382,19 +392,19 @@
             int state = mStatusBarStateController.getState();
             boolean shouldReset =
                     state != StatusBarState.KEYGUARD && state != StatusBarState.SHADE_LOCKED;
-            mOverviewProxyService.getProxy().touchAutoDim(shouldReset);
+            mOverviewProxyService.getProxy().touchAutoDim(displayId, shouldReset);
         } catch (RemoteException e) {
             Log.e(TAG, "touchAutoDim() failed", e);
         }
     }
 
-    void transitionTo(@BarTransitions.TransitionMode int barMode, boolean animate) {
+    void transitionTo(int displayId, @BarTransitions.TransitionMode int barMode, boolean animate) {
         if (mOverviewProxyService.getProxy() == null) {
             return;
         }
 
         try {
-            mOverviewProxyService.getProxy().transitionTo(barMode, animate);
+            mOverviewProxyService.getProxy().transitionTo(displayId, barMode, animate);
         } catch (RemoteException e) {
             Log.e(TAG, "transitionTo() failed, barMode: " + barMode, e);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index d0f6f79..1fa5baa 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -118,9 +118,7 @@
                 getUserForHandlingNotesTaking(entryPoint)
             }
         activityContext.startActivityAsUser(
-            Intent(Intent.ACTION_MANAGE_DEFAULT_APP).apply {
-                putExtra(Intent.EXTRA_ROLE_NAME, ROLE_NOTES)
-            },
+            createNotesRoleHolderSettingsIntent(),
             user
         )
     }
@@ -399,6 +397,10 @@
          * @see com.android.launcher3.icons.IconCache.EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE
          */
         const val EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE = "extra_shortcut_badge_override_package"
+
+        /** Returns notes role holder settings intent. */
+        fun createNotesRoleHolderSettingsIntent() = Intent(Intent.ACTION_MANAGE_DEFAULT_APP).
+            putExtra(Intent.EXTRA_ROLE_NAME, ROLE_NOTES)
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt
index 4420002..2d62c10 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt
@@ -44,4 +44,7 @@
 
     /** @see [NoteTaskInitializer.callbacks] */
     KEYBOARD_SHORTCUT,
+
+    /** @see [NotesTile] */
+    QS_NOTES_TILE,
 }
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEventLogger.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEventLogger.kt
index a79057e..f152b01 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEventLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEventLogger.kt
@@ -19,6 +19,7 @@
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.notetask.NoteTaskEntryPoint.APP_CLIPS
 import com.android.systemui.notetask.NoteTaskEntryPoint.KEYBOARD_SHORTCUT
+import com.android.systemui.notetask.NoteTaskEntryPoint.QS_NOTES_TILE
 import com.android.systemui.notetask.NoteTaskEntryPoint.QUICK_AFFORDANCE
 import com.android.systemui.notetask.NoteTaskEntryPoint.TAIL_BUTTON
 import com.android.systemui.notetask.NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT
@@ -43,45 +44,47 @@
     /** Logs a [NoteTaskInfo] as an **open** [NoteTaskUiEvent], including package name and uid. */
     fun logNoteTaskOpened(info: NoteTaskInfo) {
         val event =
-                when (info.entryPoint) {
-                    TAIL_BUTTON -> {
-                        if (info.isKeyguardLocked) {
-                            NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED
-                        } else {
-                            NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON
-                        }
+            when (info.entryPoint) {
+                TAIL_BUTTON -> {
+                    if (info.isKeyguardLocked) {
+                        NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED
+                    } else {
+                        NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON
                     }
-
-                    WIDGET_PICKER_SHORTCUT,
-                    WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE -> NOTE_OPENED_VIA_SHORTCUT
-
-                    QUICK_AFFORDANCE -> NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE
-                    APP_CLIPS,
-                    KEYBOARD_SHORTCUT,
-                    null -> return
                 }
+
+                WIDGET_PICKER_SHORTCUT,
+                WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE -> NOTE_OPENED_VIA_SHORTCUT
+
+                QUICK_AFFORDANCE -> NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE
+                APP_CLIPS,
+                KEYBOARD_SHORTCUT,
+                QS_NOTES_TILE,  // TODO(b/376640872): Add logging for QS Tile entry point.
+                null -> return
+            }
         uiEventLogger.log(event, info.uid, info.packageName)
     }
 
     /** Logs a [NoteTaskInfo] as a **closed** [NoteTaskUiEvent], including package name and uid. */
     fun logNoteTaskClosed(info: NoteTaskInfo) {
         val event =
-                when (info.entryPoint) {
-                    TAIL_BUTTON -> {
-                        if (info.isKeyguardLocked) {
-                            NoteTaskUiEvent.NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON_LOCKED
-                        } else {
-                            NoteTaskUiEvent.NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON
-                        }
+            when (info.entryPoint) {
+                TAIL_BUTTON -> {
+                    if (info.isKeyguardLocked) {
+                        NoteTaskUiEvent.NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON_LOCKED
+                    } else {
+                        NoteTaskUiEvent.NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON
                     }
-
-                    WIDGET_PICKER_SHORTCUT,
-                    WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE,
-                    QUICK_AFFORDANCE,
-                    APP_CLIPS,
-                    KEYBOARD_SHORTCUT,
-                    null -> return
                 }
+
+                WIDGET_PICKER_SHORTCUT,
+                WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE,
+                QUICK_AFFORDANCE,
+                APP_CLIPS,
+                KEYBOARD_SHORTCUT,
+                QS_NOTES_TILE,
+                null -> return
+            }
         uiEventLogger.log(event, info.uid, info.packageName)
     }
 
@@ -90,19 +93,20 @@
 
         @UiEvent(doc = "User opened a note by tapping on the lockscreen shortcut.")
         NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE(1294),
-
-        @UiEvent(doc = "User opened a note by pressing the stylus tail button while the screen was unlocked.") // ktlint-disable max-line-length
+        @UiEvent(
+            doc =
+                "User opened a note by pressing the stylus tail button while the screen was unlocked."
+        )
         NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON(1295),
-
-        @UiEvent(doc = "User opened a note by pressing the stylus tail button while the screen was locked.") // ktlint-disable max-line-length
+        @UiEvent(
+            doc =
+                "User opened a note by pressing the stylus tail button while the screen was locked."
+        )
         NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED(1296),
-
         @UiEvent(doc = "User opened a note by tapping on an app shortcut.")
         NOTE_OPENED_VIA_SHORTCUT(1297),
-
         @UiEvent(doc = "Note closed via a tail button while device is unlocked")
         NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON(1311),
-
         @UiEvent(doc = "Note closed via a tail button while device is locked")
         NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON_LOCKED(1312);
 
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
index c7aae3c..a1c5c9c 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
@@ -27,11 +27,27 @@
 import com.android.systemui.notetask.quickaffordance.NoteTaskQuickAffordanceModule
 import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
 import com.android.systemui.notetask.shortcut.LaunchNoteTaskActivity
+import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.shared.model.TileCategory
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tiles.NotesTile
+import com.android.systemui.qs.tiles.base.interactor.QSTileAvailabilityInteractor
+import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory
+import com.android.systemui.qs.tiles.impl.notes.domain.NotesTileMapper
+import com.android.systemui.qs.tiles.impl.notes.domain.interactor.NotesTileDataInteractor
+import com.android.systemui.qs.tiles.impl.notes.domain.interactor.NotesTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.notes.domain.model.NotesTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
+import com.android.systemui.res.R
 import dagger.Binds
 import dagger.Module
 import dagger.Provides
 import dagger.multibindings.ClassKey
 import dagger.multibindings.IntoMap
+import dagger.multibindings.StringKey
 
 /** Compose all dependencies required by Note Task feature. */
 @Module(includes = [NoteTaskQuickAffordanceModule::class])
@@ -54,8 +70,22 @@
     @[Binds IntoMap ClassKey(CreateNoteTaskShortcutActivity::class)]
     fun bindNoteTaskShortcutActivity(activity: CreateNoteTaskShortcutActivity): Activity
 
+    @Binds
+    @IntoMap
+    @StringKey(NOTES_TILE_SPEC)
+    fun provideNotesAvailabilityInteractor(
+        impl: NotesTileDataInteractor
+    ): QSTileAvailabilityInteractor
+
+    @Binds
+    @IntoMap
+    @StringKey(NotesTile.TILE_SPEC)
+    fun bindNotesTile(notesTile: NotesTile): QSTileImpl<*>
+
     companion object {
 
+        const val NOTES_TILE_SPEC = "notes"
+
         @[Provides NoteTaskEnabledKey]
         fun provideIsNoteTaskEnabled(
             featureFlags: FeatureFlags,
@@ -65,5 +95,37 @@
             val isFeatureEnabled = featureFlags.isEnabled(Flags.NOTE_TASKS)
             return isRoleAvailable && isFeatureEnabled
         }
+
+        /** Inject NotesTile into tileViewModelMap in QSModule */
+        @Provides
+        @IntoMap
+        @StringKey(NOTES_TILE_SPEC)
+        fun provideNotesTileViewModel(
+            factory: QSTileViewModelFactory.Static<NotesTileModel>,
+            mapper: NotesTileMapper,
+            stateInteractor: NotesTileDataInteractor,
+            userActionInteractor: NotesTileUserActionInteractor,
+        ): QSTileViewModel =
+            factory.create(
+                TileSpec.create(NOTES_TILE_SPEC),
+                userActionInteractor,
+                stateInteractor,
+                mapper,
+            )
+
+        @Provides
+        @IntoMap
+        @StringKey(NOTES_TILE_SPEC)
+        fun provideNotesTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+            QSTileConfig(
+                tileSpec = TileSpec.create(NOTES_TILE_SPEC),
+                uiConfig =
+                QSTileUIConfig.Resource(
+                    iconRes = R.drawable.ic_qs_notes,
+                    labelRes = R.string.quick_settings_notes_label,
+                ),
+                instanceId = uiEventLogger.getNewInstanceId(),
+                category = TileCategory.UTILITIES,
+            )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.kt
index 63bfbd1..195b0ce 100644
--- a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.kt
@@ -18,7 +18,6 @@
 
 import com.android.compose.animation.scene.Back
 import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
 import com.android.compose.animation.scene.UserActionResult.HideOverlay
@@ -38,7 +37,7 @@
             mapOf(
                 Swipe.Up to HideOverlay(Overlays.NotificationsShade),
                 Back to HideOverlay(Overlays.NotificationsShade),
-                Swipe(direction = SwipeDirection.Down, fromSource = SceneContainerEdge.TopRight) to
+                Swipe.Down(fromSource = SceneContainerEdge.TopRight) to
                     ReplaceByOverlay(Overlays.QuickSettingsShade),
             )
         )
diff --git a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt
index 7178d09..4fe6337 100644
--- a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt
@@ -16,19 +16,23 @@
 
 package com.android.systemui.notifications.ui.viewmodel
 
+import androidx.compose.runtime.getValue
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.lifecycle.Hydrator
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /**
  * Models UI state used to render the content of the notifications shade overlay.
@@ -43,10 +47,32 @@
     val notificationsPlaceholderViewModelFactory: NotificationsPlaceholderViewModel.Factory,
     val sceneInteractor: SceneInteractor,
     private val shadeInteractor: ShadeInteractor,
+    activeNotificationsInteractor: ActiveNotificationsInteractor,
 ) : ExclusiveActivatable() {
 
+    private val hydrator = Hydrator("NotificationsShadeOverlayContentViewModel.hydrator")
+
+    val showHeader: Boolean by
+        hydrator.hydratedStateOf(
+            traceName = "showHeader",
+            initialValue =
+                shouldShowHeader(
+                    isShadeLayoutWide = shadeInteractor.isShadeLayoutWide.value,
+                    areAnyNotificationsPresent =
+                        activeNotificationsInteractor.areAnyNotificationsPresentValue,
+                ),
+            source =
+                combine(
+                    shadeInteractor.isShadeLayoutWide,
+                    activeNotificationsInteractor.areAnyNotificationsPresent,
+                    this::shouldShowHeader,
+                ),
+        )
+
     override suspend fun onActivated(): Nothing {
         coroutineScope {
+            launch { hydrator.activate() }
+
             launch {
                 sceneInteractor.currentScene.collect { currentScene ->
                     when (currentScene) {
@@ -77,6 +103,13 @@
         shadeInteractor.collapseNotificationsShade(loggingReason = "shade scrim clicked")
     }
 
+    private fun shouldShowHeader(
+        isShadeLayoutWide: Boolean,
+        areAnyNotificationsPresent: Boolean,
+    ): Boolean {
+        return !isShadeLayoutWide && areAnyNotificationsPresent
+    }
+
     @AssistedFactory
     interface Factory {
         fun create(): NotificationsShadeOverlayContentViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeUserActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeUserActionsViewModel.kt
index 11854d9..398ace4 100644
--- a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeUserActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeUserActionsViewModel.kt
@@ -18,7 +18,6 @@
 
 import com.android.compose.animation.scene.Back
 import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
 import com.android.compose.animation.scene.UserActionResult.ReplaceByOverlay
@@ -40,7 +39,7 @@
             mapOf(
                 Back to SceneFamilies.Home,
                 Swipe.Up to SceneFamilies.Home,
-                Swipe(direction = SwipeDirection.Down, fromSource = SceneContainerEdge.TopRight) to
+                Swipe.Down(fromSource = SceneContainerEdge.TopRight) to
                     ReplaceByOverlay(Overlays.QuickSettingsShade),
             )
         )
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
index bacff99..51ff598 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -31,6 +31,7 @@
 import androidx.activity.OnBackPressedDispatcher
 import androidx.activity.OnBackPressedDispatcherOwner
 import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
+import androidx.annotation.VisibleForTesting
 import androidx.compose.animation.AnimatedContent
 import androidx.compose.animation.AnimatedVisibility
 import androidx.compose.animation.core.tween
@@ -41,14 +42,14 @@
 import androidx.compose.foundation.layout.Arrangement.spacedBy
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.WindowInsets
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.navigationBars
+import androidx.compose.foundation.layout.heightIn
 import androidx.compose.foundation.layout.offset
-import androidx.compose.foundation.layout.windowInsetsPadding
+import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
@@ -59,6 +60,7 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.runtime.snapshotFlow
+import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.input.pointer.PointerEventPass
@@ -75,7 +77,6 @@
 import androidx.compose.ui.semantics.customActions
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.unit.IntOffset
-import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.round
 import androidx.compose.ui.util.fastRoundToInt
 import androidx.compose.ui.viewinterop.AndroidView
@@ -97,6 +98,7 @@
 import com.android.compose.modifiers.thenIf
 import com.android.compose.theme.PlatformTheme
 import com.android.systemui.Dumpable
+import com.android.systemui.brightness.ui.compose.BrightnessSliderContainer
 import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.lifecycle.repeatWhenAttached
@@ -107,6 +109,7 @@
 import com.android.systemui.qs.composefragment.SceneKeys.QuickQuickSettings
 import com.android.systemui.qs.composefragment.SceneKeys.QuickSettings
 import com.android.systemui.qs.composefragment.SceneKeys.toIdleSceneKey
+import com.android.systemui.qs.composefragment.ui.GridAnchor
 import com.android.systemui.qs.composefragment.ui.NotificationScrimClipParams
 import com.android.systemui.qs.composefragment.ui.notificationScrimClip
 import com.android.systemui.qs.composefragment.ui.quickQuickSettingsToQuickSettings
@@ -115,8 +118,8 @@
 import com.android.systemui.qs.footer.ui.compose.FooterActions
 import com.android.systemui.qs.panels.ui.compose.EditMode
 import com.android.systemui.qs.panels.ui.compose.QuickQuickSettings
+import com.android.systemui.qs.panels.ui.compose.TileGrid
 import com.android.systemui.qs.shared.ui.ElementKeys
-import com.android.systemui.qs.ui.composable.QuickSettingsLayout
 import com.android.systemui.qs.ui.composable.QuickSettingsShade
 import com.android.systemui.qs.ui.composable.QuickSettingsTheme
 import com.android.systemui.res.R
@@ -195,6 +198,7 @@
         val context = inflater.context
         val composeView =
             ComposeView(context).apply {
+                id = R.id.quick_settings_container
                 repeatWhenAttached {
                     repeatOnLifecycle(Lifecycle.State.CREATED) {
                         setViewTreeOnBackPressedDispatcherOwner(
@@ -239,7 +243,6 @@
                 visible = viewModel.isQsVisible,
                 modifier =
                     Modifier.graphicsLayer { alpha = viewModel.viewAlpha }
-                        .windowInsetsPadding(WindowInsets.navigationBars)
                         // Clipping before translation to match QSContainerImpl.onDraw
                         .offset {
                             IntOffset(x = 0, y = viewModel.viewTranslationY.fastRoundToInt())
@@ -299,7 +302,7 @@
                 transitions =
                     transitions {
                         from(QuickQuickSettings, QuickSettings) {
-                            quickQuickSettingsToQuickSettings(viewModel::inFirstPage::get)
+                            quickQuickSettingsToQuickSettings(viewModel::animateTilesExpansion::get)
                         }
                     },
             )
@@ -596,8 +599,21 @@
                         }
                         .padding(top = { qqsPadding }, bottom = { bottomPadding })
             ) {
+                val Tiles =
+                    @Composable {
+                        QuickQuickSettings(
+                            viewModel = viewModel.containerViewModel.quickQuickSettingsViewModel
+                        )
+                    }
+                val Media =
+                    @Composable {
+                        if (viewModel.qqsMediaVisible) {
+                            MediaObject(mediaHost = viewModel.qqsMediaHost)
+                        }
+                    }
+
                 if (viewModel.isQsEnabled) {
-                    Column(
+                    Box(
                         modifier =
                             Modifier.collapseExpandSemanticAction(
                                     stringResource(
@@ -608,16 +624,13 @@
                                     horizontal = {
                                         QuickSettingsShade.Dimensions.Padding.roundToPx()
                                     }
-                                ),
-                        verticalArrangement =
-                            spacedBy(dimensionResource(R.dimen.qs_tile_margin_vertical)),
+                                )
                     ) {
-                        QuickQuickSettings(
-                            viewModel = viewModel.containerViewModel.quickQuickSettingsViewModel
+                        QuickQuickSettingsLayout(
+                            tiles = Tiles,
+                            media = Media,
+                            mediaInRow = viewModel.qqsMediaInRow,
                         )
-                        if (viewModel.qqsMediaVisible) {
-                            MediaObject(mediaHost = viewModel.qqsMediaHost)
-                        }
                     }
                 }
             }
@@ -657,23 +670,58 @@
                                 .verticalScroll(scrollState)
                                 .sysuiResTag(ResIdTags.qsScroll)
                     ) {
+                        val containerViewModel = viewModel.containerViewModel
                         Spacer(
                             modifier = Modifier.height { qqsPadding + qsExtraPadding.roundToPx() }
                         )
-                        QuickSettingsLayout(
-                            viewModel = viewModel.containerViewModel,
-                            modifier = Modifier.sysuiResTag(ResIdTags.quickSettingsPanel),
-                        )
-                        Spacer(modifier = Modifier.height(8.dp))
-                        if (viewModel.qsMediaVisible) {
-                            MediaObject(
-                                mediaHost = viewModel.qsMediaHost,
-                                modifier =
-                                    Modifier.padding(
-                                        horizontal = {
-                                            QuickSettingsShade.Dimensions.Padding.roundToPx()
-                                        }
-                                    ),
+                        val BrightnessSlider =
+                            @Composable {
+                                BrightnessSliderContainer(
+                                    viewModel = containerViewModel.brightnessSliderViewModel,
+                                    modifier =
+                                        Modifier.fillMaxWidth()
+                                            .height(
+                                                QuickSettingsShade.Dimensions.BrightnessSliderHeight
+                                            ),
+                                )
+                            }
+                        val TileGrid =
+                            @Composable {
+                                Box {
+                                    GridAnchor()
+                                    TileGrid(
+                                        viewModel = containerViewModel.tileGridViewModel,
+                                        modifier =
+                                            Modifier.fillMaxWidth()
+                                                .heightIn(
+                                                    max =
+                                                        QuickSettingsShade.Dimensions.GridMaxHeight
+                                                ),
+                                        containerViewModel.editModeViewModel::startEditing,
+                                    )
+                                }
+                            }
+                        val Media =
+                            @Composable {
+                                if (viewModel.qsMediaVisible) {
+                                    MediaObject(mediaHost = viewModel.qsMediaHost)
+                                }
+                            }
+                        Box(
+                            modifier =
+                                Modifier.fillMaxWidth()
+                                    .sysuiResTag(ResIdTags.quickSettingsPanel)
+                                    .padding(
+                                        top = QuickSettingsShade.Dimensions.Padding,
+                                        start = QuickSettingsShade.Dimensions.Padding,
+                                        end = QuickSettingsShade.Dimensions.Padding,
+                                    )
+                        ) {
+                            QuickSettingsLayout(
+                                brightness = BrightnessSlider,
+                                tiles = TileGrid,
+                                media = Media,
+                                mediaInRow = viewModel.qsMediaInRow,
                             )
                         }
                     }
@@ -957,6 +1005,63 @@
     }
 }
 
+@Composable
+@VisibleForTesting
+fun QuickQuickSettingsLayout(
+    tiles: @Composable () -> Unit,
+    media: @Composable () -> Unit,
+    mediaInRow: Boolean,
+) {
+    if (mediaInRow) {
+        Row(
+            horizontalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_vertical)),
+            verticalAlignment = Alignment.CenterVertically,
+        ) {
+            Box(modifier = Modifier.weight(1f)) { tiles() }
+            Box(modifier = Modifier.weight(1f)) { media() }
+        }
+    } else {
+        Column(verticalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_vertical))) {
+            tiles()
+            media()
+        }
+    }
+}
+
+@Composable
+@VisibleForTesting
+fun QuickSettingsLayout(
+    brightness: @Composable () -> Unit,
+    tiles: @Composable () -> Unit,
+    media: @Composable () -> Unit,
+    mediaInRow: Boolean,
+) {
+    if (mediaInRow) {
+        Column(
+            verticalArrangement = spacedBy(QuickSettingsShade.Dimensions.Padding),
+            horizontalAlignment = Alignment.CenterHorizontally,
+        ) {
+            brightness()
+            Row(
+                horizontalArrangement = spacedBy(QuickSettingsShade.Dimensions.Padding),
+                verticalAlignment = Alignment.CenterVertically,
+            ) {
+                Box(modifier = Modifier.weight(1f)) { tiles() }
+                Box(modifier = Modifier.weight(1f)) { media() }
+            }
+        }
+    } else {
+        Column(
+            verticalArrangement = spacedBy(QuickSettingsShade.Dimensions.Padding),
+            horizontalAlignment = Alignment.CenterHorizontally,
+        ) {
+            brightness()
+            tiles()
+            media()
+        }
+    }
+}
+
 private object ResIdTags {
     const val quickSettingsPanel = "quick_settings_panel"
     const val quickQsPanel = "quick_qs_panel"
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeModule.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeModule.kt
index 5127320..676f6a4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeModule.kt
@@ -19,6 +19,7 @@
 import android.content.Context
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.qs.flags.QSComposeFragment
 import com.android.systemui.util.Utils
 import dagger.Module
 import dagger.Provides
@@ -34,7 +35,7 @@
         @SysUISingleton
         @Named(QS_USING_MEDIA_PLAYER)
         fun providesUsingMedia(@Application context: Context): Boolean {
-            return Utils.useQsMediaPlayer(context)
+            return QSComposeFragment.isEnabled && Utils.useQsMediaPlayer(context)
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/FromQuickQuickSettingsToQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/FromQuickQuickSettingsToQuickSettings.kt
index 9e3945e..c1a4174 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/FromQuickQuickSettingsToQuickSettings.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/FromQuickQuickSettingsToQuickSettings.kt
@@ -20,7 +20,9 @@
 import com.android.systemui.qs.composefragment.SceneKeys
 import com.android.systemui.qs.shared.ui.ElementKeys
 
-fun TransitionBuilder.quickQuickSettingsToQuickSettings(inFirstPage: () -> Boolean = { true }) {
+fun TransitionBuilder.quickQuickSettingsToQuickSettings(
+    animateTilesExpansion: () -> Boolean = { true }
+) {
 
     fractionRange(start = 0.5f) { fade(ElementKeys.QuickSettingsContent) }
 
@@ -28,7 +30,7 @@
 
     anchoredTranslate(ElementKeys.QuickSettingsContent, ElementKeys.GridAnchor)
 
-    sharedElement(ElementKeys.TileElementMatcher, enabled = inFirstPage())
+    sharedElement(ElementKeys.TileElementMatcher, enabled = animateTilesExpansion())
 
     // This will animate between 0f (QQS) and 0.6, fading in the QQS tiles when coming back
     // from non first page QS. The QS content ends fading out at 0.5f, so there's a brief
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
index 0ca621d..624cf30 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
@@ -39,6 +39,8 @@
 import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.lifecycle.Hydrator
 import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QQS
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QS
 import com.android.systemui.media.controls.ui.view.MediaHost
 import com.android.systemui.media.controls.ui.view.MediaHostState
 import com.android.systemui.media.dagger.MediaModule.QS_PANEL
@@ -49,10 +51,12 @@
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
 import com.android.systemui.qs.panels.domain.interactor.TileSquishinessInteractor
 import com.android.systemui.qs.panels.ui.viewmodel.InFirstPageViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.MediaInRowInLandscapeViewModel
 import com.android.systemui.qs.ui.viewmodel.QuickSettingsContainerViewModel
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.LargeScreenHeaderHelper
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -90,10 +94,11 @@
     disableFlagsRepository: DisableFlagsRepository,
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val largeScreenShadeInterpolator: LargeScreenShadeInterpolator,
-    configurationInteractor: ConfigurationInteractor,
+    @ShadeDisplayAware configurationInteractor: ConfigurationInteractor,
     private val largeScreenHeaderHelper: LargeScreenHeaderHelper,
     private val squishinessInteractor: TileSquishinessInteractor,
     private val inFirstPageViewModel: InFirstPageViewModel,
+    mediaInRowInLandscapeViewModelFactory: MediaInRowInLandscapeViewModel.Factory,
     @Named(QUICK_QS_PANEL) val qqsMediaHost: MediaHost,
     @Named(QS_PANEL) val qsMediaHost: MediaHost,
     @Named(QSFragmentComposeModule.QS_USING_MEDIA_PLAYER) private val usingMedia: Boolean,
@@ -101,6 +106,8 @@
 ) : Dumpable, ExclusiveActivatable() {
 
     val containerViewModel = containerViewModelFactory.create(true)
+    private val qqsMediaInRowViewModel = mediaInRowInLandscapeViewModelFactory.create(LOCATION_QQS)
+    private val qsMediaInRowViewModel = mediaInRowInLandscapeViewModelFactory.create(LOCATION_QS)
 
     private val hydrator = Hydrator("QSFragmentComposeViewModel.hydrator")
 
@@ -195,7 +202,7 @@
         }
     }
 
-    val isQsFullyExpanded by derivedStateOf { expansionState.progress >= 1f }
+    val isQsFullyExpanded by derivedStateOf { expansionState.progress >= 1f && isQsExpanded }
 
     /**
      * Accessibility action for collapsing/expanding QS. The provided runnable is responsible for
@@ -203,9 +210,6 @@
      */
     var collapseExpandAccessibilityAction: Runnable? = null
 
-    val inFirstPage: Boolean
-        get() = inFirstPageViewModel.inFirstPage
-
     var overScrollAmount by mutableStateOf(0)
 
     val viewTranslationY by derivedStateOf {
@@ -252,6 +256,9 @@
                 },
         )
 
+    val qqsMediaInRow: Boolean
+        get() = qqsMediaInRowViewModel.shouldMediaShowInRow
+
     val qsMediaVisible by
         hydrator.hydratedStateOf(
             traceName = "qsMediaVisible",
@@ -259,6 +266,18 @@
             source = if (usingMedia) mediaHostVisible(qsMediaHost) else flowOf(false),
         )
 
+    val qsMediaInRow: Boolean
+        get() = qsMediaInRowViewModel.shouldMediaShowInRow
+
+    val animateTilesExpansion: Boolean
+        get() = inFirstPage && !mediaSuddenlyAppearingInLandscape
+
+    private val inFirstPage: Boolean
+        get() = inFirstPageViewModel.inFirstPage
+
+    private val mediaSuddenlyAppearingInLandscape: Boolean
+        get() = !qqsMediaInRow && qsMediaInRow
+
     private var qsBounds by mutableStateOf(Rect())
 
     private val constrainedSquishinessFraction: Float
@@ -362,6 +381,8 @@
             launch { hydrateSquishinessInteractor() }
             launch { hydrator.activate() }
             launch { containerViewModel.activate() }
+            launch { qqsMediaInRowViewModel.activate() }
+            launch { qsMediaInRowViewModel.activate() }
             awaitCancellation()
         }
     }
@@ -391,6 +412,7 @@
                 println("isQSVisible", isQsVisible)
                 println("isQSEnabled", isQsEnabled)
                 println("isCustomizing", containerViewModel.editModeViewModel.isEditing.value)
+                println("inFirstPage", inFirstPage)
             }
             printSection("Expansion state") {
                 println("qsExpansion", qsExpansion)
@@ -423,7 +445,9 @@
             }
             printSection("Media") {
                 println("qqsMediaVisible", qqsMediaVisible)
+                println("qqsMediaInRow", qqsMediaInRow)
                 println("qsMediaVisible", qsMediaVisible)
+                println("qsMediaInRow", qsMediaInRow)
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
index 43fd0f5..1f55ac7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
@@ -35,8 +35,6 @@
 import com.android.systemui.qs.panels.ui.compose.infinitegrid.InfiniteGridLayout
 import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModelImpl
-import com.android.systemui.qs.panels.ui.viewmodel.QSColumnsSizeViewModelImpl
-import com.android.systemui.qs.panels.ui.viewmodel.QSColumnsViewModel
 import dagger.Binds
 import dagger.Module
 import dagger.Provides
@@ -58,8 +56,6 @@
 
     @Binds fun bindIconTilesViewModel(impl: IconTilesViewModelImpl): IconTilesViewModel
 
-    @Binds fun bindQSColumnsViewModel(impl: QSColumnsSizeViewModelImpl): QSColumnsViewModel
-
     @Binds
     @PaginatedBaseLayoutType
     fun bindPaginatedBaseGridLayout(impl: InfiniteGridLayout): PaginatableGridLayout
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt
index 6cc2cbc..2efe500 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt
@@ -73,7 +73,7 @@
             tiles.forEach { it.startListening(token) }
             onDispose { tiles.forEach { it.stopListening(token) } }
         }
-        val columns by viewModel.columns
+        val columns = viewModel.columns
         val rows = viewModel.rows
 
         val pages =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
index 19ab29e..5ac2ad0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.grid.ui.compose.VerticalSpannedGrid
 import com.android.systemui.haptics.msdl.qs.TileHapticsViewModelFactoryProvider
 import com.android.systemui.lifecycle.rememberViewModel
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QS
 import com.android.systemui.qs.panels.shared.model.SizedTileImpl
 import com.android.systemui.qs.panels.ui.compose.PaginatableGridLayout
 import com.android.systemui.qs.panels.ui.compose.bounceableInfo
@@ -72,7 +73,12 @@
             rememberViewModel(traceName = "InfiniteGridLayout.TileGrid") {
                 viewModel.dynamicIconTilesViewModelFactory.create()
             }
-        val columns by viewModel.gridSizeViewModel.columns
+        val columnsWithMediaViewModel =
+            rememberViewModel(traceName = "InfiniteGridLAyout.TileGrid") {
+                viewModel.columnsWithMediaViewModelFactory.create(LOCATION_QS)
+            }
+
+        val columns = columnsWithMediaViewModel.columns
         val sizedTiles = tiles.map { SizedTileImpl(it, it.spec.width()) }
         val bounceables =
             remember(sizedTiles) { List(sizedTiles.size) { BounceableTileViewModel() } }
@@ -118,7 +124,11 @@
             rememberViewModel(traceName = "InfiniteGridLayout.EditTileGrid") {
                 viewModel.dynamicIconTilesViewModelFactory.create()
             }
-        val columns by viewModel.gridSizeViewModel.columns
+        val columnsViewModel =
+            rememberViewModel(traceName = "InfiniteGridLayout.EditTileGrid") {
+                viewModel.columnsWithMediaViewModelFactory.createWithoutMediaTracking()
+            }
+        val columns = columnsViewModel.columns
         val largeTiles by iconTilesViewModel.largeTiles.collectAsStateWithLifecycle()
 
         // Non-current tiles should always be displayed as icon tiles.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt
index 7fe856b..4e34e73 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor.Companion.POSITION_AT_END
 import com.android.systemui.qs.pipeline.domain.interactor.MinimumTilesInteractor
 import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.util.kotlin.emitOnStart
 import javax.inject.Inject
 import javax.inject.Named
@@ -54,7 +55,7 @@
     private val currentTilesInteractor: CurrentTilesInteractor,
     private val tilesAvailabilityInteractor: TilesAvailabilityInteractor,
     private val minTilesInteractor: MinimumTilesInteractor,
-    private val configurationInteractor: ConfigurationInteractor,
+    @ShadeDisplayAware private val configurationInteractor: ConfigurationInteractor,
     @Application private val applicationContext: Context,
     @Named("Default") private val defaultGridLayout: GridLayout,
     @Application private val applicationScope: CoroutineScope,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridViewModel.kt
index d687100..3327141 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridViewModel.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.qs.panels.ui.viewmodel
 
-import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.qs.panels.ui.dialog.QSResetDialogDelegate
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
@@ -25,19 +24,15 @@
 @AssistedInject
 constructor(
     val dynamicIconTilesViewModelFactory: DynamicIconTilesViewModel.Factory,
-    val gridSizeViewModel: QSColumnsViewModel,
+    val columnsWithMediaViewModelFactory: QSColumnsViewModel.Factory,
     val squishinessViewModel: TileSquishinessViewModel,
     private val resetDialogDelegate: QSResetDialogDelegate,
-) : ExclusiveActivatable() {
+) {
 
     fun showResetDialog() {
         resetDialogDelegate.showDialog()
     }
 
-    override suspend fun onActivated(): Nothing {
-        gridSizeViewModel.activate()
-    }
-
     @AssistedFactory
     interface Factory {
         fun create(): InfiniteGridViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModel.kt
new file mode 100644
index 0000000..2ed8fd2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModel.kt
@@ -0,0 +1,118 @@
+/*
+ * 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.qs.panels.ui.viewmodel
+
+import android.content.res.Configuration
+import android.content.res.Resources
+import androidx.compose.runtime.getValue
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.lifecycle.Hydrator
+import com.android.systemui.media.controls.ui.controller.MediaHostStatesManager
+import com.android.systemui.media.controls.ui.controller.MediaLocation
+import com.android.systemui.media.controls.ui.view.MediaHostState
+import com.android.systemui.qs.composefragment.dagger.QSFragmentComposeModule
+import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import javax.inject.Named
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+
+/**
+ * Indicates whether a particular UMO in [LOCATION_QQS] or [LOCATION_QS] should currently show in a
+ * row with the tiles, based on its visibility and device configuration. If the player is not
+ * visible, it will never indicate that media should show in row.
+ */
+class MediaInRowInLandscapeViewModel
+@AssistedInject
+constructor(
+    @Main resources: Resources,
+    configurationInteractor: ConfigurationInteractor,
+    shadeModeInteractor: ShadeModeInteractor,
+    private val mediaHostStatesManager: MediaHostStatesManager,
+    @Named(QSFragmentComposeModule.QS_USING_MEDIA_PLAYER) private val usingMedia: Boolean,
+    @Assisted @MediaLocation private val inLocation: Int,
+) : ExclusiveActivatable() {
+
+    private val hydrator = Hydrator("MediaInRowInLanscapeViewModel - $inLocation")
+
+    val shouldMediaShowInRow: Boolean
+        get() = usingMedia && inSingleShade && isLandscapeAndLong && isMediaVisible
+
+    private val inSingleShade: Boolean by
+        hydrator.hydratedStateOf(
+            traceName = "inSingleShade",
+            initialValue = shadeModeInteractor.shadeMode.value == ShadeMode.Single,
+            source = shadeModeInteractor.shadeMode.map { it == ShadeMode.Single },
+        )
+
+    private val isLandscapeAndLong: Boolean by
+        hydrator.hydratedStateOf(
+            traceName = "isLandscapeAndLong",
+            initialValue = resources.configuration.isLandscapeAndLong,
+            source = configurationInteractor.configurationValues.map { it.isLandscapeAndLong },
+        )
+
+    private val isMediaVisible by
+        hydrator.hydratedStateOf(
+            traceName = "isMediaVisible",
+            initialValue = false,
+            source =
+                conflatedCallbackFlow {
+                        val callback =
+                            object : MediaHostStatesManager.Callback {
+                                override fun onHostStateChanged(
+                                    location: Int,
+                                    mediaHostState: MediaHostState,
+                                ) {
+                                    if (location == inLocation) {
+                                        trySend(mediaHostState.visible)
+                                    }
+                                }
+                            }
+                        mediaHostStatesManager.addCallback(callback)
+
+                        awaitClose { mediaHostStatesManager.removeCallback(callback) }
+                    }
+                    .onStart {
+                        emit(
+                            mediaHostStatesManager.mediaHostStates.get(inLocation)?.visible ?: false
+                        )
+                    },
+        )
+
+    override suspend fun onActivated(): Nothing {
+        hydrator.activate()
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(@MediaLocation inLocation: Int): MediaInRowInLandscapeViewModel
+    }
+}
+
+private val Configuration.isLandscapeAndLong: Boolean
+    get() =
+        orientation == Configuration.ORIENTATION_LANDSCAPE &&
+            (screenLayout and Configuration.SCREENLAYOUT_LONG_MASK) ==
+                Configuration.SCREENLAYOUT_LONG_YES
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt
index 8bd9ed0..e5607eb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt
@@ -16,10 +16,10 @@
 
 package com.android.systemui.qs.panels.ui.viewmodel
 
-import androidx.compose.runtime.State
 import androidx.compose.runtime.getValue
 import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.lifecycle.Hydrator
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QS
 import com.android.systemui.qs.panels.domain.interactor.PaginatedGridInteractor
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
@@ -31,12 +31,13 @@
 @AssistedInject
 constructor(
     iconTilesViewModel: IconTilesViewModel,
-    private val gridSizeViewModel: QSColumnsViewModel,
+    columnsWithMediaViewModelFactory: QSColumnsViewModel.Factory,
     paginatedGridInteractor: PaginatedGridInteractor,
     inFirstPageViewModel: InFirstPageViewModel,
 ) : IconTilesViewModel by iconTilesViewModel, ExclusiveActivatable() {
 
     private val hydrator = Hydrator("PaginatedGridViewModel")
+    private val columnsWithMediaViewModel = columnsWithMediaViewModelFactory.create(LOCATION_QS)
 
     val rows by
         hydrator.hydratedStateOf(
@@ -47,13 +48,13 @@
 
     var inFirstPage by inFirstPageViewModel::inFirstPage
 
-    val columns: State<Int>
-        get() = gridSizeViewModel.columns
+    val columns: Int
+        get() = columnsWithMediaViewModel.columns
 
     override suspend fun onActivated(): Nothing {
         coroutineScope {
             launch { hydrator.activate() }
-            launch { gridSizeViewModel.activate() }
+            launch { columnsWithMediaViewModel.activate() }
             awaitCancellation()
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModel.kt
index 8926d2f..85b7831 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModel.kt
@@ -16,25 +16,61 @@
 
 package com.android.systemui.qs.panels.ui.viewmodel
 
-import androidx.compose.runtime.State
-import com.android.systemui.lifecycle.Activatable
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
 import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.lifecycle.Hydrator
+import com.android.systemui.media.controls.ui.controller.MediaLocation
 import com.android.systemui.qs.panels.domain.interactor.QSColumnsInteractor
-import javax.inject.Inject
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
 
-interface QSColumnsViewModel : Activatable {
-    val columns: State<Int>
-}
+/**
+ * View model for the number of columns that should be shown in a QS grid.
+ * * Create it with a [MediaLocation] to halve the number of columns when media should show in a row
+ *   with the tiles.
+ * * Create it with a `null` [MediaLocation] to ignore media visibility (useful for edit mode).
+ */
+class QSColumnsViewModel
+@AssistedInject
+constructor(
+    interactor: QSColumnsInteractor,
+    mediaInRowInLandscapeViewModelFactory: MediaInRowInLandscapeViewModel.Factory,
+    @Assisted @MediaLocation mediaLocation: Int?,
+) : ExclusiveActivatable() {
 
-class QSColumnsSizeViewModelImpl @Inject constructor(interactor: QSColumnsInteractor) :
-    QSColumnsViewModel, ExclusiveActivatable() {
-    private val hydrator = Hydrator("QSColumnsSizeViewModelImpl")
+    private val hydrator = Hydrator("QSColumnsViewModelWithMedia")
 
-    override val columns =
-        hydrator.hydratedStateOf(traceName = "columns", source = interactor.columns)
+    val columns by derivedStateOf {
+        if (mediaInRowInLandscapeViewModel?.shouldMediaShowInRow == true) {
+            columnsWithoutMedia / 2
+        } else {
+            columnsWithoutMedia
+        }
+    }
+
+    private val mediaInRowInLandscapeViewModel =
+        mediaLocation?.let { mediaInRowInLandscapeViewModelFactory.create(it) }
+
+    private val columnsWithoutMedia by
+        hydrator.hydratedStateOf(traceName = "columnsWithoutMedia", source = interactor.columns)
 
     override suspend fun onActivated(): Nothing {
-        hydrator.activate()
+        coroutineScope {
+            launch { hydrator.activate() }
+            launch { mediaInRowInLandscapeViewModel?.activate() }
+            awaitCancellation()
+        }
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(mediaLocation: Int?): QSColumnsViewModel
+
+        fun createWithoutMediaTracking() = create(null)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
index 0859c86..33ce551 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.haptics.msdl.qs.TileHapticsViewModelFactoryProvider
 import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.lifecycle.Hydrator
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QQS
 import com.android.systemui.qs.panels.domain.interactor.QuickQuickSettingsRowInteractor
 import com.android.systemui.qs.panels.shared.model.SizedTileImpl
 import com.android.systemui.qs.panels.shared.model.splitInRowsSequence
@@ -36,23 +37,35 @@
 @AssistedInject
 constructor(
     tilesInteractor: CurrentTilesInteractor,
-    private val qsColumnsViewModel: QSColumnsViewModel,
+    qsColumnsViewModelFactory: QSColumnsViewModel.Factory,
     quickQuickSettingsRowInteractor: QuickQuickSettingsRowInteractor,
+    mediaInRowInLandscapeViewModelFactory: MediaInRowInLandscapeViewModel.Factory,
     val squishinessViewModel: TileSquishinessViewModel,
     iconTilesViewModel: IconTilesViewModel,
     val tileHapticsViewModelFactoryProvider: TileHapticsViewModelFactoryProvider,
 ) : ExclusiveActivatable() {
 
     private val hydrator = Hydrator("QuickQuickSettingsViewModel")
+    private val qsColumnsViewModel = qsColumnsViewModelFactory.create(LOCATION_QQS)
+    private val mediaInRowViewModel = mediaInRowInLandscapeViewModelFactory.create(LOCATION_QQS)
 
-    val columns by qsColumnsViewModel.columns
+    val columns: Int
+        get() = qsColumnsViewModel.columns
 
     private val largeTiles by
         hydrator.hydratedStateOf(traceName = "largeTiles", source = iconTilesViewModel.largeTiles)
 
-    private val rows by
+    private val rows: Int
+        get() =
+            if (mediaInRowViewModel.shouldMediaShowInRow) {
+                rowsWithoutMedia * 2
+            } else {
+                rowsWithoutMedia
+            }
+
+    private val rowsWithoutMedia by
         hydrator.hydratedStateOf(
-            traceName = "rows",
+            traceName = "rowsWithoutMedia",
             initialValue = quickQuickSettingsRowInteractor.defaultRows,
             source = quickQuickSettingsRowInteractor.rows,
         )
@@ -73,6 +86,7 @@
         coroutineScope {
             launch { hydrator.activate() }
             launch { qsColumnsViewModel.activate() }
+            launch { mediaInRowViewModel.activate() }
             awaitCancellation()
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt
index f702da4..c9a0635 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt
@@ -54,6 +54,7 @@
         subtitleIdsMap["dream"] = R.array.tile_states_dream
         subtitleIdsMap["font_scaling"] = R.array.tile_states_font_scaling
         subtitleIdsMap["hearing_devices"] = R.array.tile_states_hearing_devices
+        subtitleIdsMap["notes"] = R.array.tile_states_notes
     }
 
     /** Get the subtitle resource id of the given tile */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NotesTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/NotesTile.kt
new file mode 100644
index 0000000..69df096
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NotesTile.kt
@@ -0,0 +1,111 @@
+/*
+ * 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.qs.tiles
+
+import android.content.Intent
+import android.os.Handler
+import android.os.Looper
+import android.service.quicksettings.Tile
+import com.android.internal.logging.MetricsLogger
+import com.android.systemui.animation.Expandable
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tiles.impl.notes.domain.NotesTileMapper
+import com.android.systemui.qs.tiles.impl.notes.domain.interactor.NotesTileDataInteractor
+import com.android.systemui.qs.tiles.impl.notes.domain.interactor.NotesTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.notes.domain.model.NotesTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import javax.inject.Inject
+import kotlinx.coroutines.runBlocking
+
+/** Quick settings tile: Notes */
+class NotesTile
+@Inject constructor(
+    private val host: QSHost,
+    private val uiEventLogger: QsEventLogger,
+    @Background private val  backgroundLooper: Looper,
+    @Main private val mainHandler: Handler,
+    private val falsingManager: FalsingManager,
+    private val metricsLogger: MetricsLogger,
+    private val statusBarStateController: StatusBarStateController,
+    private val activityStarter: ActivityStarter,
+    private val qsLogger: QSLogger,
+    private val qsTileConfigProvider: QSTileConfigProvider,
+    private val dataInteractor: NotesTileDataInteractor,
+    private val tileMapper: NotesTileMapper,
+    private val userActionInteractor: NotesTileUserActionInteractor,
+) :
+    QSTileImpl<QSTile.State?>(
+        host,
+        uiEventLogger,
+        backgroundLooper,
+        mainHandler,
+        falsingManager,
+        metricsLogger,
+        statusBarStateController,
+        activityStarter,
+        qsLogger,
+    ) {
+
+    private lateinit var tileState: QSTileState
+    private val config = qsTileConfigProvider.getConfig(TILE_SPEC)
+
+    override fun getTileLabel(): CharSequence =
+        mContext.getString(config.uiConfig.labelRes)
+
+    override fun newTileState(): QSTile.State? {
+        return QSTile.State().apply { state = Tile.STATE_INACTIVE }
+    }
+
+    override fun handleClick(expandable: Expandable?) {
+        userActionInteractor.handleClick()
+    }
+
+    override fun getLongClickIntent(): Intent = userActionInteractor.longClickIntent
+
+    override fun handleUpdateState(state: QSTile.State?, arg: Any?) {
+        val model =
+            if (arg is NotesTileModel) arg else dataInteractor.getCurrentTileModel()
+        tileState = tileMapper.map(config, model)
+
+        state?.apply {
+            this.state = tileState.activationState.legacyState
+            icon = ResourceIcon.get(tileState.iconRes ?: R.drawable.ic_qs_notes)
+            label = tileState.label
+            contentDescription = tileState.contentDescription
+            expandedAccessibilityClassName = tileState.expandedAccessibilityClassName
+        }
+    }
+
+    override fun isAvailable(): Boolean {
+        return dataInteractor.isAvailable()
+    }
+
+    companion object {
+        const val TILE_SPEC = "notes"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
index 284239a..f3be340 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -191,8 +191,7 @@
             mPanelInteractor.collapsePanels();
         };
 
-        final Dialog dialog = mController.createScreenRecordDialog(mContext, mFlags,
-                mDialogTransitionAnimator, mActivityStarter, onStartRecordingClicked);
+        final Dialog dialog = mController.createScreenRecordDialog(onStartRecordingClicked);
 
         ActivityStarter.OnDismissAction dismissAction = () -> {
             if (shouldAnimateFromExpandable) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapper.kt
new file mode 100644
index 0000000..ee1b9e5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapper.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.qs.tiles.impl.notes.domain
+
+import android.content.res.Resources
+import android.widget.Button
+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.notes.domain.model.NotesTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+class NotesTileMapper
+@Inject
+constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
+    QSTileDataToStateMapper<NotesTileModel> {
+    override fun map(config: QSTileConfig, data: NotesTileModel): QSTileState =
+        QSTileState.build(resources, theme, config.uiConfig) {
+            iconRes = R.drawable.ic_qs_notes
+            icon =
+                Icon.Loaded(
+                    resources.getDrawable(
+                        iconRes!!,
+                        theme),
+                    contentDescription = null
+                )
+            contentDescription = label
+            activationState = QSTileState.ActivationState.INACTIVE
+            sideViewIcon = QSTileState.SideViewIcon.Chevron
+            supportedActions =
+                setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+            expandedAccessibilityClass = Button::class
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/interactor/NotesTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/interactor/NotesTileDataInteractor.kt
new file mode 100644
index 0000000..a501b85
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/interactor/NotesTileDataInteractor.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.qs.tiles.impl.notes.domain.interactor
+
+import android.os.UserHandle
+import com.android.systemui.Flags
+import com.android.systemui.notetask.NoteTaskEnabledKey
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.notes.domain.model.NotesTileModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+class NotesTileDataInteractor
+@Inject
+constructor(@NoteTaskEnabledKey private val isNoteTaskEnabled: Boolean) :
+    QSTileDataInteractor<NotesTileModel> {
+    override fun tileData(
+        user: UserHandle,
+        triggers: Flow<DataUpdateTrigger>,
+    ): Flow<NotesTileModel> = flowOf(NotesTileModel)
+
+    override fun availability(user: UserHandle): Flow<Boolean> = flowOf(isAvailable())
+
+    fun isAvailable(): Boolean {
+        return Flags.notesRoleQsTile() && isNoteTaskEnabled
+    }
+
+    fun getCurrentTileModel(): NotesTileModel {
+        return NotesTileModel
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/interactor/NotesTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/interactor/NotesTileUserActionInteractor.kt
new file mode 100644
index 0000000..df01d99
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/interactor/NotesTileUserActionInteractor.kt
@@ -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.systemui.qs.tiles.impl.notes.domain.interactor
+
+import com.android.systemui.animation.Expandable
+import com.android.systemui.notetask.NoteTaskController
+import com.android.systemui.notetask.NoteTaskEntryPoint
+import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.notes.domain.model.NotesTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import javax.inject.Inject
+
+class NotesTileUserActionInteractor
+@Inject constructor(
+    private val qsTileIntentUserInputHandler: QSTileIntentUserInputHandler,
+    private val panelInteractor: PanelInteractor,
+    private val noteTaskController: NoteTaskController,
+) : QSTileUserActionInteractor<NotesTileModel> {
+    val longClickIntent = NoteTaskController.createNotesRoleHolderSettingsIntent()
+
+    override suspend fun handleInput(input: QSTileInput<NotesTileModel>) {
+        when (input.action) {
+            is QSTileUserAction.Click -> handleClick()
+            is QSTileUserAction.LongClick -> handleLongClick(input.action.expandable)
+            is QSTileUserAction.ToggleClick -> {}
+        }
+    }
+
+    fun handleClick() {
+        noteTaskController.showNoteTask(NoteTaskEntryPoint.QS_NOTES_TILE)
+        panelInteractor.collapsePanels()
+    }
+
+    fun handleLongClick(expandable: Expandable?) {
+        qsTileIntentUserInputHandler.handle(expandable, longClickIntent)
+    }
+}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/model/NotesTileModel.kt
similarity index 82%
copy from libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
copy to packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/model/NotesTileModel.kt
index e21bf8f..b72aa60 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/model/NotesTileModel.kt
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.shared;
+package com.android.systemui.qs.tiles.impl.notes.domain.model
 
-parcelable GroupedRecentTaskInfo;
\ No newline at end of file
+/** NotesTileModel tile model. */
+data object NotesTileModel
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractor.kt
index 48b39ed..85aa674 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractor.kt
@@ -16,16 +16,13 @@
 
 package com.android.systemui.qs.tiles.impl.screenrecord.domain.interactor
 
-import android.content.Context
 import android.util.Log
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.systemui.animation.DialogCuj
 import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.animation.Expandable
-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.flags.FeatureFlagsClassic
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
 import com.android.systemui.plugins.ActivityStarter
@@ -45,7 +42,6 @@
 class ScreenRecordTileUserActionInteractor
 @Inject
 constructor(
-    @Application private val context: Context,
     @Main private val mainContext: CoroutineContext,
     @Background private val backgroundContext: CoroutineContext,
     private val screenRecordRepository: ScreenRecordRepository,
@@ -55,8 +51,6 @@
     private val dialogTransitionAnimator: DialogTransitionAnimator,
     private val panelInteractor: PanelInteractor,
     private val mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
-    private val featureFlags: FeatureFlagsClassic,
-    private val activityStarter: ActivityStarter,
 ) : QSTileUserActionInteractor<ScreenRecordModel> {
     override suspend fun handleInput(input: QSTileInput<ScreenRecordModel>): Unit =
         with(input) {
@@ -89,14 +83,7 @@
             panelInteractor.collapsePanels()
         }
 
-        val dialog =
-            recordingController.createScreenRecordDialog(
-                context,
-                featureFlags,
-                dialogTransitionAnimator,
-                activityStarter,
-                onStartRecordingClicked
-            )
+        val dialog = recordingController.createScreenRecordDialog(onStartRecordingClicked)
 
         if (dialog == null) {
             Log.w(TAG, "showPrompt: dialog was null")
@@ -115,7 +102,7 @@
                         expandable?.dialogTransitionController(
                             DialogCuj(
                                 InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
-                                INTERACTION_JANK_TAG
+                                INTERACTION_JANK_TAG,
                             )
                         )
                     controller?.let {
@@ -135,7 +122,7 @@
         keyguardDismissUtil.executeWhenUnlocked(
             dismissAction,
             false /* requiresShadeOpen */,
-            true /* afterKeyguardDone */
+            true, /* afterKeyguardDone */
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt
index 9a416d1..000f7f8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt
@@ -18,7 +18,6 @@
 
 import com.android.compose.animation.scene.Back
 import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
 import com.android.compose.animation.scene.UserActionResult.HideOverlay
@@ -47,7 +46,7 @@
                         put(Back, HideOverlay(Overlays.QuickSettingsShade))
                     }
                     put(
-                        Swipe(SwipeDirection.Down, fromSource = SceneContainerEdge.TopLeft),
+                        Swipe.Down(fromSource = SceneContainerEdge.TopLeft),
                         ReplaceByOverlay(Overlays.NotificationsShade),
                     )
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsUserActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsUserActionsViewModel.kt
index 54e5cac..f595415 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsUserActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsUserActionsViewModel.kt
@@ -20,7 +20,6 @@
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.qs.ui.adapter.QSSceneAdapter
@@ -44,10 +43,8 @@
  */
 class QuickSettingsUserActionsViewModel
 @AssistedInject
-constructor(
-    private val qsSceneAdapter: QSSceneAdapter,
-    sceneBackInteractor: SceneBackInteractor,
-) : UserActionsViewModel() {
+constructor(private val qsSceneAdapter: QSSceneAdapter, sceneBackInteractor: SceneBackInteractor) :
+    UserActionsViewModel() {
 
     private val backScene: Flow<SceneKey> =
         sceneBackInteractor.backScene
@@ -55,10 +52,7 @@
             .map { it ?: Scenes.Shade }
 
     override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
-        combine(
-                qsSceneAdapter.isCustomizerShowing,
-                backScene,
-            ) { isCustomizing, backScene ->
+        combine(qsSceneAdapter.isCustomizerShowing, backScene) { isCustomizing, backScene ->
                 buildMap<UserAction, UserActionResult> {
                     if (isCustomizing) {
                         // TODO(b/332749288) Empty map so there are no back handlers and back can
@@ -69,9 +63,9 @@
                         // while customizing
                     } else {
                         put(Back, UserActionResult(backScene))
-                        put(Swipe(SwipeDirection.Up), UserActionResult(backScene))
+                        put(Swipe.Up, UserActionResult(backScene))
                         put(
-                            Swipe(fromSource = Edge.Bottom, direction = SwipeDirection.Up),
+                            Swipe.Up(fromSource = Edge.Bottom),
                             UserActionResult(SceneFamilies.Home),
                         )
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
index 6758c3b..02b2bb1 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
@@ -35,7 +35,6 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES
 import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
 import com.android.systemui.mediaprojection.SessionCreationSource
 import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver
@@ -132,10 +131,9 @@
     @WorkerThread
     private fun onScreenRecordSwitchClicked() {
         if (
-            flags.isEnabled(WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES) &&
-                devicePolicyResolver
-                    .get()
-                    .isScreenCaptureCompletelyDisabled(UserHandle.of(userTracker.userId))
+            devicePolicyResolver
+                .get()
+                .isScreenCaptureCompletelyDisabled(UserHandle.of(userTracker.userId))
         ) {
             mainExecutor.execute {
                 screenCaptureDisabledDialogDelegate.createSysUIDialog().show()
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 1fbe8e2..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,
@@ -270,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/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index a8a78a9..d7463f8 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -32,17 +32,13 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.animation.DialogTransitionAnimator;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger;
 import com.android.systemui.mediaprojection.SessionCreationSource;
 import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
 import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialogDelegate;
-import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.CallbackController;
 
@@ -66,12 +62,10 @@
     private CountDownTimer mCountDownTimer = null;
     private final Executor mMainExecutor;
     private final BroadcastDispatcher mBroadcastDispatcher;
-    private final FeatureFlags mFlags;
     private final UserTracker mUserTracker;
     private final RecordingControllerLogger mRecordingControllerLogger;
     private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
     private final ScreenCaptureDisabledDialogDelegate mScreenCaptureDisabledDialogDelegate;
-    private final ScreenRecordDialogDelegate.Factory mScreenRecordDialogFactory;
     private final ScreenRecordPermissionDialogDelegate.Factory
             mScreenRecordPermissionDialogDelegateFactory;
 
@@ -116,24 +110,20 @@
     public RecordingController(
             @Main Executor mainExecutor,
             BroadcastDispatcher broadcastDispatcher,
-            FeatureFlags flags,
             Lazy<ScreenCaptureDevicePolicyResolver> devicePolicyResolver,
             UserTracker userTracker,
             RecordingControllerLogger recordingControllerLogger,
             MediaProjectionMetricsLogger mediaProjectionMetricsLogger,
             ScreenCaptureDisabledDialogDelegate screenCaptureDisabledDialogDelegate,
-            ScreenRecordDialogDelegate.Factory screenRecordDialogFactory,
             ScreenRecordPermissionDialogDelegate.Factory
                     screenRecordPermissionDialogDelegateFactory) {
         mMainExecutor = mainExecutor;
-        mFlags = flags;
         mDevicePolicyResolver = devicePolicyResolver;
         mBroadcastDispatcher = broadcastDispatcher;
         mUserTracker = userTracker;
         mRecordingControllerLogger = recordingControllerLogger;
         mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger;
         mScreenCaptureDisabledDialogDelegate = screenCaptureDisabledDialogDelegate;
-        mScreenRecordDialogFactory = screenRecordDialogFactory;
         mScreenRecordPermissionDialogDelegateFactory = screenRecordPermissionDialogDelegateFactory;
 
         BroadcastOptions options = BroadcastOptions.makeBasic();
@@ -158,12 +148,8 @@
     /** Create a dialog to show screen recording options to the user.
      *  If screen capturing is currently not allowed it will return a dialog
      *  that warns users about it. */
-    public Dialog createScreenRecordDialog(Context context, FeatureFlags flags,
-                                           DialogTransitionAnimator dialogTransitionAnimator,
-                                           ActivityStarter activityStarter,
-                                           @Nullable Runnable onStartRecordingClicked) {
-        if (mFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES)
-                && mDevicePolicyResolver.get()
+    public Dialog createScreenRecordDialog(@Nullable Runnable onStartRecordingClicked) {
+        if (mDevicePolicyResolver.get()
                         .isScreenCaptureCompletelyDisabled(getHostUserHandle())) {
             return mScreenCaptureDisabledDialogDelegate.createSysUIDialog();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialogDelegate.java
deleted file mode 100644
index 9f1447b..0000000
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialogDelegate.java
+++ /dev/null
@@ -1,192 +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.screenrecord;
-
-import static android.app.Activity.RESULT_OK;
-
-import static com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity.KEY_CAPTURE_TARGET;
-import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.INTERNAL;
-import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.MIC;
-import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.MIC_AND_INTERNAL;
-import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.NONE;
-
-import android.app.Activity;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.ResultReceiver;
-import android.view.Gravity;
-import android.view.View;
-import android.view.Window;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.widget.ArrayAdapter;
-import android.widget.Spinner;
-import android.widget.Switch;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.systemui.mediaprojection.MediaProjectionCaptureTarget;
-import com.android.systemui.res.R;
-import com.android.systemui.settings.UserContextProvider;
-import com.android.systemui.statusbar.phone.SystemUIDialog;
-
-import dagger.assisted.Assisted;
-import dagger.assisted.AssistedFactory;
-import dagger.assisted.AssistedInject;
-
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Dialog to select screen recording options
- */
-public class ScreenRecordDialogDelegate implements SystemUIDialog.Delegate {
-    private static final List<ScreenRecordingAudioSource> MODES = Arrays.asList(INTERNAL, MIC,
-            MIC_AND_INTERNAL);
-    private static final long DELAY_MS = 3000;
-    private static final long INTERVAL_MS = 1000;
-
-    private final SystemUIDialog.Factory mSystemUIDialogFactory;
-    private final UserContextProvider mUserContextProvider;
-    private final RecordingController mController;
-    private final Runnable mOnStartRecordingClicked;
-    private Switch mTapsSwitch;
-    private Switch mAudioSwitch;
-    private Spinner mOptions;
-
-    @AssistedFactory
-    public interface Factory {
-        ScreenRecordDialogDelegate create(
-                RecordingController recordingController,
-                @Nullable Runnable onStartRecordingClicked
-        );
-    }
-
-    @AssistedInject
-    public ScreenRecordDialogDelegate(
-            SystemUIDialog.Factory systemUIDialogFactory,
-            UserContextProvider userContextProvider,
-            @Assisted RecordingController controller,
-            @Assisted @Nullable Runnable onStartRecordingClicked) {
-        mSystemUIDialogFactory = systemUIDialogFactory;
-        mUserContextProvider = userContextProvider;
-        mController = controller;
-        mOnStartRecordingClicked = onStartRecordingClicked;
-    }
-
-    @Override
-    public SystemUIDialog createDialog() {
-        return mSystemUIDialogFactory.create(this);
-    }
-
-    @Override
-    public void onCreate(SystemUIDialog dialog, Bundle savedInstanceState) {
-        Window window = dialog.getWindow();
-
-        window.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS);
-
-        window.setGravity(Gravity.CENTER);
-        dialog.setTitle(R.string.screenrecord_title);
-
-        dialog.setContentView(R.layout.screen_record_dialog);
-
-        TextView cancelBtn = dialog.findViewById(R.id.button_cancel);
-        cancelBtn.setOnClickListener(v -> dialog.dismiss());
-        TextView startBtn = dialog.findViewById(R.id.button_start);
-        startBtn.setOnClickListener(v -> {
-            if (mOnStartRecordingClicked != null) {
-                // Note that it is important to run this callback before dismissing, so that the
-                // callback can disable the dialog exit animation if it wants to.
-                mOnStartRecordingClicked.run();
-            }
-
-            // Start full-screen recording
-            requestScreenCapture(/* captureTarget= */ null);
-            dialog.dismiss();
-        });
-
-        mAudioSwitch = dialog.findViewById(R.id.screenrecord_audio_switch);
-        mTapsSwitch = dialog.findViewById(R.id.screenrecord_taps_switch);
-        mOptions = dialog.findViewById(R.id.screen_recording_options);
-        ArrayAdapter a = new ScreenRecordingAdapter(dialog.getContext().getApplicationContext(),
-                android.R.layout.simple_spinner_dropdown_item,
-                MODES);
-        a.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
-        mOptions.setAdapter(a);
-        mOptions.setOnItemClickListenerInt((parent, view, position, id) -> {
-            mAudioSwitch.setChecked(true);
-        });
-
-        // disable redundant Touch & Hold accessibility action for Switch Access
-        mOptions.setAccessibilityDelegate(new View.AccessibilityDelegate() {
-            @Override
-            public void onInitializeAccessibilityNodeInfo(@NonNull View host,
-                    @NonNull AccessibilityNodeInfo info) {
-                info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK);
-                super.onInitializeAccessibilityNodeInfo(host, info);
-            }
-        });
-        mOptions.setLongClickable(false);
-    }
-
-    /**
-     * Starts screen capture after some countdown
-     * @param captureTarget target to capture (could be e.g. a task) or
-     *                      null to record the whole screen
-     */
-    private void requestScreenCapture(@Nullable MediaProjectionCaptureTarget captureTarget) {
-        Context userContext = mUserContextProvider.getUserContext();
-        boolean showTaps = mTapsSwitch.isChecked();
-        ScreenRecordingAudioSource audioMode = mAudioSwitch.isChecked()
-                ? (ScreenRecordingAudioSource) mOptions.getSelectedItem()
-                : NONE;
-        PendingIntent startIntent = PendingIntent.getForegroundService(userContext,
-                RecordingService.REQUEST_CODE,
-                RecordingService.getStartIntent(
-                        userContext, Activity.RESULT_OK,
-                        audioMode.ordinal(), showTaps, captureTarget),
-                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
-        PendingIntent stopIntent = PendingIntent.getService(userContext,
-                RecordingService.REQUEST_CODE,
-                RecordingService.getStopIntent(userContext),
-                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
-        mController.startCountdown(DELAY_MS, INTERVAL_MS, startIntent, stopIntent);
-    }
-
-    private class CaptureTargetResultReceiver extends ResultReceiver {
-
-        CaptureTargetResultReceiver() {
-            super(new Handler(Looper.getMainLooper()));
-        }
-
-        @Override
-        protected void onReceiveResult(int resultCode, Bundle resultData) {
-            if (resultCode == RESULT_OK) {
-                MediaProjectionCaptureTarget captureTarget = resultData
-                        .getParcelable(KEY_CAPTURE_TARGET, MediaProjectionCaptureTarget.class);
-
-                // Start recording of the selected target
-                requestScreenCapture(captureTarget);
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt
index e5f6846..b0777c9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt
@@ -18,7 +18,6 @@
 
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.scene.shared.model.Overlays
@@ -35,7 +34,7 @@
     return arrayOf(
         // Swiping down, not from the edge, always goes to shade.
         Swipe.Down to shadeUserActionResult,
-        swipeDown(pointerCount = 2) to shadeUserActionResult,
+        Swipe.Down(pointerCount = 2) to shadeUserActionResult,
 
         // Swiping down from the top edge.
         swipeDownFromTop(pointerCount = 1) to
@@ -54,7 +53,7 @@
     return arrayOf(
         // Swiping down, not from the edge, always goes to shade.
         Swipe.Down to shadeUserActionResult,
-        swipeDown(pointerCount = 2) to shadeUserActionResult,
+        Swipe.Down(pointerCount = 2) to shadeUserActionResult,
         // Swiping down from the top edge goes to QS.
         swipeDownFromTop(pointerCount = 1) to shadeUserActionResult,
         swipeDownFromTop(pointerCount = 2) to shadeUserActionResult,
@@ -69,15 +68,10 @@
         UserActionResult.ShowOverlay(Overlays.QuickSettingsShade, isIrreversible = true)
     return arrayOf(
         Swipe.Down to notifShadeUserActionResult,
-        Swipe(direction = SwipeDirection.Down, fromSource = SceneContainerEdge.TopRight) to
-            qsShadeuserActionResult,
+        Swipe.Down(fromSource = SceneContainerEdge.TopRight) to qsShadeuserActionResult,
     )
 }
 
 private fun swipeDownFromTop(pointerCount: Int): Swipe {
-    return Swipe(SwipeDirection.Down, fromSource = Edge.Top, pointerCount = pointerCount)
-}
-
-private fun swipeDown(pointerCount: Int): Swipe {
-    return Swipe(SwipeDirection.Down, pointerCount = pointerCount)
+    return Swipe.Down(fromSource = Edge.Top, pointerCount = pointerCount)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModel.kt
index 4bdd367..7d6b1a3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModel.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.shade.ui.viewmodel
 
 import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.qs.ui.adapter.QSSceneAdapter
@@ -58,7 +57,7 @@
                 buildMap<UserAction, UserActionResult> {
                     if (!isCustomizerShowing) {
                         set(
-                            Swipe(SwipeDirection.Up),
+                            Swipe.Up,
                             UserActionResult(
                                 backScene,
                                 ToSplitShade.takeIf { shadeMode is ShadeMode.Split },
@@ -69,7 +68,7 @@
                     // TODO(b/330200163) Add an else to be able to collapse the shade while
                     // customizing
                     if (shadeMode is ShadeMode.Single) {
-                        set(Swipe(SwipeDirection.Down), UserActionResult(Scenes.QuickSettings))
+                        set(Swipe.Down, UserActionResult(Scenes.QuickSettings))
                     }
                 }
             }
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/OperatorNameViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameViewController.java
index 0d789c7..f5d4434 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameViewController.java
@@ -119,7 +119,6 @@
 
     /** Factory for constructing an {@link OperatorNameViewController}. */
     public static class Factory {
-        private final DarkIconDispatcher mDarkIconDispatcher;
         private final TunerService mTunerService;
         private final TelephonyManager mTelephonyManager;
         private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@@ -129,7 +128,7 @@
         private final JavaAdapter mJavaAdapter;
 
         @Inject
-        public Factory(DarkIconDispatcher darkIconDispatcher,
+        public Factory(
                 TunerService tunerService,
                 TelephonyManager telephonyManager,
                 KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -137,7 +136,6 @@
                 AirplaneModeInteractor airplaneModeInteractor,
                 SubscriptionManagerProxy subscriptionManagerProxy,
                 JavaAdapter javaAdapter) {
-            mDarkIconDispatcher = darkIconDispatcher;
             mTunerService = tunerService;
             mTelephonyManager = telephonyManager;
             mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -148,9 +146,11 @@
         }
 
         /** Create an {@link OperatorNameViewController}. */
-        public OperatorNameViewController create(OperatorNameView view) {
-            return new OperatorNameViewController(view,
-                    mDarkIconDispatcher,
+        public OperatorNameViewController create(
+                OperatorNameView view, DarkIconDispatcher darkIconDispatcher) {
+            return new OperatorNameViewController(
+                    view,
+                    darkIconDispatcher,
                     mTunerService,
                     mTelephonyManager,
                     mKeyguardUpdateMonitor,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
index c8d3f33..7526748 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
@@ -68,13 +68,8 @@
                     notifChipsInteractor.onPromotedNotificationChipTapped(this@toChipModel.key)
                 }
             }
-        return OngoingActivityChipModel.Shown.ShortTimeDelta(
-            icon,
-            colors,
-            time = this.whenTime,
-            onClickListener,
-        )
-        // TODO(b/364653005): If Notification.showWhen = false, don't show the time delta.
+        return OngoingActivityChipModel.Shown.IconOnly(icon, colors, onClickListener)
+        // TODO(b/364653005): Use Notification.showWhen to determine if we should show the time.
         // TODO(b/364653005): If Notification.whenTime is in the past, show "ago" in the text.
         // TODO(b/364653005): If Notification.shortCriticalText is set, use that instead of `when`.
         // TODO(b/364653005): If the app that posted the notification is in the foreground, don't
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt
index 9b3513e..84c7ab2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.display.data.repository.DisplayRepository
 import com.android.systemui.display.data.repository.DisplayScopeRepository
+import com.android.systemui.statusbar.data.repository.LightBarControllerStore
 import com.android.systemui.statusbar.data.repository.PrivacyDotWindowControllerStore
 import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
 import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
@@ -50,6 +51,7 @@
     private val statusBarWindowControllerStore: StatusBarWindowControllerStore,
     private val statusBarInitializerStore: StatusBarInitializerStore,
     private val privacyDotWindowControllerStore: PrivacyDotWindowControllerStore,
+    private val lightBarControllerStore: LightBarControllerStore,
 ) : CoreStartable {
 
     init {
@@ -74,6 +76,14 @@
         createAndStartOrchestratorForDisplay(displayId)
         createAndStartInitializerForDisplay(displayId)
         startPrivacyDotForDisplay(displayId)
+        createLightBarControllerForDisplay(displayId)
+    }
+
+    private fun createLightBarControllerForDisplay(displayId: Int) {
+        // Explicitly not calling `start()`, because the store is already calling `start()`.
+        // This is to maintain the legacy behavior with NavigationBar, that was already expecting
+        // LightBarController to start at construction time.
+        lightBarControllerStore.forDisplay(displayId)
     }
 
     private fun createAndStartOrchestratorForDisplay(displayId: Int) {
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 39de28e..27d8151 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.statusbar.data
 
+import com.android.systemui.statusbar.data.repository.DarkIconDispatcherStoreModule
 import com.android.systemui.statusbar.data.repository.KeyguardStatusBarRepositoryModule
 import com.android.systemui.statusbar.data.repository.LightBarControllerStoreModule
 import com.android.systemui.statusbar.data.repository.RemoteInputRepositoryModule
@@ -28,6 +29,7 @@
 @Module(
     includes =
         [
+            DarkIconDispatcherStoreModule::class,
             KeyguardStatusBarRepositoryModule::class,
             LightBarControllerStoreModule::class,
             RemoteInputRepositoryModule::class,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/DarkIconDispatcherStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/DarkIconDispatcherStore.kt
new file mode 100644
index 0000000..8183a48
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/DarkIconDispatcherStore.kt
@@ -0,0 +1,142 @@
+/*
+ * 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.content.Context
+import android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR
+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.DisplayWindowPropertiesRepository
+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.plugins.DarkIconDispatcher
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.statusbar.phone.DarkIconDispatcherImpl
+import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher
+import dagger.Binds
+import dagger.Lazy
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+
+/** Provides per display instances of [DarkIconDispatcher]. */
+interface DarkIconDispatcherStore : PerDisplayStore<DarkIconDispatcher>
+
+/** Provides per display instances of [SysuiDarkIconDispatcher]. */
+interface SysuiDarkIconDispatcherStore : PerDisplayStore<SysuiDarkIconDispatcher>
+
+/**
+ * Multi display implementation that should be used when the [StatusBarConnectedDisplays] flag is
+ * enabled.
+ */
+@SysUISingleton
+class MultiDisplayDarkIconDispatcherStore
+@Inject
+constructor(
+    @Background backgroundApplicationScope: CoroutineScope,
+    displayRepository: DisplayRepository,
+    private val factory: DarkIconDispatcherImpl.Factory,
+    private val displayWindowPropertiesRepository: DisplayWindowPropertiesRepository,
+) :
+    SysuiDarkIconDispatcherStore,
+    PerDisplayStoreImpl<SysuiDarkIconDispatcher>(backgroundApplicationScope, displayRepository) {
+
+    init {
+        StatusBarConnectedDisplays.assertInNewMode()
+    }
+
+    override fun createInstanceForDisplay(displayId: Int): SysuiDarkIconDispatcher {
+        val properties = displayWindowPropertiesRepository.get(displayId, TYPE_STATUS_BAR)
+        return factory.create(displayId, properties.context)
+    }
+
+    override suspend fun onDisplayRemovalAction(instance: SysuiDarkIconDispatcher) {
+        instance.stop()
+    }
+
+    override val instanceClass = SysuiDarkIconDispatcher::class.java
+}
+
+/**
+ * Single display implementation that should be used when the [StatusBarConnectedDisplays] flag is
+ * disabled.
+ */
+@SysUISingleton
+class SingleDisplayDarkIconDispatcherStore
+@Inject
+constructor(factory: DarkIconDispatcherImpl.Factory, context: Context) :
+    SysuiDarkIconDispatcherStore,
+    PerDisplayStore<SysuiDarkIconDispatcher> by SingleDisplayStore(
+        defaultInstance = factory.create(context.displayId, context)
+    ) {
+
+    init {
+        StatusBarConnectedDisplays.assertInLegacyMode()
+    }
+}
+
+/** Extra implementation that simply implements the [DarkIconDispatcherStore] interface. */
+@SysUISingleton
+class DarkIconDispatcherStoreImpl
+@Inject
+constructor(private val store: SysuiDarkIconDispatcherStore) : DarkIconDispatcherStore {
+    override val defaultDisplay: DarkIconDispatcher
+        get() = store.defaultDisplay
+
+    override fun forDisplay(displayId: Int): DarkIconDispatcher = store.forDisplay(displayId)
+}
+
+@Module
+interface DarkIconDispatcherStoreModule {
+
+    @Binds fun store(impl: DarkIconDispatcherStoreImpl): DarkIconDispatcherStore
+
+    companion object {
+        @Provides
+        @SysUISingleton
+        fun sysUiStore(
+            singleDisplayLazy: Lazy<SingleDisplayDarkIconDispatcherStore>,
+            multiDisplayLazy: Lazy<MultiDisplayDarkIconDispatcherStore>,
+        ): SysuiDarkIconDispatcherStore {
+            return if (StatusBarConnectedDisplays.isEnabled) {
+                multiDisplayLazy.get()
+            } else {
+                singleDisplayLazy.get()
+            }
+        }
+
+        @Provides
+        @SysUISingleton
+        @IntoMap
+        @ClassKey(DarkIconDispatcherStore::class)
+        fun storeAsCoreStartable(
+            multiDisplayLazy: Lazy<MultiDisplayDarkIconDispatcherStore>
+        ): CoreStartable {
+            return if (StatusBarConnectedDisplays.isEnabled) {
+                multiDisplayLazy.get()
+            } else {
+                CoreStartable.NOP
+            }
+        }
+    }
+}
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
index ff50e31..e498755 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/LightBarControllerStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/LightBarControllerStore.kt
@@ -44,6 +44,7 @@
     private val factory: LightBarControllerImpl.Factory,
     private val displayScopeRepository: DisplayScopeRepository,
     private val statusBarModeRepositoryStore: StatusBarModeRepositoryStore,
+    private val darkIconDispatcherStore: DarkIconDispatcherStore,
 ) :
     LightBarControllerStore,
     PerDisplayStoreImpl<LightBarController>(backgroundApplicationScope, displayRepository) {
@@ -53,6 +54,7 @@
             .create(
                 displayId,
                 displayScopeRepository.scopeForDisplay(displayId),
+                darkIconDispatcherStore.forDisplay(displayId),
                 statusBarModeRepositoryStore.forDisplay(displayId),
             )
             .also { it.start() }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationContentDescription.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationContentDescription.kt
index bdd9fd0..6b6920a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationContentDescription.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationContentDescription.kt
@@ -28,14 +28,5 @@
 @MainThread
 fun contentDescForNotification(c: Context, n: Notification): CharSequence {
     val appName = n.loadHeaderAppName(c) ?: ""
-    val title = n.extras?.getCharSequence(Notification.EXTRA_TITLE)
-    val text = n.extras?.getCharSequence(Notification.EXTRA_TEXT)
-    val ticker = n.tickerText
-
-    // Some apps just put the app name into the title
-    val titleOrText = if (TextUtils.equals(title, appName)) text else title
-    val desc =
-        if (!TextUtils.isEmpty(titleOrText)) titleOrText
-        else if (!TextUtils.isEmpty(ticker)) ticker else ""
-    return c.getString(R.string.accessibility_desc_notification_icon, appName, desc)
+    return c.getString(R.string.accessibility_desc_notification_icon, appName, "")
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerAlwaysOnDisplayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerAlwaysOnDisplayViewBinder.kt
index 663588c..fc432ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerAlwaysOnDisplayViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerAlwaysOnDisplayViewBinder.kt
@@ -17,11 +17,13 @@
 package com.android.systemui.statusbar.notification.icon.ui.viewbinder
 
 import androidx.lifecycle.lifecycleScope
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.app.tracing.traceSection
 import com.android.systemui.common.ui.ConfigurationState
 import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.statusbar.notification.collection.NotifCollection
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore
 import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel
@@ -30,7 +32,6 @@
 import com.android.systemui.statusbar.ui.SystemBarUtilsState
 import javax.inject.Inject
 import kotlinx.coroutines.DisposableHandle
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /** Binds a [NotificationIconContainer] to a [NotificationIconContainerAlwaysOnDisplayViewModel]. */
 class NotificationIconContainerAlwaysOnDisplayViewBinder
@@ -38,7 +39,7 @@
 constructor(
     private val viewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
     private val keyguardRootViewModel: KeyguardRootViewModel,
-    private val configuration: ConfigurationState,
+    @ShadeDisplayAware private val configuration: ConfigurationState,
     private val failureTracker: StatusBarIconViewBindingFailureTracker,
     private val screenOffAnimationController: ScreenOffAnimationController,
     private val systemBarUtilsState: SystemBarUtilsState,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerShelfViewBinder.kt
index 4e40888..5432f14 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerShelfViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerShelfViewBinder.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.notification.icon.ui.viewbinder
 
 import com.android.systemui.common.ui.ConfigurationState
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.statusbar.notification.collection.NotifCollection
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.bindIcons
@@ -30,7 +31,7 @@
 @Inject
 constructor(
     private val viewModel: NotificationIconContainerShelfViewModel,
-    private val configuration: ConfigurationState,
+    @ShadeDisplayAware private val configuration: ConfigurationState,
     private val systemBarUtilsState: SystemBarUtilsState,
     private val failureTracker: StatusBarIconViewBindingFailureTracker,
     private val viewStore: ShelfNotificationIconViewStore,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt
index f0f529e..a21dabb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt
@@ -17,9 +17,11 @@
 package com.android.systemui.statusbar.notification.icon.ui.viewbinder
 
 import androidx.lifecycle.lifecycleScope
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.app.tracing.traceSection
 import com.android.systemui.common.ui.ConfigurationState
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.statusbar.notification.collection.NotifCollection
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore
 import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel
@@ -27,23 +29,23 @@
 import com.android.systemui.statusbar.ui.SystemBarUtilsState
 import javax.inject.Inject
 import kotlinx.coroutines.DisposableHandle
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /** Binds a [NotificationIconContainer] to a [NotificationIconContainerStatusBarViewModel]. */
 class NotificationIconContainerStatusBarViewBinder
 @Inject
 constructor(
     private val viewModel: NotificationIconContainerStatusBarViewModel,
-    private val configuration: ConfigurationState,
+    @ShadeDisplayAware private val configuration: ConfigurationState,
     private val systemBarUtilsState: SystemBarUtilsState,
     private val failureTracker: StatusBarIconViewBindingFailureTracker,
     private val viewStore: StatusBarNotificationIconViewStore,
 ) {
-    fun bindWhileAttached(view: NotificationIconContainer): DisposableHandle {
+    fun bindWhileAttached(view: NotificationIconContainer, displayId: Int): DisposableHandle {
         return traceSection("NICStatusBar#bindWhileAttached") {
             view.repeatWhenAttached {
                 lifecycleScope.launch {
                     NotificationIconContainerViewBinder.bind(
+                        displayId = displayId,
                         view = view,
                         viewModel = viewModel,
                         configuration = configuration,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
index 063fe45..6dbb714 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
@@ -24,6 +24,7 @@
 import androidx.annotation.ColorInt
 import androidx.collection.ArrayMap
 import androidx.lifecycle.lifecycleScope
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.app.tracing.traceSection
 import com.android.internal.R as RInternal
 import com.android.internal.statusbar.StatusBarIcon
@@ -54,12 +55,12 @@
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.mapNotNull
 import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /** Binds a view-model to a [NotificationIconContainer]. */
 object NotificationIconContainerViewBinder {
 
     suspend fun bind(
+        displayId: Int,
         view: NotificationIconContainer,
         viewModel: NotificationIconContainerStatusBarViewModel,
         configuration: ConfigurationState,
@@ -70,7 +71,10 @@
         launch {
             val contrastColorUtil = ContrastColorUtil.getInstance(view.context)
             val iconColors: StateFlow<NotificationIconColors> =
-                viewModel.iconColors.mapNotNull { it.iconColors(view.viewBounds) }.stateIn(this)
+                viewModel
+                    .iconColors(displayId)
+                    .mapNotNull { it.iconColors(view.viewBounds) }
+                    .stateIn(this)
             viewModel.icons.bindIcons(
                 logTag = "statusbar",
                 view = view,
@@ -79,11 +83,7 @@
                 notifyBindingFailures = { failureTracker.statusBarFailures = it },
                 viewStore = viewStore,
             ) { _, sbiv ->
-                StatusBarIconViewBinder.bindIconColors(
-                    sbiv,
-                    iconColors,
-                    contrastColorUtil,
-                )
+                StatusBarIconViewBinder.bindIconColors(sbiv, iconColors, contrastColorUtil)
             }
         }
         launch { viewModel.bindIsolatedIcon(view, viewStore) }
@@ -194,8 +194,7 @@
             combine(iconSizeFlow, iconHorizontalPaddingFlow, systemBarUtilsState.statusBarHeight) {
                     iconSize,
                     iconHPadding,
-                    statusBarHeight,
-                    ->
+                    statusBarHeight ->
                     FrameLayout.LayoutParams(iconSize + 2 * iconHPadding, statusBarHeight)
                 }
                 .stateIn(this)
@@ -251,10 +250,7 @@
                     traceSection("addIcon") {
                         (sbiv.parent as? ViewGroup)?.run {
                             if (this !== view) {
-                                Log.wtf(
-                                    TAG,
-                                    "[$logTag] SBIV($notifKey) has an unexpected parent",
-                                )
+                                Log.wtf(TAG, "[$logTag] SBIV($notifKey) has an unexpected parent")
                             }
                             // If the container was re-inflated and re-bound, then SBIVs might still
                             // be attached to the prior view.
@@ -271,7 +267,7 @@
                                 launch {
                                     launch {
                                         layoutParams.collectTracingEach(
-                                            tag = { "[$logTag] SBIV#bindLayoutParams" },
+                                            tag = { "[$logTag] SBIV#bindLayoutParams" }
                                         ) {
                                             if (it != sbiv.layoutParams) {
                                                 sbiv.layoutParams = it
@@ -344,7 +340,7 @@
     //  a single SBIV instance for the group. Then this whole concept can go away.
     private inline fun <R> NotificationIconContainer.withIconReplacements(
         replacements: ArrayMap<String, StatusBarIcon>,
-        block: () -> R
+        block: () -> R,
     ): R {
         setReplacingIcons(replacements)
         return block().also { setReplacingIcons(null) }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
index a64f888..f0b0306 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
@@ -45,8 +45,8 @@
 class NotificationIconContainerStatusBarViewModel
 @Inject
 constructor(
-    @Background bgContext: CoroutineContext,
-    darkIconInteractor: DarkIconInteractor,
+    @Background private val bgContext: CoroutineContext,
+    private val darkIconInteractor: DarkIconInteractor,
     iconsInteractor: StatusBarNotificationIconsInteractor,
     headsUpIconInteractor: HeadsUpNotificationIconInteractor,
     keyguardInteractor: KeyguardInteractor,
@@ -58,10 +58,9 @@
 
     /** Are changes to the icon container animated? */
     val animationsEnabled: Flow<Boolean> =
-        combine(
-                shadeInteractor.isShadeTouchable,
-                keyguardInteractor.isKeyguardShowing,
-            ) { panelTouchesEnabled, isKeyguardShowing ->
+        combine(shadeInteractor.isShadeTouchable, keyguardInteractor.isKeyguardShowing) {
+                panelTouchesEnabled,
+                isKeyguardShowing ->
                 panelTouchesEnabled && !isKeyguardShowing
             }
             .flowOn(bgContext)
@@ -69,8 +68,9 @@
             .distinctUntilChanged()
 
     /** The colors with which to display the notification icons. */
-    val iconColors: Flow<NotificationIconColorLookup> =
-        darkIconInteractor.darkState
+    fun iconColors(displayId: Int): Flow<NotificationIconColorLookup> =
+        darkIconInteractor
+            .darkState(displayId)
             .map { (areas: Collection<Rect>, tint: Int) ->
                 NotificationIconColorLookup { viewBounds: Rect ->
                     if (DarkIconDispatcher.isInAreas(areas, viewBounds)) {
@@ -125,10 +125,8 @@
     val isolatedIconLocation: Flow<Rect> =
         headsUpIconInteractor.isolatedIconLocation.filterNotNull().conflate().distinctUntilChanged()
 
-    private class IconColorsImpl(
-        override val tint: Int,
-        private val areas: Collection<Rect>,
-    ) : NotificationIconColors {
+    private class IconColorsImpl(override val tint: Int, private val areas: Collection<Rect>) :
+        NotificationIconColors {
         override fun staticDrawableColor(viewBounds: Rect): Int {
             return if (DarkIconDispatcher.isInAreas(areas, viewBounds)) {
                 tint
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/stack/domain/interactor/HideNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractor.kt
index 3a650aa..53d0c2e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractor.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.power.shared.model.ScreenPowerState.SCREEN_ON
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
 import com.android.systemui.util.animation.data.repository.AnimationStatusRepository
 import com.android.systemui.util.kotlin.WithPrev
@@ -46,9 +47,9 @@
 @Inject
 constructor(
     private val unfoldTransitionInteractor: UnfoldTransitionInteractor,
-    private val configurationInteractor: ConfigurationInteractor,
+    @ShadeDisplayAware private val configurationInteractor: ConfigurationInteractor,
     private val animationsStatus: AnimationStatusRepository,
-    private val powerInteractor: PowerInteractor
+    private val powerInteractor: PowerInteractor,
 ) {
 
     val shouldHideNotifications: Flow<Boolean>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt
index e644815..b6ce708 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt
@@ -47,7 +47,7 @@
 constructor(
     @ShadeDisplayAware private val context: Context,
     private val splitShadeStateController: Lazy<SplitShadeStateController>,
-    configurationInteractor: ConfigurationInteractor,
+    @ShadeDisplayAware configurationInteractor: ConfigurationInteractor,
     keyguardInteractor: KeyguardInteractor,
     deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
     largeScreenHeaderHelperLazy: Lazy<LargeScreenHeaderHelper>,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
index f75c89a..bffcae9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
@@ -20,6 +20,7 @@
 import android.view.View
 import androidx.lifecycle.lifecycleScope
 import com.android.app.tracing.TraceUtils.traceAsync
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.internal.logging.MetricsLogger
 import com.android.internal.logging.nano.MetricsProto
 import com.android.systemui.common.ui.ConfigurationState
@@ -30,6 +31,7 @@
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.statusbar.NotificationShelf
 import com.android.systemui.statusbar.notification.NotificationActivityStarter
 import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController
@@ -70,7 +72,6 @@
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /** Binds a [NotificationStackScrollLayout] to its [view model][NotificationListViewModel]. */
 class NotificationListViewBinder
@@ -78,7 +79,7 @@
 constructor(
     @Background private val backgroundDispatcher: CoroutineDispatcher,
     private val hiderTracker: DisplaySwitchNotificationsHiderTracker,
-    private val configuration: ConfigurationState,
+    @ShadeDisplayAware private val configuration: ConfigurationState,
     private val falsingManager: FalsingManager,
     private val hunBinder: HeadsUpNotificationViewBinder,
     private val loggerOptional: Optional<NotificationStatsLogger>,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
index 4a76871..c5bef99 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.lifecycle.viewModel
 import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationScrollViewModel
 import com.android.systemui.util.kotlin.FlowDumperImpl
@@ -48,7 +49,7 @@
     @Main private val mainImmediateDispatcher: CoroutineDispatcher,
     private val view: NotificationScrollView,
     private val viewModelFactory: NotificationScrollViewModel.Factory,
-    private val configuration: ConfigurationState,
+    @ShadeDisplayAware private val configuration: ConfigurationState,
 ) : FlowDumperImpl(dumpManager) {
 
     private val viewLeftOffset = MutableStateFlow(0).dumpValue("viewLeftOffset")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 827e2bf..fb60f26 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -72,6 +72,7 @@
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.LargeScreenHeaderHelper
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.shared.model.ShadeMode.Dual
 import com.android.systemui.shade.shared.model.ShadeMode.Single
@@ -116,7 +117,7 @@
     dumpManager: DumpManager,
     @Application applicationScope: CoroutineScope,
     private val context: Context,
-    configurationInteractor: ConfigurationInteractor,
+    @ShadeDisplayAware configurationInteractor: ConfigurationInteractor,
     private val keyguardInteractor: KeyguardInteractor,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val shadeInteractor: ShadeInteractor,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
index 398c1d4..90b591f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
@@ -23,11 +23,15 @@
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.util.ArrayMap;
+import android.view.Display;
 import android.widget.ImageView;
 
-import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
 
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
+
 import kotlinx.coroutines.flow.FlowKt;
 import kotlinx.coroutines.flow.MutableStateFlow;
 import kotlinx.coroutines.flow.StateFlow;
@@ -36,17 +40,15 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 
-import javax.inject.Inject;
-
 /**
  */
-@SysUISingleton
 public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher,
         LightBarTransitionsController.DarkIntensityApplier {
 
     private final LightBarTransitionsController mTransitionsController;
     private final ArrayList<Rect> mTintAreas = new ArrayList<>();
     private final ArrayMap<Object, DarkReceiver> mReceivers = new ArrayMap<>();
+    private final DumpManager mDumpManager;
 
     private int mIconTint = DEFAULT_ICON_TINT;
     private int mContrastTint = DEFAULT_INVERSE_ICON_TINT;
@@ -61,14 +63,25 @@
     private final MutableStateFlow<DarkChange> mDarkChangeFlow = StateFlowKt.MutableStateFlow(
             DarkChange.EMPTY);
 
+    private final String mDumpableName;
+
+    /** */
+    @AssistedFactory
+    @FunctionalInterface
+    public interface Factory {
+        /** */
+        DarkIconDispatcherImpl create(int displayId, Context context);
+    }
+
     /**
      */
-    @Inject
+    @AssistedInject
     public DarkIconDispatcherImpl(
-            Context context,
+            @Assisted int displayId,
+            @Assisted Context context,
             LightBarTransitionsController.Factory lightBarTransitionsControllerFactory,
             DumpManager dumpManager) {
-
+        mDumpManager = dumpManager;
         if (newStatusBarIcons()) {
             mDarkModeIconColorSingleTone = Color.BLACK;
             mLightModeIconColorSingleTone = Color.WHITE;
@@ -81,7 +94,19 @@
 
         mTransitionsController = lightBarTransitionsControllerFactory.create(this);
 
-        dumpManager.registerDumpable(getClass().getSimpleName(), this);
+        mDumpableName = getDumpableName(displayId);
+        dumpManager.registerNormalDumpable(mDumpableName, this);
+    }
+
+    @Override
+    public void stop() {
+        mDumpManager.unregisterDumpable(mDumpableName);
+    }
+
+    private String getDumpableName(int displayId) {
+        String dumpableNameSuffix =
+                displayId == Display.DEFAULT_DISPLAY ? "" : String.valueOf(displayId);
+        return getClass().getSimpleName() + dumpableNameSuffix;
     }
 
     public LightBarTransitionsController getTransitionsController() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index d0f4b6f..8de03d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -26,6 +26,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.widget.ViewClippingUtil;
+import com.android.systemui.dagger.qualifiers.DisplaySpecific;
 import com.android.systemui.flags.FeatureFlagsClassic;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -117,7 +118,7 @@
             PhoneStatusBarTransitions phoneStatusBarTransitions,
             KeyguardBypassController bypassController,
             NotificationWakeUpCoordinator wakeUpCoordinator,
-            DarkIconDispatcher darkIconDispatcher,
+            @DisplaySpecific DarkIconDispatcher darkIconDispatcher,
             KeyguardStateController keyguardStateController,
             CommandQueue commandQueue,
             NotificationStackScrollLayoutController stackScrollerController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java
index edc1f88..ccb9a11 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java
@@ -133,7 +133,7 @@
     public LightBarControllerImpl(
             @Assisted int displayId,
             @Assisted CoroutineScope coroutineScope,
-            DarkIconDispatcher darkIconDispatcher,
+            @Assisted DarkIconDispatcher darkIconDispatcher,
             BatteryController batteryController,
             NavigationModeController navModeController,
             @Assisted StatusBarModePerDisplayRepository statusBarModeRepository,
@@ -487,6 +487,7 @@
         LightBarControllerImpl create(
                 int displayId,
                 CoroutineScope coroutineScope,
+                DarkIconDispatcher darkIconDispatcher,
                 StatusBarModePerDisplayRepository statusBarModePerDisplayRepository);
     }
 }
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 4245494..16e023c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.Flags
 import com.android.systemui.Gefingerpoken
 import com.android.systemui.battery.BatteryMeterView
+import com.android.systemui.dagger.qualifiers.DisplaySpecific
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags.ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS
 import com.android.systemui.plugins.DarkIconDispatcher
@@ -341,7 +342,7 @@
         private val viewUtil: ViewUtil,
         private val configurationController: ConfigurationController,
         private val statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory,
-        private val darkIconDispatcher: DarkIconDispatcher,
+        @DisplaySpecific private val darkIconDispatcher: DarkIconDispatcher,
         private val statusBarContentInsetsProviderStore: StatusBarContentInsetsProviderStore,
     ) {
         fun create(view: PhoneStatusBarView): PhoneStatusBarViewController {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListener.kt
index c341bd3..394502b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListener.kt
@@ -28,10 +28,13 @@
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.DarkIconDispatcher
 import com.android.systemui.res.R
+import com.android.systemui.statusbar.data.repository.StatusBarConfigurationControllerStore
+import com.android.systemui.statusbar.data.repository.SysuiDarkIconDispatcherStore
 import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
@@ -40,14 +43,14 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 class StatusOverlayHoverListenerFactory
 @Inject
 constructor(
     @Main private val resources: Resources,
     private val configurationController: ConfigurationController,
-    private val darkIconDispatcher: SysuiDarkIconDispatcher,
+    private val darkIconDispatcherStore: SysuiDarkIconDispatcherStore,
+    private val statusBarConfigurationControllerStore: StatusBarConfigurationControllerStore,
 ) {
 
     /** Creates listener always using the same light color for overlay */
@@ -63,7 +66,7 @@
      * Creates listener using [DarkIconDispatcher] to determine light or dark color of the overlay
      */
     fun createDarkAwareListener(view: View) =
-        createDarkAwareListener(view, darkIconDispatcher.darkChangeFlow())
+        createDarkAwareListener(view, view.darkIconDispatcher.darkChangeFlow())
 
     /**
      * Creates listener using [DarkIconDispatcher] to determine light or dark color of the overlay
@@ -78,7 +81,7 @@
     ) =
         createDarkAwareListener(
             view,
-            darkIconDispatcher.darkChangeFlow(),
+            view.darkIconDispatcher.darkChangeFlow(),
             leftHoverMargin,
             rightHoverMargin,
             topHoverMargin,
@@ -92,8 +95,8 @@
     fun createDarkAwareListener(view: View, darkFlow: StateFlow<DarkChange>) =
         StatusOverlayHoverListener(
             view,
-            configurationController,
-            resources,
+            view.statusBarConfigurationController,
+            view.resources,
             darkFlow.map { toHoverTheme(view, it) },
         )
 
@@ -107,8 +110,8 @@
     ) =
         StatusOverlayHoverListener(
             view,
-            configurationController,
-            resources,
+            view.statusBarConfigurationController,
+            view.resources,
             darkFlow.map { toHoverTheme(view, it) },
             leftHoverMargin,
             rightHoverMargin,
@@ -116,6 +119,12 @@
             bottomHoverMargin,
         )
 
+    private val View.statusBarConfigurationController
+        get() = statusBarConfigurationControllerStore.forDisplay(context.displayId)
+
+    private val View.darkIconDispatcher
+        get() = darkIconDispatcherStore.forDisplay(context.displayId)
+
     private fun toHoverTheme(view: View, darkChange: DarkChange): HoverTheme {
         val calculatedTint = DarkIconDispatcher.getTint(darkChange.areas, view, darkChange.tint)
         // currently calculated tint is either white or some shade of black.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/data/repository/DarkIconRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/data/repository/DarkIconRepository.kt
index ba377497..49356eb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/data/repository/DarkIconRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/data/repository/DarkIconRepository.kt
@@ -16,7 +16,7 @@
 package com.android.systemui.statusbar.phone.data.repository
 
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher
+import com.android.systemui.statusbar.data.repository.SysuiDarkIconDispatcherStore
 import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange
 import dagger.Binds
 import dagger.Module
@@ -25,16 +25,16 @@
 
 /** Dark-mode state for tinting icons. */
 interface DarkIconRepository {
-    val darkState: StateFlow<DarkChange>
+    fun darkState(displayId: Int): StateFlow<DarkChange>
 }
 
 @SysUISingleton
 class DarkIconRepositoryImpl
 @Inject
-constructor(
-    darkIconDispatcher: SysuiDarkIconDispatcher,
-) : DarkIconRepository {
-    override val darkState: StateFlow<DarkChange> = darkIconDispatcher.darkChangeFlow()
+constructor(private val darkIconDispatcherStore: SysuiDarkIconDispatcherStore) :
+    DarkIconRepository {
+    override fun darkState(displayId: Int): StateFlow<DarkChange> =
+        darkIconDispatcherStore.forDisplay(displayId).darkChangeFlow()
 }
 
 @Module
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/interactor/DarkIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/interactor/DarkIconInteractor.kt
index 72f4540..095f0cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/interactor/DarkIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/interactor/DarkIconInteractor.kt
@@ -22,7 +22,8 @@
 import kotlinx.coroutines.flow.map
 
 /** States pertaining to calculating colors for icons in dark mode. */
-class DarkIconInteractor @Inject constructor(repository: DarkIconRepository) {
+class DarkIconInteractor @Inject constructor(private val repository: DarkIconRepository) {
     /** Dark-mode state for tinting icons. */
-    val darkState: Flow<DarkState> = repository.darkState.map { DarkState(it.areas, it.tint) }
+    fun darkState(displayId: Int): Flow<DarkState> =
+        repository.darkState(displayId).map { DarkState(it.areas, it.tint) }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 013141b..5cc4476 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -351,8 +351,11 @@
             mStatusBar.restoreHierarchyState(
                     savedInstanceState.getSparseParcelableArray(EXTRA_PANEL_STATE));
         }
-        mDarkIconManager = mDarkIconManagerFactory.create(
-                view.findViewById(R.id.statusIcons), StatusBarLocation.HOME);
+        mDarkIconManager =
+                mDarkIconManagerFactory.create(
+                        view.findViewById(R.id.statusIcons),
+                        StatusBarLocation.HOME,
+                        mHomeStatusBarComponent.getDarkIconDispatcher());
         mDarkIconManager.setShouldLog(true);
         updateBlockedIcons();
         mStatusBarIconController.addIconGroup(mDarkIconManager);
@@ -496,11 +499,12 @@
         NotificationIconContainer notificationIcons =
                 notificationIconArea.requireViewById(R.id.notificationIcons);
         mNotificationIconAreaInner = notificationIcons;
-        if (getContext().getDisplayId() == Display.DEFAULT_DISPLAY) {
+        int displayId = mHomeStatusBarComponent.getDisplayId();
+        if (displayId == Display.DEFAULT_DISPLAY) {
             //TODO(b/369337701): implement notification icons for all displays.
             // Currently if we try to bind for all displays, there is a crash, because the same
             // notification icon view can't have multiple parents.
-            mNicBindingDisposable = mNicViewBinder.bindWhileAttached(notificationIcons);
+            mNicBindingDisposable = mNicViewBinder.bindWhileAttached(notificationIcons, displayId);
         }
 
         if (!StatusBarSimpleFragment.isEnabled()) {
@@ -939,7 +943,9 @@
         if (mCarrierConfigTracker.getShowOperatorNameInStatusBarConfig(subId)) {
             View view = mStatusBar.findViewById(R.id.operator_name);
             mOperatorNameViewController =
-                    mOperatorNameViewControllerFactory.create((OperatorNameView) view);
+                    mOperatorNameViewControllerFactory.create(
+                            (OperatorNameView) view,
+                            mHomeStatusBarComponent.getDarkIconDispatcher());
             mOperatorNameViewController.init();
             // This view should not be visible on lock-screen
             if (mKeyguardStateController.isShowing()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarComponent.java
index d4cb625..f8ad0f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarComponent.java
@@ -17,7 +17,9 @@
 package com.android.systemui.statusbar.phone.fragment.dagger;
 
 import com.android.systemui.battery.BatteryMeterViewController;
+import com.android.systemui.dagger.qualifiers.DisplaySpecific;
 import com.android.systemui.dagger.qualifiers.RootView;
+import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor;
 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
 import com.android.systemui.statusbar.phone.LegacyLightsOutNotifController;
@@ -121,4 +123,12 @@
 
     /** */
     StatusBarBoundsProvider getBoundsProvider();
+
+    /** */
+    @DisplaySpecific
+    DarkIconDispatcher getDarkIconDispatcher();
+
+    /** */
+    @DisplaySpecific
+    int getDisplayId();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarModule.java
index 45c53b0..182f8d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarModule.java
@@ -22,8 +22,10 @@
 import com.android.systemui.battery.BatteryMeterView;
 import com.android.systemui.dagger.qualifiers.DisplaySpecific;
 import com.android.systemui.dagger.qualifiers.RootView;
+import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.HeadsUpStatusBarView;
+import com.android.systemui.statusbar.data.repository.DarkIconDispatcherStore;
 import com.android.systemui.statusbar.data.repository.StatusBarConfigurationController;
 import com.android.systemui.statusbar.data.repository.StatusBarConfigurationControllerStore;
 import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions;
@@ -164,4 +166,12 @@
         return store.forDisplay(displayId);
     }
 
+    /** */
+    @Provides
+    @HomeStatusBarScope
+    @DisplaySpecific
+    static DarkIconDispatcher darkIconDispatcher(
+            @DisplaySpecific int displayId, DarkIconDispatcherStore store) {
+        return store.forDisplay(displayId);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/DarkIconManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/DarkIconManager.java
index 6c30330..8d314ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/DarkIconManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/DarkIconManager.java
@@ -19,7 +19,6 @@
 import android.widget.LinearLayout;
 
 import com.android.internal.statusbar.StatusBarIcon;
-import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.statusbar.StatusIconDisplayable;
 import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider;
@@ -29,35 +28,34 @@
 import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter;
 import com.android.systemui.statusbar.pipeline.wifi.ui.WifiUiAdapter;
 
-import javax.inject.Inject;
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
 
-/**
- * Version of {@link IconManager} that observes state from the DarkIconDispatcher.
- */
+/** Version of {@link IconManager} that observes state from the {@link DarkIconDispatcher}. */
 public class DarkIconManager extends IconManager {
     private final DarkIconDispatcher mDarkIconDispatcher;
     private final int mIconHorizontalMargin;
 
+    @AssistedInject
     public DarkIconManager(
-            LinearLayout linearLayout,
-            StatusBarLocation location,
+            @Assisted LinearLayout linearLayout,
+            @Assisted StatusBarLocation location,
             WifiUiAdapter wifiUiAdapter,
             MobileUiAdapter mobileUiAdapter,
             MobileContextProvider mobileContextProvider,
-            DarkIconDispatcher darkIconDispatcher) {
-        super(linearLayout,
-                location,
-                wifiUiAdapter,
-                mobileUiAdapter,
-                mobileContextProvider);
-        mIconHorizontalMargin = mContext.getResources().getDimensionPixelSize(
-                com.android.systemui.res.R.dimen.status_bar_icon_horizontal_margin);
+            @Assisted DarkIconDispatcher darkIconDispatcher) {
+        super(linearLayout, location, wifiUiAdapter, mobileUiAdapter, mobileContextProvider);
+        mIconHorizontalMargin =
+                mContext.getResources()
+                        .getDimensionPixelSize(
+                                com.android.systemui.res.R.dimen.status_bar_icon_horizontal_margin);
         mDarkIconDispatcher = darkIconDispatcher;
     }
 
     @Override
-    protected void onIconAdded(int index, String slot, boolean blocked,
-            StatusBarIconHolder holder) {
+    protected void onIconAdded(
+            int index, String slot, boolean blocked, StatusBarIconHolder holder) {
         StatusIconDisplayable view = addHolder(index, slot, blocked, holder);
         mDarkIconDispatcher.addDarkReceiver(view);
     }
@@ -106,34 +104,14 @@
         super.exitDemoMode();
     }
 
-    @SysUISingleton
-    public static class Factory {
-        private final WifiUiAdapter mWifiUiAdapter;
-        private final MobileContextProvider mMobileContextProvider;
-        private final MobileUiAdapter mMobileUiAdapter;
-        private final DarkIconDispatcher mDarkIconDispatcher;
+    /** */
+    @AssistedFactory
+    public interface Factory {
 
-        @Inject
-        public Factory(
-                WifiUiAdapter wifiUiAdapter,
-                MobileContextProvider mobileContextProvider,
-                MobileUiAdapter mobileUiAdapter,
-                DarkIconDispatcher darkIconDispatcher) {
-            mWifiUiAdapter = wifiUiAdapter;
-            mMobileContextProvider = mobileContextProvider;
-            mMobileUiAdapter = mobileUiAdapter;
-            mDarkIconDispatcher = darkIconDispatcher;
-        }
-
-        /** Creates a new {@link DarkIconManager} for the given view group and location. */
-        public DarkIconManager create(LinearLayout group, StatusBarLocation location) {
-            return new DarkIconManager(
-                    group,
-                    location,
-                    mWifiUiAdapter,
-                    mMobileUiAdapter,
-                    mMobileContextProvider,
-                    mDarkIconDispatcher);
-        }
+        /** Creates a new {@link DarkIconManager}. */
+        DarkIconManager create(
+                LinearLayout group,
+                StatusBarLocation location,
+                DarkIconDispatcher darkIconDispatcher);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
index a472318..247abc3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
@@ -31,7 +31,10 @@
 import androidx.compose.ui.platform.ComposeView
 import androidx.compose.ui.viewinterop.AndroidView
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.systemui.plugins.DarkIconDispatcher
 import com.android.systemui.res.R
+import com.android.systemui.statusbar.data.repository.DarkIconDispatcherStore
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder
 import com.android.systemui.statusbar.phone.NotificationIconContainer
 import com.android.systemui.statusbar.phone.PhoneStatusBarView
@@ -44,7 +47,6 @@
 import com.android.systemui.statusbar.pipeline.shared.ui.binder.StatusBarVisibilityChangeListener
 import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel
 import javax.inject.Inject
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /** Factory to simplify the dependency management for [StatusBarRoot] */
 class StatusBarRootFactory
@@ -56,6 +58,7 @@
     private val darkIconManagerFactory: DarkIconManager.Factory,
     private val iconController: StatusBarIconController,
     private val ongoingCallController: OngoingCallController,
+    private val darkIconDispatcherStore: DarkIconDispatcherStore,
 ) {
     fun create(root: ViewGroup, andThen: (ViewGroup) -> Unit): ComposeView {
         val composeView = ComposeView(root.context)
@@ -69,6 +72,7 @@
                     darkIconManagerFactory = darkIconManagerFactory,
                     iconController = iconController,
                     ongoingCallController = ongoingCallController,
+                    darkIconDispatcher = darkIconDispatcherStore.forDisplay(root.context.displayId),
                     onViewCreated = andThen,
                 )
             }
@@ -97,6 +101,7 @@
     darkIconManagerFactory: DarkIconManager.Factory,
     iconController: StatusBarIconController,
     ongoingCallController: OngoingCallController,
+    darkIconDispatcher: DarkIconDispatcher,
     onViewCreated: (ViewGroup) -> Unit,
 ) {
     // None of these methods are used when [StatusBarSimpleFragment] is on.
@@ -135,7 +140,11 @@
                         phoneStatusBarView.requireViewById<StatusIconContainer>(R.id.statusIcons)
                     // TODO(b/364360986): turn this into a repo/intr/viewmodel
                     val darkIconManager =
-                        darkIconManagerFactory.create(statusIconContainer, StatusBarLocation.HOME)
+                        darkIconManagerFactory.create(
+                            statusIconContainer,
+                            StatusBarLocation.HOME,
+                            darkIconDispatcher,
+                        )
                     iconController.addIconGroup(darkIconManager)
 
                     // TODO(b/372657935): This won't be needed once OngoingCallController is
@@ -157,9 +166,13 @@
                     // TODO(b/369337701): implement notification icons for all displays.
                     //  Currently if we try to bind for all displays, there is a crash, because the
                     //  same notification icon view can't have multiple parents.
-                    if (context.displayId == Display.DEFAULT_DISPLAY) {
+                    val displayId = context.displayId
+                    if (displayId == Display.DEFAULT_DISPLAY) {
                         scope.launch {
-                            notificationIconsBinder.bindWhileAttached(notificationIconContainer)
+                            notificationIconsBinder.bindWhileAttached(
+                                notificationIconContainer,
+                                displayId,
+                            )
                         }
                     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractor.kt
index 03499cb..885a2b0 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractor.kt
@@ -19,6 +19,7 @@
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.unfold.data.repository.UnfoldTransitionRepository
 import com.android.systemui.unfold.data.repository.UnfoldTransitionStatus.TransitionFinished
 import com.android.systemui.unfold.data.repository.UnfoldTransitionStatus.TransitionInProgress
@@ -41,7 +42,7 @@
 @Inject
 constructor(
     private val repository: UnfoldTransitionRepository,
-    private val configurationInteractor: ConfigurationInteractor,
+    @ShadeDisplayAware private val configurationInteractor: ConfigurationInteractor,
 ) {
     /** Returns availability of fold/unfold transitions on the device */
     val isAvailable: Boolean
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/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderScope.kt
similarity index 65%
copy from libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
copy to packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderScope.kt
index e21bf8f..9f5e0f6 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderScope.kt
@@ -14,6 +14,15 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.shared;
+package com.android.systemui.volume.dialog.sliders.dagger
 
-parcelable GroupedRecentTaskInfo;
\ No newline at end of file
+import javax.inject.Scope
+
+/**
+ * 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/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/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/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt
index b3bd7d1..c7beb15 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt
@@ -23,7 +23,6 @@
 import android.view.accessibility.AccessibilityManager
 import com.android.app.viewcapture.ViewCaptureAwareWindowManager
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.media.taptotransfer.MediaTttFlags
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.temporarydisplay.TemporaryViewUiEventLogger
@@ -43,7 +42,6 @@
     dumpManager: DumpManager,
     powerManager: PowerManager,
     mainHandler: Handler,
-    mediaTttFlags: MediaTttFlags,
     uiEventLogger: MediaTttReceiverUiEventLogger,
     viewUtil: ViewUtil,
     wakeLockBuilder: WakeLock.Builder,
@@ -62,7 +60,6 @@
         dumpManager,
         powerManager,
         mainHandler,
-        mediaTttFlags,
         uiEventLogger,
         viewUtil,
         wakeLockBuilder,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index 9afa5ad..378dd45 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -37,7 +37,6 @@
 import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.media.taptotransfer.MediaTttFlags
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.policy.ConfigurationController
@@ -55,7 +54,6 @@
 import org.mockito.ArgumentCaptor
 import org.mockito.Mock
 import org.mockito.Mockito.never
-import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
@@ -66,32 +64,18 @@
 class MediaTttChipControllerReceiverTest : SysuiTestCase() {
     private lateinit var controllerReceiver: MediaTttChipControllerReceiver
 
-    @Mock
-    private lateinit var packageManager: PackageManager
-    @Mock
-    private lateinit var applicationInfo: ApplicationInfo
-    @Mock
-    private lateinit var logger: MediaTttReceiverLogger
-    @Mock
-    private lateinit var accessibilityManager: AccessibilityManager
-    @Mock
-    private lateinit var configurationController: ConfigurationController
-    @Mock
-    private lateinit var dumpManager: DumpManager
-    @Mock
-    private lateinit var mediaTttFlags: MediaTttFlags
-    @Mock
-    private lateinit var powerManager: PowerManager
-    @Mock
-    private lateinit var viewUtil: ViewUtil
-    @Mock
-    private lateinit var windowManager: WindowManager
-    @Mock
-    private lateinit var commandQueue: CommandQueue
-    @Mock
-    private lateinit var rippleController: MediaTttReceiverRippleController
-    @Mock
-    private lateinit var lazyViewCapture: Lazy<ViewCapture>
+    @Mock private lateinit var packageManager: PackageManager
+    @Mock private lateinit var applicationInfo: ApplicationInfo
+    @Mock private lateinit var logger: MediaTttReceiverLogger
+    @Mock private lateinit var accessibilityManager: AccessibilityManager
+    @Mock private lateinit var configurationController: ConfigurationController
+    @Mock private lateinit var dumpManager: DumpManager
+    @Mock private lateinit var powerManager: PowerManager
+    @Mock private lateinit var viewUtil: ViewUtil
+    @Mock private lateinit var windowManager: WindowManager
+    @Mock private lateinit var commandQueue: CommandQueue
+    @Mock private lateinit var rippleController: MediaTttReceiverRippleController
+    @Mock private lateinit var lazyViewCapture: Lazy<ViewCapture>
     private lateinit var viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager
     private lateinit var commandQueueCallback: CommandQueue.Callbacks
     private lateinit var fakeAppIconDrawable: Drawable
@@ -106,14 +90,17 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        whenever(mediaTttFlags.isMediaTttEnabled()).thenReturn(true)
 
         fakeAppIconDrawable = context.getDrawable(R.drawable.ic_cake)!!
         whenever(packageManager.getApplicationIcon(PACKAGE_NAME)).thenReturn(fakeAppIconDrawable)
         whenever(applicationInfo.loadLabel(packageManager)).thenReturn(APP_NAME)
-        whenever(packageManager.getApplicationInfo(
-            eq(PACKAGE_NAME), any<PackageManager.ApplicationInfoFlags>()
-        )).thenReturn(applicationInfo)
+        whenever(
+                packageManager.getApplicationInfo(
+                    eq(PACKAGE_NAME),
+                    any<PackageManager.ApplicationInfoFlags>(),
+                )
+            )
+            .thenReturn(applicationInfo)
         context.setMockPackageManager(packageManager)
 
         fakeClock = FakeSystemClock()
@@ -127,27 +114,31 @@
         fakeWakeLockBuilder = WakeLockFake.Builder(context)
         fakeWakeLockBuilder.setWakeLock(fakeWakeLock)
 
-        viewCaptureAwareWindowManager = ViewCaptureAwareWindowManager(windowManager,
-                lazyViewCapture, isViewCaptureEnabled = false)
-        controllerReceiver = FakeMediaTttChipControllerReceiver(
-            commandQueue,
-            context,
-            logger,
-            viewCaptureAwareWindowManager,
-            fakeExecutor,
-            accessibilityManager,
-            configurationController,
-            dumpManager,
-            powerManager,
-            Handler.getMain(),
-            mediaTttFlags,
-            receiverUiEventLogger,
-            viewUtil,
-            fakeWakeLockBuilder,
-            fakeClock,
-            rippleController,
-            temporaryViewUiEventLogger,
-        )
+        viewCaptureAwareWindowManager =
+            ViewCaptureAwareWindowManager(
+                windowManager,
+                lazyViewCapture,
+                isViewCaptureEnabled = false,
+            )
+        controllerReceiver =
+            FakeMediaTttChipControllerReceiver(
+                commandQueue,
+                context,
+                logger,
+                viewCaptureAwareWindowManager,
+                fakeExecutor,
+                accessibilityManager,
+                configurationController,
+                dumpManager,
+                powerManager,
+                Handler.getMain(),
+                receiverUiEventLogger,
+                viewUtil,
+                fakeWakeLockBuilder,
+                fakeClock,
+                rippleController,
+                temporaryViewUiEventLogger,
+            )
         controllerReceiver.start()
 
         val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
@@ -156,48 +147,18 @@
     }
 
     @Test
-    fun commandQueueCallback_flagOff_noCallbackAdded() {
-        reset(commandQueue)
-        whenever(mediaTttFlags.isMediaTttEnabled()).thenReturn(false)
-
-        controllerReceiver = MediaTttChipControllerReceiver(
-            commandQueue,
-            context,
-            logger,
-            viewCaptureAwareWindowManager,
-            FakeExecutor(FakeSystemClock()),
-            accessibilityManager,
-            configurationController,
-            dumpManager,
-            powerManager,
-            Handler.getMain(),
-            mediaTttFlags,
-            receiverUiEventLogger,
-            viewUtil,
-            fakeWakeLockBuilder,
-            fakeClock,
-            rippleController,
-            temporaryViewUiEventLogger,
-        )
-        controllerReceiver.start()
-
-        verify(commandQueue, never()).addCallback(any())
-    }
-
-    @Test
     fun commandQueueCallback_closeToSender_triggersChip() {
         val appName = "FakeAppName"
         commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
             StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER,
             routeInfo,
             /* appIcon= */ null,
-            appName
+            appName,
         )
 
         assertThat(getChipView().getAppIconView().contentDescription).isEqualTo(appName)
-        assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(
-            MediaTttReceiverUiEvents.MEDIA_TTT_RECEIVER_CLOSE_TO_SENDER.id
-        )
+        assertThat(uiEventLoggerFake.eventId(0))
+            .isEqualTo(MediaTttReceiverUiEvents.MEDIA_TTT_RECEIVER_CLOSE_TO_SENDER.id)
         assertThat(uiEventLoggerFake.logs[0].instanceId).isNotNull()
     }
 
@@ -207,45 +168,44 @@
             StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER,
             routeInfo,
             null,
-            null
+            null,
         )
 
         verify(windowManager, never()).addView(any(), any())
-        assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(
-            MediaTttReceiverUiEvents.MEDIA_TTT_RECEIVER_FAR_FROM_SENDER.id
-        )
+        assertThat(uiEventLoggerFake.eventId(0))
+            .isEqualTo(MediaTttReceiverUiEvents.MEDIA_TTT_RECEIVER_FAR_FROM_SENDER.id)
         assertThat(uiEventLoggerFake.logs[0].instanceId).isNotNull()
     }
 
     @Test
     fun commandQueueCallback_transferToReceiverSucceeded_noChipShown() {
         commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
-                StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
-                routeInfo,
-                null,
-                null
+            StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+            routeInfo,
+            null,
+            null,
         )
 
         verify(windowManager, never()).addView(any(), any())
-        assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(
+        assertThat(uiEventLoggerFake.eventId(0))
+            .isEqualTo(
                 MediaTttReceiverUiEvents.MEDIA_TTT_RECEIVER_TRANSFER_TO_RECEIVER_SUCCEEDED.id
-        )
+            )
         assertThat(uiEventLoggerFake.logs[0].instanceId).isNotNull()
     }
 
     @Test
     fun commandQueueCallback_transferToReceiverFailed_noChipShown() {
         commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
-                StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_TRANSFER_TO_RECEIVER_FAILED,
-                routeInfo,
-                null,
-                null
+            StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_TRANSFER_TO_RECEIVER_FAILED,
+            routeInfo,
+            null,
+            null,
         )
 
         verify(windowManager, never()).addView(any(), any())
-        assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(
-                MediaTttReceiverUiEvents.MEDIA_TTT_RECEIVER_TRANSFER_TO_RECEIVER_FAILED.id
-        )
+        assertThat(uiEventLoggerFake.eventId(0))
+            .isEqualTo(MediaTttReceiverUiEvents.MEDIA_TTT_RECEIVER_TRANSFER_TO_RECEIVER_FAILED.id)
         assertThat(uiEventLoggerFake.logs[0].instanceId).isNotNull()
     }
 
@@ -255,14 +215,14 @@
             StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER,
             routeInfo,
             null,
-            null
+            null,
         )
 
         commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
             StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER,
             routeInfo,
             null,
-            null
+            null,
         )
 
         val viewCaptor = ArgumentCaptor.forClass(View::class.java)
@@ -276,14 +236,14 @@
             StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER,
             routeInfo,
             null,
-            null
+            null,
         )
 
         commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
             StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
             routeInfo,
             null,
-            null
+            null,
         )
 
         val viewCaptor = ArgumentCaptor.forClass(View::class.java)
@@ -297,14 +257,14 @@
             StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER,
             routeInfo,
             null,
-            null
+            null,
         )
 
         commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
             StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
             routeInfo,
             null,
-            null
+            null,
         )
 
         assertThat(uiEventLoggerFake[0].instanceId).isEqualTo(uiEventLoggerFake[1].instanceId)
@@ -316,14 +276,14 @@
             StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER,
             routeInfo,
             null,
-            null
+            null,
         )
 
         commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
             StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_TRANSFER_TO_RECEIVER_FAILED,
             routeInfo,
             null,
-            null
+            null,
         )
 
         val viewCaptor = ArgumentCaptor.forClass(View::class.java)
@@ -334,19 +294,19 @@
     @Test
     fun commandQueueCallback_closeThenFar_wakeLockAcquiredThenReleased() {
         commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
-                StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER,
-                routeInfo,
-                null,
-                null
+            StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER,
+            routeInfo,
+            null,
+            null,
         )
 
         assertThat(fakeWakeLock.isHeld).isTrue()
 
         commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
-                StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER,
-                routeInfo,
-                null,
-                null
+            StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER,
+            routeInfo,
+            null,
+            null,
         )
 
         assertThat(fakeWakeLock.isHeld).isFalse()
@@ -355,10 +315,10 @@
     @Test
     fun commandQueueCallback_closeThenFar_wakeLockNeverAcquired() {
         commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
-                StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER,
-                routeInfo,
-                null,
-                null
+            StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER,
+            routeInfo,
+            null,
+            null,
         )
 
         assertThat(fakeWakeLock.isHeld).isFalse()
@@ -370,7 +330,7 @@
             StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER,
             routeInfo,
             null,
-            null
+            null,
         )
 
         verify(logger).logStateChange(any(), any(), any())
@@ -391,10 +351,12 @@
         val view = getChipView()
         assertThat(view.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
         assertThat(view.getAppIconView().contentDescription)
-            .isEqualTo(context.getString(
-                R.string.media_transfer_receiver_content_description_with_app_name,
-                APP_NAME,
-            ))
+            .isEqualTo(
+                context.getString(
+                    R.string.media_transfer_receiver_content_description_with_app_name,
+                    APP_NAME,
+                )
+            )
     }
 
     @Test
@@ -463,7 +425,7 @@
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
             routeInfo,
             null,
-            APP_NAME
+            APP_NAME,
         )
 
         verify(windowManager, never()).addView(any(), any())
@@ -476,10 +438,11 @@
     }
 
     private fun getChipReceiverInfo(packageName: String?): ChipReceiverInfo {
-        val routeInfo = MediaRoute2Info.Builder("id", "Test route name")
-            .addFeature("feature")
-            .setClientPackageName(packageName)
-            .build()
+        val routeInfo =
+            MediaRoute2Info.Builder("id", "Test route name")
+                .addFeature("feature")
+                .setClientPackageName(packageName)
+                .build()
         return ChipReceiverInfo(
             routeInfo,
             null,
@@ -495,7 +458,8 @@
 private const val APP_NAME = "Fake app name"
 private const val PACKAGE_NAME = "com.android.systemui"
 
-private val routeInfo = MediaRoute2Info.Builder("id", "Test route name")
-    .addFeature("feature")
-    .setClientPackageName(PACKAGE_NAME)
-    .build()
+private val routeInfo =
+    MediaRoute2Info.Builder("id", "Test route name")
+        .addFeature("feature")
+        .setClientPackageName(PACKAGE_NAME)
+        .build()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
index b4cad6b..c90ac59 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
@@ -41,7 +41,6 @@
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.common.shared.model.Text.Companion.loadText
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.media.taptotransfer.MediaTttFlags
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.CommandQueue
@@ -95,7 +94,6 @@
     @Mock private lateinit var falsingCollector: FalsingCollector
     @Mock private lateinit var chipbarLogger: ChipbarLogger
     @Mock private lateinit var logger: MediaTttSenderLogger
-    @Mock private lateinit var mediaTttFlags: MediaTttFlags
     @Mock private lateinit var packageManager: PackageManager
     @Mock private lateinit var powerManager: PowerManager
     @Mock private lateinit var viewUtil: ViewUtil
@@ -118,7 +116,6 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        whenever(mediaTttFlags.isMediaTttEnabled()).thenReturn(true)
         whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())).thenReturn(TIMEOUT)
 
         fakeAppIconDrawable = context.getDrawable(R.drawable.ic_cake)!!
@@ -127,7 +124,7 @@
         whenever(
                 packageManager.getApplicationInfo(
                     eq(PACKAGE_NAME),
-                    any<PackageManager.ApplicationInfoFlags>()
+                    any<PackageManager.ApplicationInfoFlags>(),
                 )
             )
             .thenReturn(applicationInfo)
@@ -148,8 +145,11 @@
             ChipbarCoordinator(
                 context,
                 chipbarLogger,
-                ViewCaptureAwareWindowManager(windowManager, lazyViewCapture,
-                        isViewCaptureEnabled = false),
+                ViewCaptureAwareWindowManager(
+                    windowManager,
+                    lazyViewCapture,
+                    isViewCaptureEnabled = false,
+                ),
                 fakeExecutor,
                 accessibilityManager,
                 configurationController,
@@ -174,7 +174,6 @@
                 context,
                 dumpManager,
                 logger,
-                mediaTttFlags,
                 uiEventLogger,
             )
         underTest.start()
@@ -183,30 +182,11 @@
     }
 
     @Test
-    fun commandQueueCallback_flagOff_noCallbackAdded() {
-        reset(commandQueue)
-        whenever(mediaTttFlags.isMediaTttEnabled()).thenReturn(false)
-        underTest =
-            MediaTttSenderCoordinator(
-                chipbarCoordinator,
-                commandQueue,
-                context,
-                dumpManager,
-                logger,
-                mediaTttFlags,
-                uiEventLogger,
-            )
-        underTest.start()
-
-        verify(commandQueue, never()).addCallback(any())
-    }
-
-    @Test
     fun commandQueueCallback_almostCloseToStartCast_triggersCorrectChip() {
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
             routeInfo,
-            null
+            null,
         )
 
         val chipbarView = getChipbarView()
@@ -220,13 +200,7 @@
         assertThat(uiEventLoggerFake.eventId(0))
             .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_START_CAST.id)
         verify(vibratorHelper)
-            .vibrate(
-                any(),
-                any(),
-                any<VibrationEffect>(),
-                any(),
-                any<VibrationAttributes>(),
-            )
+            .vibrate(any(), any(), any<VibrationEffect>(), any(), any<VibrationAttributes>())
     }
 
     @Test
@@ -249,7 +223,7 @@
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
             routeInfo,
-            null
+            null,
         )
 
         val chipbarView = getChipbarView()
@@ -263,13 +237,7 @@
         assertThat(uiEventLoggerFake.eventId(0))
             .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_END_CAST.id)
         verify(vibratorHelper)
-            .vibrate(
-                any(),
-                any(),
-                any<VibrationEffect>(),
-                any(),
-                any<VibrationAttributes>(),
-            )
+            .vibrate(any(), any(), any<VibrationEffect>(), any(), any<VibrationAttributes>())
     }
 
     @Test
@@ -277,7 +245,7 @@
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
             routeInfo,
-            null
+            null,
         )
 
         val chipbarView = getChipbarView()
@@ -291,13 +259,7 @@
         assertThat(uiEventLoggerFake.eventId(0))
             .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_TRIGGERED.id)
         verify(vibratorHelper)
-            .vibrate(
-                any(),
-                any(),
-                any<VibrationEffect>(),
-                any(),
-                any<VibrationAttributes>(),
-            )
+            .vibrate(any(), any(), any<VibrationEffect>(), any(), any<VibrationAttributes>())
     }
 
     @Test
@@ -320,7 +282,7 @@
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
             routeInfo,
-            null
+            null,
         )
 
         val chipbarView = getChipbarView()
@@ -334,13 +296,7 @@
         assertThat(uiEventLoggerFake.eventId(0))
             .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_TRIGGERED.id)
         verify(vibratorHelper)
-            .vibrate(
-                any(),
-                any(),
-                any<VibrationEffect>(),
-                any(),
-                any<VibrationAttributes>(),
-            )
+            .vibrate(any(), any(), any<VibrationEffect>(), any(), any<VibrationAttributes>())
     }
 
     @Test
@@ -350,7 +306,7 @@
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
             routeInfo,
-            null
+            null,
         )
 
         val chipbarView = getChipbarView()
@@ -364,13 +320,7 @@
         assertThat(uiEventLoggerFake.eventId(2))
             .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_SUCCEEDED.id)
         verify(vibratorHelper, never())
-            .vibrate(
-                any(),
-                any(),
-                any<VibrationEffect>(),
-                any(),
-                any<VibrationAttributes>(),
-            )
+            .vibrate(any(), any(), any<VibrationEffect>(), any(), any<VibrationAttributes>())
     }
 
     @Test
@@ -380,7 +330,7 @@
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
             routeInfo,
-            null
+            null,
         )
 
         // Event index 2 since initially displaying the triggered chip would also log two events.
@@ -397,7 +347,7 @@
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
             routeInfo,
-            /* undoCallback= */ null
+            /* undoCallback= */ null,
         )
 
         val chipbarView = getChipbarView()
@@ -452,7 +402,7 @@
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
             routeInfo,
-            null
+            null,
         )
 
         val chipbarView = getChipbarView()
@@ -466,13 +416,7 @@
         assertThat(uiEventLoggerFake.eventId(2))
             .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_SUCCEEDED.id)
         verify(vibratorHelper, never())
-            .vibrate(
-                any(),
-                any(),
-                any<VibrationEffect>(),
-                any(),
-                any<VibrationAttributes>(),
-            )
+            .vibrate(any(), any(), any<VibrationEffect>(), any(), any<VibrationAttributes>())
     }
 
     @Test
@@ -481,7 +425,7 @@
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
             routeInfo,
-            /* undoCallback= */ null
+            /* undoCallback= */ null,
         )
 
         val chipbarView = getChipbarView()
@@ -538,7 +482,7 @@
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED,
             routeInfo,
-            null
+            null,
         )
 
         val chipbarView = getChipbarView()
@@ -553,13 +497,7 @@
         assertThat(uiEventLoggerFake.eventId(2))
             .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_FAILED.id)
         verify(vibratorHelper)
-            .vibrate(
-                any(),
-                any(),
-                any<VibrationEffect>(),
-                any(),
-                any<VibrationAttributes>(),
-            )
+            .vibrate(any(), any(), any<VibrationEffect>(), any(), any<VibrationAttributes>())
     }
 
     @Test
@@ -567,13 +505,13 @@
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
             routeInfo,
-            null
+            null,
         )
         reset(vibratorHelper)
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED,
             routeInfo,
-            null
+            null,
         )
 
         val chipbarView = getChipbarView()
@@ -588,13 +526,7 @@
         assertThat(uiEventLoggerFake.eventId(2))
             .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_FAILED.id)
         verify(vibratorHelper)
-            .vibrate(
-                any(),
-                any(),
-                any<VibrationEffect>(),
-                any(),
-                any<VibrationAttributes>(),
-            )
+            .vibrate(any(), any(), any<VibrationEffect>(), any(), any<VibrationAttributes>())
     }
 
     @Test
@@ -602,7 +534,7 @@
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
             routeInfo,
-            null
+            null,
         )
 
         verify(windowManager, never()).addView(any(), any())
@@ -615,13 +547,13 @@
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
             routeInfo,
-            null
+            null,
         )
 
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
             routeInfo,
-            null
+            null,
         )
 
         val viewCaptor = ArgumentCaptor.forClass(View::class.java)
@@ -635,7 +567,7 @@
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
             routeInfo,
-            null
+            null,
         )
 
         assertThat(fakeWakeLock.isHeld).isTrue()
@@ -643,7 +575,7 @@
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
             routeInfo,
-            null
+            null,
         )
 
         assertThat(fakeWakeLock.isHeld).isFalse()
@@ -654,7 +586,7 @@
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
             routeInfo,
-            null
+            null,
         )
 
         assertThat(fakeWakeLock.isHeld).isFalse()
@@ -672,7 +604,7 @@
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
             routeInfo,
-            null
+            null,
         )
         verify(windowManager).addView(any(), any())
         reset(windowManager)
@@ -680,7 +612,7 @@
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
             routeInfo,
-            null
+            null,
         )
 
         verify(logger).logInvalidStateTransitionError(any(), any())
@@ -692,7 +624,7 @@
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
             routeInfo,
-            null
+            null,
         )
         verify(windowManager).addView(any(), any())
         reset(windowManager)
@@ -700,7 +632,7 @@
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
             routeInfo,
-            null
+            null,
         )
 
         verify(logger).logInvalidStateTransitionError(any(), any())
@@ -713,14 +645,14 @@
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
             routeInfo,
-            null
+            null,
         )
         reset(windowManager)
 
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
             routeInfo,
-            null
+            null,
         )
 
         verify(logger).logInvalidStateTransitionError(any(), any())
@@ -733,14 +665,14 @@
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
             routeInfo,
-            null
+            null,
         )
         reset(windowManager)
 
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
             routeInfo,
-            null
+            null,
         )
 
         verify(logger).logInvalidStateTransitionError(any(), any())
@@ -752,7 +684,7 @@
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
             routeInfo,
-            null
+            null,
         )
         verify(windowManager).addView(any(), any())
         reset(windowManager)
@@ -760,7 +692,7 @@
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
             routeInfo,
-            null
+            null,
         )
 
         verify(logger).logInvalidStateTransitionError(any(), any())
@@ -772,7 +704,7 @@
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
             routeInfo,
-            null
+            null,
         )
         verify(windowManager).addView(any(), any())
         reset(windowManager)
@@ -780,7 +712,7 @@
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
             routeInfo,
-            null
+            null,
         )
 
         verify(logger).logInvalidStateTransitionError(any(), any())
@@ -792,7 +724,7 @@
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
             routeInfo,
-            null
+            null,
         )
         verify(windowManager).addView(any(), any())
         reset(windowManager)
@@ -800,7 +732,7 @@
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED,
             routeInfo,
-            null
+            null,
         )
 
         verify(logger).logInvalidStateTransitionError(any(), any())
@@ -812,7 +744,7 @@
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
             routeInfo,
-            null
+            null,
         )
         verify(windowManager).addView(any(), any())
         reset(windowManager)
@@ -820,7 +752,7 @@
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED,
             routeInfo,
-            null
+            null,
         )
 
         verify(logger).logInvalidStateTransitionError(any(), any())
@@ -925,7 +857,7 @@
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
             routeInfo,
-            null
+            null,
         )
 
         verify(logger).logStateChange(any(), any(), any())
@@ -936,13 +868,13 @@
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
             routeInfo,
-            null
+            null,
         )
 
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
             routeInfo,
-            null
+            null,
         )
         fakeExecutor.runAllReady()
 
@@ -959,13 +891,13 @@
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
             routeInfo,
-            null
+            null,
         )
 
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
             routeInfo,
-            null
+            null,
         )
         fakeExecutor.runAllReady()
 
@@ -983,13 +915,13 @@
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
             routeInfo,
-            null
+            null,
         )
 
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
             routeInfo,
-            null
+            null,
         )
         fakeExecutor.runAllReady()
 
@@ -1007,13 +939,13 @@
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
             routeInfo,
-            null
+            null,
         )
 
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
             routeInfo,
-            null
+            null,
         )
         fakeExecutor.runAllReady()
 
@@ -1051,7 +983,7 @@
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
             routeInfo,
-            null
+            null,
         )
         fakeExecutor.runAllReady()
 
@@ -1091,7 +1023,7 @@
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
             routeInfo,
-            null
+            null,
         )
         fakeExecutor.runAllReady()
 
@@ -1116,7 +1048,6 @@
                 context,
                 dumpManager,
                 logger,
-                mediaTttFlags,
                 uiEventLogger,
             )
         underTest.start()
@@ -1144,7 +1075,6 @@
                 context,
                 dumpManager,
                 logger,
-                mediaTttFlags,
                 uiEventLogger,
             )
         underTest.start()
@@ -1178,7 +1108,6 @@
                 context,
                 dumpManager,
                 logger,
-                mediaTttFlags,
                 uiEventLogger,
             )
         underTest.start()
@@ -1211,7 +1140,6 @@
                 context,
                 dumpManager,
                 logger,
-                mediaTttFlags,
                 uiEventLogger,
             )
         underTest.start()
@@ -1230,7 +1158,7 @@
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
             routeInfo,
-            null
+            null,
         )
 
         // THEN the media coordinator unregisters the listener
@@ -1248,7 +1176,6 @@
                 context,
                 dumpManager,
                 logger,
-                mediaTttFlags,
                 uiEventLogger,
             )
         underTest.start()
@@ -1549,7 +1476,7 @@
     private fun ViewGroup.getUndoButton(): View = this.requireViewById(R.id.end_button)
 
     private fun ChipStateSender.getExpectedStateText(
-        otherDeviceName: String = OTHER_DEVICE_NAME,
+        otherDeviceName: String = OTHER_DEVICE_NAME
     ): String? {
         return this.getChipTextString(context, otherDeviceName).loadText(context)
     }
@@ -1560,7 +1487,7 @@
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
             routeInfo,
-            null
+            null,
         )
     }
 
@@ -1570,7 +1497,7 @@
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
             routeInfo,
-            null
+            null,
         )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
index 9639735..991f78a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
@@ -27,7 +27,6 @@
 import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
 import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
 import com.android.systemui.mediaprojection.SessionCreationSource
 import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver
@@ -56,7 +55,6 @@
 import org.mockito.Mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.spy
-import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
@@ -150,8 +148,6 @@
 
     @Test
     fun screenCaptureDisabledDialog_isShown_whenFunctionalityIsDisabled() {
-        whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES))
-            .thenReturn(true)
         whenever(devicePolicyResolver.isScreenCaptureCompletelyDisabled(any<UserHandle>()))
             .thenReturn(true)
 
@@ -170,48 +166,6 @@
     }
 
     @Test
-    fun screenCapturePermissionDialog_isShown_correctly() {
-        whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES))
-            .thenReturn(false)
-        whenever(devicePolicyResolver.isScreenCaptureCompletelyDisabled(any<UserHandle>()))
-            .thenReturn(false)
-        whenever(state.hasUserApprovedScreenRecording).thenReturn(false)
-
-        val screenRecordSwitch = dialog.requireViewById<Switch>(R.id.screenrecord_switch)
-        screenRecordSwitch.isChecked = true
-
-        bgExecutor.runAllReady()
-        mainExecutor.runAllReady()
-
-        verify(mediaProjectionMetricsLogger)
-            .notifyProjectionInitiated(
-                anyInt(),
-                eq(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER),
-            )
-        verify(factory, times(2)).create(any(SystemUIDialog.Delegate::class.java))
-    }
-
-    @Test
-    fun noDialogsAreShown_forScreenRecord_whenApprovalIsAlreadyGiven() {
-        whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES))
-            .thenReturn(false)
-        whenever(devicePolicyResolver.isScreenCaptureCompletelyDisabled(any<UserHandle>()))
-            .thenReturn(false)
-
-        val screenRecordSwitch = dialog.requireViewById<Switch>(R.id.screenrecord_switch)
-        screenRecordSwitch.isChecked = true
-
-        bgExecutor.runAllReady()
-
-        verify(mediaProjectionMetricsLogger)
-            .notifyProjectionInitiated(
-                anyInt(),
-                eq(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER),
-            )
-        verify(factory, never()).create()
-    }
-
-    @Test
     fun startButton_isDisabled_beforeIssueTypeIsSelected() {
         assertThat(dialog.getButton(Dialog.BUTTON_POSITIVE).isEnabled).isFalse()
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
index 6b16e78..afff485 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
@@ -42,15 +42,11 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.animation.DialogTransitionAnimator;
 import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger;
 import com.android.systemui.mediaprojection.SessionCreationSource;
 import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
 import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialogDelegate;
-import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.util.concurrency.FakeExecutor;
@@ -79,10 +75,6 @@
     @Mock
     private ScreenCaptureDevicePolicyResolver mDevicePolicyResolver;
     @Mock
-    private DialogTransitionAnimator mDialogTransitionAnimator;
-    @Mock
-    private ActivityStarter mActivityStarter;
-    @Mock
     private UserTracker mUserTracker;
     @Mock
     private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
@@ -92,10 +84,6 @@
     @Mock
     private SystemUIDialog mScreenCaptureDisabledDialog;
     @Mock
-    private ScreenRecordDialogDelegate.Factory mScreenRecordDialogFactory;
-    @Mock
-    private ScreenRecordDialogDelegate mScreenRecordDialogDelegate;
-    @Mock
     private ScreenRecordPermissionDialogDelegate.Factory
             mScreenRecordPermissionDialogDelegateFactory;
     @Mock
@@ -103,7 +91,6 @@
     @Mock
     private SystemUIDialog mScreenRecordSystemUIDialog;
 
-    private FakeFeatureFlags mFeatureFlags;
     private RecordingController mController;
 
     private static final int USER_ID = 10;
@@ -114,12 +101,8 @@
         Context spiedContext = spy(mContext);
         when(spiedContext.getUserId()).thenReturn(TEST_USER_ID);
 
-        mFeatureFlags = new FakeFeatureFlags();
         when(mScreenCaptureDisabledDialogDelegate.createSysUIDialog())
                 .thenReturn(mScreenCaptureDisabledDialog);
-        when(mScreenRecordDialogFactory.create(any(), any()))
-                .thenReturn(mScreenRecordDialogDelegate);
-        when(mScreenRecordDialogDelegate.createDialog()).thenReturn(mScreenRecordSystemUIDialog);
         when(mScreenRecordPermissionDialogDelegateFactory.create(any(), any(), anyInt(), any()))
                 .thenReturn(mScreenRecordPermissionDialogDelegate);
         when(mScreenRecordPermissionDialogDelegate.createDialog())
@@ -127,13 +110,11 @@
         mController = new RecordingController(
                 mMainExecutor,
                 mBroadcastDispatcher,
-                mFeatureFlags,
                 () -> mDevicePolicyResolver,
                 mUserTracker,
                 new RecordingControllerLogger(logcatLogBuffer("RecordingControllerTest")),
                 mMediaProjectionMetricsLogger,
                 mScreenCaptureDisabledDialogDelegate,
-                mScreenRecordDialogFactory,
                 mScreenRecordPermissionDialogDelegateFactory
         );
         mController.addCallback(mCallback);
@@ -236,46 +217,19 @@
     }
 
     @Test
-    public void testPoliciesFlagDisabled_screenCapturingNotAllowed_returnsNullDevicePolicyDialog() {
-        mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, false);
+    public void testScreenCapturingNotAllowed_returnsDevicePolicyDialog() {
         when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(true);
 
-        Dialog dialog =
-                mController.createScreenRecordDialog(
-                        mContext,
-                        mFeatureFlags,
-                        mDialogTransitionAnimator,
-                        mActivityStarter,
-                        /* onStartRecordingClicked= */ null);
-
-        assertThat(dialog).isSameInstanceAs(mScreenRecordSystemUIDialog);
-        assertThat(mScreenRecordPermissionDialogDelegate)
-                .isInstanceOf(ScreenRecordPermissionDialogDelegate.class);
-    }
-
-    @Test
-    public void testPoliciesFlagEnabled_screenCapturingNotAllowed_returnsDevicePolicyDialog() {
-        mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true);
-        when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(true);
-
-        Dialog dialog = mController.createScreenRecordDialog(mContext, mFeatureFlags,
-                mDialogTransitionAnimator, mActivityStarter, /* onStartRecordingClicked= */ null);
+        Dialog dialog = mController.createScreenRecordDialog(/* onStartRecordingClicked= */ null);
 
         assertThat(dialog).isEqualTo(mScreenCaptureDisabledDialog);
     }
 
     @Test
-    public void testPoliciesFlagEnabled_screenCapturingAllowed_returnsNullDevicePolicyDialog() {
-        mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true);
+    public void testScreenCapturingAllowed_returnsNullDevicePolicyDialog() {
         when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(false);
 
-        Dialog dialog =
-                mController.createScreenRecordDialog(
-                        mContext,
-                        mFeatureFlags,
-                        mDialogTransitionAnimator,
-                        mActivityStarter,
-                        /* onStartRecordingClicked= */ null);
+        Dialog dialog = mController.createScreenRecordDialog(/* onStartRecordingClicked= */ null);
 
         assertThat(dialog).isSameInstanceAs(mScreenRecordSystemUIDialog);
         assertThat(mScreenRecordPermissionDialogDelegate)
@@ -283,12 +237,10 @@
     }
 
     @Test
-    public void testPoliciesFlagEnabled_screenCapturingAllowed_logsProjectionInitiated() {
-        mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true);
+    public void testScreenCapturingAllowed_logsProjectionInitiated() {
         when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(false);
 
-        mController.createScreenRecordDialog(mContext, mFeatureFlags,
-                mDialogTransitionAnimator, mActivityStarter, /* onStartRecordingClicked= */ null);
+        mController.createScreenRecordDialog(/* onStartRecordingClicked= */ null);
 
         verify(mMediaProjectionMetricsLogger)
                 .notifyProjectionInitiated(
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 0427011..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ /dev/null
@@ -1,574 +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 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.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 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(),
-                mKosmos.getActiveNotificationsInteractor(),
-                () -> 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/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 7d019bf..b142fc2 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
@@ -510,7 +510,7 @@
         }
         mShadeController.setNotificationPresenter(mNotificationPresenter);
 
-        when(mOperatorNameViewControllerFactory.create(any()))
+        when(mOperatorNameViewControllerFactory.create(any(), any()))
                 .thenReturn(mOperatorNameViewController);
         when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
         when(mUserTracker.getUserHandle()).thenReturn(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index d01c1ca..4b648a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -1184,9 +1184,9 @@
         mKeyguardStateController = mock(KeyguardStateController.class);
         mOperatorNameViewController = mock(OperatorNameViewController.class);
         mOperatorNameViewControllerFactory = mock(OperatorNameViewController.Factory.class);
-        when(mOperatorNameViewControllerFactory.create(any()))
+        when(mOperatorNameViewControllerFactory.create(any(), any()))
                 .thenReturn(mOperatorNameViewController);
-        when(mIconManagerFactory.create(any(), any())).thenReturn(mIconManager);
+        when(mIconManagerFactory.create(any(), any(), any())).thenReturn(mIconManager);
         mSecureSettings = mock(SecureSettings.class);
 
         mShadeExpansionStateManager = new ShadeExpansionStateManager();
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryKosmos.kt
index 7e7eea2..f49e377 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryKosmos.kt
@@ -16,7 +16,48 @@
 
 package com.android.systemui.media.controls.domain.pipeline
 
+import android.content.applicationContext
+import android.os.Bundle
+import android.os.Handler
+import android.os.looper
+import androidx.media3.session.CommandButton
+import androidx.media3.session.MediaController
+import androidx.media3.session.SessionToken
+import com.android.systemui.Flags
+import com.android.systemui.graphics.imageLoader
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.media.controls.shared.mediaLogger
+import com.android.systemui.media.controls.util.fakeMediaControllerFactory
+import com.android.systemui.media.controls.util.fakeSessionTokenFactory
+import com.google.common.collect.ImmutableList
 import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
 
-var Kosmos.media3ActionFactory: Media3ActionFactory by Kosmos.Fixture { mock {} }
+/**
+ * Set up fake [Media3ActionFactory]. Note that tests using this fake will need to be
+ * annotated @RunWithLooper
+ */
+var Kosmos.media3ActionFactory: Media3ActionFactory by
+    Kosmos.Fixture {
+        if (Flags.mediaControlsButtonMedia3()) {
+            val customLayout = ImmutableList.of<CommandButton>()
+            val media3Controller =
+                mock<MediaController>().also {
+                    whenever(it.customLayout).thenReturn(customLayout)
+                    whenever(it.sessionExtras).thenReturn(Bundle())
+                }
+            fakeMediaControllerFactory.setMedia3Controller(media3Controller)
+            fakeSessionTokenFactory.setMedia3SessionToken(mock<SessionToken>())
+        }
+        Media3ActionFactory(
+            context = applicationContext,
+            imageLoader = imageLoader,
+            controllerFactory = fakeMediaControllerFactory,
+            tokenFactory = fakeSessionTokenFactory,
+            logger = mediaLogger,
+            looper = looper,
+            handler = Handler(looper),
+            bgScope = testScope,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
index bda3192..4ed49123 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.qs.footerActionsViewModelFactory
 import com.android.systemui.qs.panels.domain.interactor.tileSquishinessInteractor
 import com.android.systemui.qs.panels.ui.viewmodel.inFirstPageViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.mediaInRowInLandscapeViewModelFactory
 import com.android.systemui.qs.ui.viewmodel.quickSettingsContainerViewModelFactory
 import com.android.systemui.shade.largeScreenHeaderHelper
 import com.android.systemui.shade.transition.largeScreenShadeInterpolator
@@ -57,6 +58,7 @@
                     largeScreenHeaderHelper,
                     tileSquishinessInteractor,
                     inFirstPageViewModel,
+                    mediaInRowInLandscapeViewModelFactory,
                     qqsMediaHost,
                     qsMediaHost,
                     usingMediaInComposeFragment,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridViewModelKosmos.kt
index 7613ea31..57aa20a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridViewModelKosmos.kt
@@ -25,7 +25,7 @@
             override fun create(): InfiniteGridViewModel {
                 return InfiniteGridViewModel(
                     dynamicIconTilesViewModelFactory,
-                    qsColumnsViewModel,
+                    qsColumnsViewModelFactory,
                     tileSquishinessViewModel,
                     qsResetDialogDelegateKosmos,
                 )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelKosmos.kt
new file mode 100644
index 0000000..d1b613f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelKosmos.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.qs.panels.ui.viewmodel
+
+import android.content.res.Configuration
+import android.content.res.mainResources
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.media.controls.ui.controller.mediaHostStatesManager
+import com.android.systemui.qs.composefragment.dagger.usingMediaInComposeFragment
+import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shade.domain.interactor.shadeModeInteractor
+
+val Kosmos.mediaInRowInLandscapeViewModelFactory by
+    Kosmos.Fixture {
+        object : MediaInRowInLandscapeViewModel.Factory {
+            override fun create(inLocation: Int): MediaInRowInLandscapeViewModel {
+                return MediaInRowInLandscapeViewModel(
+                    mainResources,
+                    configurationInteractor,
+                    shadeModeInteractor,
+                    mediaHostStatesManager,
+                    usingMediaInComposeFragment,
+                    inLocation,
+                )
+            }
+        }
+    }
+
+fun Kosmos.setConfigurationForMediaInRow(mediaInRow: Boolean) {
+    shadeRepository.setShadeLayoutWide(!mediaInRow) // media in row only in non wide
+    val config =
+        Configuration(mainResources.configuration).apply {
+            orientation =
+                if (mediaInRow) {
+                    Configuration.ORIENTATION_LANDSCAPE
+                } else {
+                    Configuration.ORIENTATION_PORTRAIT
+                }
+            screenLayout = Configuration.SCREENLAYOUT_LONG_YES
+        }
+    mainResources.configuration.updateFrom(config)
+    fakeConfigurationRepository.onConfigurationChange(config)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt
index 5c8ca83..0e5edb7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt
@@ -23,7 +23,7 @@
     Kosmos.Fixture {
         PaginatedGridViewModel(
             iconTilesViewModel,
-            qsColumnsViewModel,
+            qsColumnsViewModelFactory,
             paginatedGridInteractor,
             inFirstPageViewModel,
         )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModelKosmos.kt
index 16b2f54..d63b1b0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModelKosmos.kt
@@ -19,4 +19,15 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.qs.panels.domain.interactor.qsColumnsInteractor
 
-val Kosmos.qsColumnsViewModel by Kosmos.Fixture { QSColumnsSizeViewModelImpl(qsColumnsInteractor) }
+val Kosmos.qsColumnsViewModelFactory by
+    Kosmos.Fixture {
+        object : QSColumnsViewModel.Factory {
+            override fun create(mediaLocation: Int?): QSColumnsViewModel {
+                return QSColumnsViewModel(
+                    qsColumnsInteractor,
+                    mediaInRowInLandscapeViewModelFactory,
+                    mediaLocation,
+                )
+            }
+        }
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt
index 20be5c6..81beb20 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt
@@ -27,8 +27,9 @@
             override fun create(): QuickQuickSettingsViewModel {
                 return QuickQuickSettingsViewModel(
                     currentTilesInteractor,
-                    qsColumnsViewModel,
+                    qsColumnsViewModelFactory,
                     quickQuickSettingsRowInteractor,
+                    mediaInRowInLandscapeViewModelFactory,
                     tileSquishinessViewModel,
                     iconTilesViewModel,
                     tileHapticsViewModelFactoryProvider,
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/notes/NotesTileKosmos.kt
similarity index 67%
copy from libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/notes/NotesTileKosmos.kt
index e21bf8f..75c98cb 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/notes/NotesTileKosmos.kt
@@ -14,6 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.shared;
+package com.android.systemui.qs.tiles.impl.notes
 
-parcelable GroupedRecentTaskInfo;
\ No newline at end of file
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.notetask.NoteTaskModule
+import com.android.systemui.qs.qsEventLogger
+
+val Kosmos.qsNotesTileConfig by
+    Kosmos.Fixture { NoteTaskModule.provideNotesTileConfig(qsEventLogger) }
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/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.kt
index 718347f..20e4523 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeOverlayContentViewModel
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModelFactory
 
 val Kosmos.notificationsShadeOverlayContentViewModel:
@@ -30,5 +31,6 @@
         notificationsPlaceholderViewModelFactory = notificationsPlaceholderViewModelFactory,
         sceneInteractor = sceneInteractor,
         shadeInteractor = shadeInteractor,
+        activeNotificationsInteractor = activeNotificationsInteractor,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
index ad2654a..45aab86 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.shade.mockNotificationShadeWindowViewController
 import com.android.systemui.shade.mockShadeSurface
 import com.android.systemui.statusbar.data.repository.fakeStatusBarModePerDisplayRepository
+import com.android.systemui.statusbar.data.repository.lightBarControllerStore
 import com.android.systemui.statusbar.data.repository.privacyDotWindowControllerStore
 import com.android.systemui.statusbar.data.repository.statusBarModeRepository
 import com.android.systemui.statusbar.mockNotificationRemoteInputManager
@@ -79,5 +80,6 @@
             statusBarWindowControllerStore,
             statusBarInitializerStore,
             privacyDotWindowControllerStore,
+            lightBarControllerStore,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/DarkIconDispatcherStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/DarkIconDispatcherStoreKosmos.kt
new file mode 100644
index 0000000..34a8281
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/DarkIconDispatcherStoreKosmos.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.displayWindowPropertiesRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import org.mockito.kotlin.mock
+
+val Kosmos.multiDisplayDarkIconDispatcherStore by
+    Kosmos.Fixture {
+        MultiDisplayDarkIconDispatcherStore(
+            backgroundApplicationScope = applicationCoroutineScope,
+            displayRepository = displayRepository,
+            factory = { _, _ -> mock() },
+            displayWindowPropertiesRepository = displayWindowPropertiesRepository,
+        )
+    }
+
+val Kosmos.fakeDarkIconDispatcherStore by Kosmos.Fixture { FakeDarkIconDispatcherStore() }
+
+var Kosmos.darkIconDispatcherStore by Kosmos.Fixture { fakeDarkIconDispatcherStore }
+
+val Kosmos.fakeSysUiDarkIconDispatcherStore by Kosmos.Fixture { FakeSysUiDarkIconDispatcherStore() }
+
+var Kosmos.sysUiDarkIconDispatcherStore by Kosmos.Fixture { fakeSysUiDarkIconDispatcherStore }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeDarkIconDispatcherStore.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeDarkIconDispatcherStore.kt
new file mode 100644
index 0000000..871b277
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeDarkIconDispatcherStore.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.statusbar.data.repository
+
+import android.view.Display
+import com.android.systemui.plugins.DarkIconDispatcher
+import org.mockito.kotlin.mock
+
+class FakeDarkIconDispatcherStore : DarkIconDispatcherStore {
+
+    private val perDisplayMocks = mutableMapOf<Int, DarkIconDispatcher>()
+
+    override val defaultDisplay: DarkIconDispatcher
+        get() = forDisplay(Display.DEFAULT_DISPLAY)
+
+    override fun forDisplay(displayId: Int): DarkIconDispatcher {
+        return perDisplayMocks.computeIfAbsent(displayId) { mock() }
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeLightBarControllerStore.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeLightBarControllerStore.kt
new file mode 100644
index 0000000..2823246
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeLightBarControllerStore.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.statusbar.data.repository
+
+import android.view.Display
+import com.android.systemui.statusbar.phone.LightBarController
+import org.mockito.kotlin.mock
+
+class FakeLightBarControllerStore : LightBarControllerStore {
+
+    val perDisplayMocks = mutableMapOf<Int, LightBarController>()
+
+    override val defaultDisplay: LightBarController
+        get() = forDisplay(Display.DEFAULT_DISPLAY)
+
+    override fun forDisplay(displayId: Int): LightBarController {
+        return perDisplayMocks.computeIfAbsent(displayId) { mock() }
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeSysUiDarkIconDispatcherStore.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeSysUiDarkIconDispatcherStore.kt
new file mode 100644
index 0000000..4ee323a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeSysUiDarkIconDispatcherStore.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.statusbar.data.repository
+
+import android.view.Display
+import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher
+import org.mockito.kotlin.mock
+
+class FakeSysUiDarkIconDispatcherStore : SysuiDarkIconDispatcherStore {
+
+    private val perDisplayMocks = mutableMapOf<Int, SysuiDarkIconDispatcher>()
+
+    override val defaultDisplay: SysuiDarkIconDispatcher
+        get() = forDisplay(Display.DEFAULT_DISPLAY)
+
+    override fun forDisplay(displayId: Int): SysuiDarkIconDispatcher {
+        return perDisplayMocks.computeIfAbsent(displayId) { mock() }
+    }
+}
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
index 5f33732..13fa3fe 100644
--- 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
@@ -27,8 +27,13 @@
         LightBarControllerStoreImpl(
             backgroundApplicationScope = applicationCoroutineScope,
             displayRepository = displayRepository,
-            factory = { _, _, _ -> mock() },
+            factory = { _, _, _, _ -> mock() },
             displayScopeRepository = displayScopeRepository,
             statusBarModeRepositoryStore = statusBarModeRepository,
+            darkIconDispatcherStore = darkIconDispatcherStore,
         )
     }
+
+val Kosmos.fakeLightBarControllerStore by Kosmos.Fixture { FakeLightBarControllerStore() }
+
+var Kosmos.lightBarControllerStore by Kosmos.Fixture { fakeLightBarControllerStore }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/repository/FakeDarkIconRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/repository/FakeDarkIconRepository.kt
index 282e2e8..cb092ce 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/repository/FakeDarkIconRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/repository/FakeDarkIconRepository.kt
@@ -24,7 +24,10 @@
 
 @SysUISingleton
 class FakeDarkIconRepository @Inject constructor() : DarkIconRepository {
-    override val darkState = MutableStateFlow(DarkChange.EMPTY)
+    private val perDisplayStates = mutableMapOf<Int, MutableStateFlow<DarkChange>>()
+
+    override fun darkState(displayId: Int) =
+        perDisplayStates.computeIfAbsent(displayId) { MutableStateFlow(DarkChange.EMPTY) }
 }
 
 @Module
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/ExecutionKosmos.kt
similarity index 68%
copy from libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
copy to packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/ExecutionKosmos.kt
index e21bf8f..bf66cb6 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/ExecutionKosmos.kt
@@ -14,6 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.shared;
+package com.android.systemui.util.concurrency
 
-parcelable GroupedRecentTaskInfo;
\ No newline at end of file
+import com.android.systemui.kosmos.Kosmos
+
+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/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/Vcn/framework-b/Android.bp b/packages/Vcn/framework-b/Android.bp
new file mode 100644
index 0000000..be64bb1
--- /dev/null
+++ b/packages/Vcn/framework-b/Android.bp
@@ -0,0 +1,44 @@
+//
+// 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 {
+    default_team: "trendy_team_enigma",
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_defaults {
+    name: "framework-connectivity-b-defaults",
+    sdk_version: "module_current",
+    min_sdk_version: "35", // TODO: Make it Android 25Q2 when this is included in mainline
+    defaults: ["framework-module-defaults"], // This is a boot jar
+
+    srcs: [
+        "src/**/*.java",
+    ],
+}
+
+java_sdk_library {
+    name: "framework-connectivity-b",
+    defaults: [
+        "framework-connectivity-b-defaults",
+    ],
+
+    permitted_packages: [
+        "android.net.vcn",
+    ],
+
+    // TODO: b/375213246 Expose this library to Tethering module
+}
diff --git a/packages/Vcn/framework-b/api/current.txt b/packages/Vcn/framework-b/api/current.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/packages/Vcn/framework-b/api/current.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/packages/Vcn/framework-b/api/module-lib-current.txt b/packages/Vcn/framework-b/api/module-lib-current.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/packages/Vcn/framework-b/api/module-lib-current.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/packages/Vcn/framework-b/api/module-lib-removed.txt b/packages/Vcn/framework-b/api/module-lib-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/packages/Vcn/framework-b/api/module-lib-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/packages/Vcn/framework-b/api/removed.txt b/packages/Vcn/framework-b/api/removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/packages/Vcn/framework-b/api/removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/packages/Vcn/framework-b/api/system-current.txt b/packages/Vcn/framework-b/api/system-current.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/packages/Vcn/framework-b/api/system-current.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/packages/Vcn/framework-b/api/system-removed.txt b/packages/Vcn/framework-b/api/system-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/packages/Vcn/framework-b/api/system-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl b/packages/Vcn/framework-b/src/android/net/vcn/Placeholder.java
similarity index 76%
copy from libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
copy to packages/Vcn/framework-b/src/android/net/vcn/Placeholder.java
index e21bf8f..fb5e153 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
+++ b/packages/Vcn/framework-b/src/android/net/vcn/Placeholder.java
@@ -14,6 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.shared;
+package android.net.vcn;
 
-parcelable GroupedRecentTaskInfo;
\ No newline at end of file
+/**
+ * Placeholder class so new framework-vcn isn't empty
+ *
+ * @hide
+ */
+// This class will be removed once source code is migrated
+public class Placeholder {}
diff --git a/packages/Vcn/service-b/Android.bp b/packages/Vcn/service-b/Android.bp
new file mode 100644
index 0000000..a462297
--- /dev/null
+++ b/packages/Vcn/service-b/Android.bp
@@ -0,0 +1,36 @@
+//
+// 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 {
+    default_team: "trendy_team_enigma",
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_library {
+    name: "service-connectivity-b-pre-jarjar",
+    sdk_version: "system_server_current",
+    min_sdk_version: "35", // TODO: Make it Android 25Q2 when this is included in mainline
+    defaults: ["framework-system-server-module-defaults"], // This is a system server jar
+
+    srcs: [
+        "src/**/*.java",
+    ],
+
+    // TODO: b/375213246 Expose this library to Tethering module
+    visibility: [
+        "//frameworks/base/services",
+    ],
+}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl b/packages/Vcn/service-b/src/com/android/server/vcn/Placeholder.java
similarity index 75%
copy from libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
copy to packages/Vcn/service-b/src/com/android/server/vcn/Placeholder.java
index e21bf8f..e799145 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/Placeholder.java
@@ -14,6 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.shared;
+package com.android.server.vcn;
 
-parcelable GroupedRecentTaskInfo;
\ No newline at end of file
+/**
+ * Placeholder class so new service-vcn isn't empty
+ *
+ * @hide
+ */
+// This class will be removed once source code is migrated
+public class Placeholder {}
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/Framework.bp b/ravenwood/Framework.bp
index d207738..99fc31b 100644
--- a/ravenwood/Framework.bp
+++ b/ravenwood/Framework.bp
@@ -214,7 +214,8 @@
 
 java_genrule {
     name: "services.core.ravenwood",
-    defaults: ["ravenwood-internal-only-visibility-genrule"],
+    // This is used by unit tests too (so tests will be able to access HSG-processed implementation)
+    // so it's visible to all.
     cmd: "cp $(in) $(out)",
     srcs: [
         ":services.core.ravenwood-base{ravenwood.jar}",
diff --git a/ravenwood/TEST_MAPPING b/ravenwood/TEST_MAPPING
index a1243e3..607592b 100644
--- a/ravenwood/TEST_MAPPING
+++ b/ravenwood/TEST_MAPPING
@@ -117,7 +117,11 @@
       "host": true
     },
     {
-      "name": "FrameworksServicesTestsRavenwood",
+      "name": "FrameworksServicesTestsRavenwood_Compat",
+      "host": true
+    },
+    {
+      "name": "FrameworksServicesTestsRavenwood_Uri",
       "host": true
     },
     {
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 28c262d..e61a054 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
@@ -31,9 +31,12 @@
 
 import android.annotation.Nullable;
 import android.app.ActivityManager;
+import android.app.AppCompatCallbacks;
 import android.app.Instrumentation;
 import android.app.ResourcesManager;
 import android.app.UiAutomation;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
 import android.content.res.Resources;
 import android.os.Binder;
 import android.os.Build;
@@ -42,6 +45,7 @@
 import android.os.Looper;
 import android.os.Process_ravenwood;
 import android.os.ServiceManager;
+import android.os.ServiceManager.ServiceNotFoundException;
 import android.os.SystemProperties;
 import android.provider.DeviceConfig_host;
 import android.system.ErrnoException;
@@ -51,6 +55,7 @@
 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;
@@ -58,6 +63,7 @@
 import com.android.ravenwood.common.RavenwoodRuntimeException;
 import com.android.ravenwood.common.SneakyThrow;
 import com.android.server.LocalServices;
+import com.android.server.compat.PlatformCompat;
 
 import org.junit.runner.Description;
 
@@ -86,6 +92,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 =
@@ -139,23 +146,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);
@@ -294,6 +339,8 @@
 
         RavenwoodSystemServer.init(config);
 
+        initializeCompatIds(config);
+
         if (ENABLE_TIMEOUT_STACKS) {
             sPendingTimeout = sTimeoutExecutor.schedule(
                     RavenwoodRuntimeEnvironmentController::dumpStacks,
@@ -309,6 +356,31 @@
         Binder.restoreCallingIdentity(packBinderIdentityToken(false, config.mUid, config.mPid));
     }
 
+    private static void initializeCompatIds(RavenwoodConfig config) {
+        // Set up compat-IDs for the app side.
+        // TODO: Inside the system server, all the compat-IDs should be enabled,
+        // Due to the `AppCompatCallbacks.install(new long[0], new long[0])` call in
+        // SystemServer.
+
+        // Compat framework only uses the package name and the target SDK level.
+        ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.packageName = config.mTargetPackageName;
+        appInfo.targetSdkVersion = config.mTargetSdkLevel;
+
+        PlatformCompat platformCompat = null;
+        try {
+            platformCompat = (PlatformCompat) ServiceManager.getServiceOrThrow(
+                    Context.PLATFORM_COMPAT_SERVICE);
+        } catch (ServiceNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+
+        var disabledChanges = platformCompat.getDisabledChanges(appInfo);
+        var loggableChanges = platformCompat.getLoggableChanges(appInfo);
+
+        AppCompatCallbacks.install(disabledChanges, loggableChanges);
+    }
+
     /**
      * De-initialize.
      */
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java
index f198a08..438a2bf 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java
@@ -17,7 +17,9 @@
 package android.platform.test.ravenwood;
 
 import android.content.ClipboardManager;
+import android.content.Context;
 import android.hardware.SerialManager;
+import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.ravenwood.example.BlueManager;
 import android.ravenwood.example.RedManager;
@@ -27,6 +29,8 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.SystemServiceManager;
+import com.android.server.compat.PlatformCompat;
+import com.android.server.compat.PlatformCompatNative;
 import com.android.server.utils.TimingsTraceAndSlog;
 
 import java.util.List;
@@ -65,6 +69,14 @@
     private static SystemServiceManager sServiceManager;
 
     public static void init(RavenwoodConfig config) {
+        // Always start PlatformCompat, regardless of the requested services.
+        // PlatformCompat is not really a SystemService, so it won't receive boot phases / etc.
+        // This initialization code is copied from SystemServer.java.
+        PlatformCompat platformCompat = new PlatformCompat(config.mState.mSystemServerContext);
+        ServiceManager.addService(Context.PLATFORM_COMPAT_SERVICE, platformCompat);
+        ServiceManager.addService(Context.PLATFORM_COMPAT_NATIVE_SERVICE,
+                new PlatformCompatNative(platformCompat));
+
         // Avoid overhead if no services required
         if (config.mServicesRequired.isEmpty()) return;
 
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 5b75e98..c1993f6 100644
--- a/ravenwood/runtime-jni/ravenwood_runtime.cpp
+++ b/ravenwood/runtime-jni/ravenwood_runtime.cpp
@@ -180,24 +180,6 @@
     return syscall(__NR_gettid);
 }
 
-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);
-}
-
 // ---- Registration ----
 
 extern void register_android_system_OsConstants(JNIEnv* env);
@@ -218,8 +200,6 @@
 };
 
 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 672c685..1910100 100755
--- a/ravenwood/scripts/run-ravenwood-tests.sh
+++ b/ravenwood/scripts/run-ravenwood-tests.sh
@@ -26,7 +26,7 @@
 
 
 # Regex to identify slow tests, in PCRE
-SLOW_TEST_RE='^(SystemUiRavenTests|CtsIcuTestCasesRavenwood)$'
+SLOW_TEST_RE='^(SystemUiRavenTests|CtsIcuTestCasesRavenwood|CarSystemUIRavenTests)$'
 
 smoke=0
 include_re=""
@@ -67,7 +67,7 @@
     if [[ "$re" == "" ]] ; then
         cat # No filtering
     else
-        grep $grep_arg -P "$re"
+        grep $grep_arg -iP "$re"
     fi
 }
 
diff --git a/ravenwood/tests/bivalenttest/Android.bp b/ravenwood/tests/bivalenttest/Android.bp
index 4895a1a..40e6672 100644
--- a/ravenwood/tests/bivalenttest/Android.bp
+++ b/ravenwood/tests/bivalenttest/Android.bp
@@ -58,6 +58,9 @@
 java_defaults {
     name: "ravenwood-bivalent-device-defaults",
     defaults: ["ravenwood-bivalent-defaults"],
+
+    target_sdk_version: "34", // For compat-framework tests
+
     // TODO(b/371215487): migrate bivalenttest.ravenizer tests to another architecture
     exclude_srcs: [
         "test/**/ravenizer/*.java",
diff --git a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/compat/RavenwoodCompatFrameworkTest.kt b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/compat/RavenwoodCompatFrameworkTest.kt
new file mode 100644
index 0000000..a95760d
--- /dev/null
+++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/compat/RavenwoodCompatFrameworkTest.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.bivalenttest.compat
+
+import android.app.compat.CompatChanges
+import android.os.Build
+import android.platform.test.ravenwood.RavenwoodConfig
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.internal.ravenwood.RavenwoodEnvironment.CompatIdsForTest
+import org.junit.Assert
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class RavenwoodCompatFrameworkTest {
+    companion object {
+        @JvmField // Expose as a raw field, not as a property.
+        @RavenwoodConfig.Config
+        val config = RavenwoodConfig.Builder()
+            .setTargetSdkLevel(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+            .build()
+    }
+
+    @Test
+    fun testEnabled() {
+        Assert.assertTrue(CompatChanges.isChangeEnabled(CompatIdsForTest.TEST_COMPAT_ID_1))
+    }
+
+    @Test
+    fun testDisabled() {
+        Assert.assertFalse(CompatChanges.isChangeEnabled(CompatIdsForTest.TEST_COMPAT_ID_2))
+    }
+
+    @Test
+    fun testEnabledAfterSForUApps() {
+        Assert.assertTrue(CompatChanges.isChangeEnabled(CompatIdsForTest.TEST_COMPAT_ID_3))
+    }
+
+    @Test
+    fun testEnabledAfterUForUApps() {
+        Assert.assertFalse(CompatChanges.isChangeEnabled(CompatIdsForTest.TEST_COMPAT_ID_4))
+    }
+}
\ No newline at end of file
diff --git a/ravenwood/tests/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java b/ravenwood/tests/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java
index b3d3963..4aae1e1 100644
--- a/ravenwood/tests/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java
+++ b/ravenwood/tests/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java
@@ -19,17 +19,22 @@
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
 
 import android.content.Context;
 import android.hardware.SerialManager;
 import android.hardware.SerialManagerInternal;
-import android.platform.test.ravenwood.RavenwoodRule;
+import android.platform.test.ravenwood.RavenwoodConfig;
+import android.platform.test.ravenwood.RavenwoodConfig.Config;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.server.LocalServices;
 
-import org.junit.Rule;
+import com.google.common.collect.Lists;
+
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -37,18 +42,25 @@
 public class RavenwoodServicesTest {
     private static final String TEST_VIRTUAL_PORT = "virtual:example";
 
-    @Rule
-    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+    @Config
+    public static final RavenwoodConfig sRavenwood = new RavenwoodConfig.Builder()
             .setProcessSystem()
             .setServicesRequired(SerialManager.class)
             .build();
 
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getInstrumentation().getContext();
+    }
+
     @Test
     public void testDefined() {
         final SerialManager fromName = (SerialManager)
-                mRavenwood.getContext().getSystemService(Context.SERIAL_SERVICE);
+                mContext.getSystemService(Context.SERIAL_SERVICE);
         final SerialManager fromClass =
-                mRavenwood.getContext().getSystemService(SerialManager.class);
+                mContext.getSystemService(SerialManager.class);
         assertNotNull(fromName);
         assertNotNull(fromClass);
         assertEquals(fromName, fromClass);
@@ -61,9 +73,9 @@
         // Verify that we can obtain a manager, and talk to the backend service, and that no
         // serial ports are configured by default
         final SerialManager service = (SerialManager)
-                mRavenwood.getContext().getSystemService(Context.SERIAL_SERVICE);
+                mContext.getSystemService(Context.SERIAL_SERVICE);
         final String[] ports = service.getSerialPorts();
-        final String[] refPorts = mRavenwood.getContext().getResources().getStringArray(
+        final String[] refPorts = mContext.getResources().getStringArray(
                 com.android.internal.R.array.config_serialPorts);
         assertArrayEquals(refPorts, ports);
     }
@@ -71,7 +83,7 @@
     @Test
     public void testDriven() {
         final SerialManager service = (SerialManager)
-                mRavenwood.getContext().getSystemService(Context.SERIAL_SERVICE);
+                mContext.getSystemService(Context.SERIAL_SERVICE);
         final SerialManagerInternal internal = LocalServices.getService(
                 SerialManagerInternal.class);
 
@@ -79,8 +91,17 @@
             throw new UnsupportedOperationException(
                     "Needs socketpair() to offer accurate emulation");
         });
-        final String[] ports = service.getSerialPorts();
-        assertEquals(1, ports.length);
-        assertEquals(TEST_VIRTUAL_PORT, ports[0]);
+        try {
+            final String[] ports = service.getSerialPorts();
+            for (var port : ports) {
+                if (TEST_VIRTUAL_PORT.equals(port)) {
+                    return; // Pass
+                }
+            }
+            fail("Virtual port " + TEST_VIRTUAL_PORT + " not found. Actual="
+                    + Lists.newArrayList(ports));
+        } finally {
+            internal.removeVirtualSerialPortForTest(TEST_VIRTUAL_PORT);
+        }
     }
 }
diff --git a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
index 70c1d78..82be2c0 100644
--- a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
@@ -365,3 +365,9 @@
 
 android.os.IpcDataCache
 android.app.PropertyInvalidatedCache
+
+android.app.compat.*
+com.android.server.compat.*
+com.android.internal.compat.*
+android.app.AppCompatCallbacks
+
diff --git a/ravenwood/texts/ravenwood-services-policies.txt b/ravenwood/texts/ravenwood-services-policies.txt
index cc2fa60..530e5c8 100644
--- a/ravenwood/texts/ravenwood-services-policies.txt
+++ b/ravenwood/texts/ravenwood-services-policies.txt
@@ -1 +1,12 @@
 # Ravenwood "policy" file for services.core.
+
+# Auto-generated from XSD
+class com.android.server.compat.config.Change keepclass
+class com.android.server.compat.config.Config keepclass
+class com.android.server.compat.config.XmlParser keepclass
+class com.android.server.compat.overrides.ChangeOverrides keepclass
+class com.android.server.compat.overrides.OverrideValue keepclass
+class com.android.server.compat.overrides.Overrides keepclass
+class com.android.server.compat.overrides.RawOverrideValue keepclass
+class com.android.server.compat.overrides.XmlParser keepclass
+class com.android.server.compat.overrides.XmlWriter keepclass
\ No newline at end of file
diff --git a/services/Android.bp b/services/Android.bp
index 899e224..fc0bb33 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -282,6 +282,7 @@
         "services.wifi",
         "service-blobstore",
         "service-jobscheduler",
+        "service-connectivity-b-pre-jarjar", // Move it to mainline module
         "android.hidl.base-V1.0-java",
     ],
 
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..ea2bc17
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/CompanionExemptionProcessor.java
@@ -0,0 +1,225 @@
+/*
+ * 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.Pair;
+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.HashSet;
+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);
+                Set<Pair<String, Integer>> exemptedPackages = new HashSet<>();
+                for (AssociationInfo a : associations) {
+                    try {
+                        int uid = pm.getPackageUidAsUser(a.getPackageName(), userId);
+                        exemptedPackages.add(new Pair<>(a.getPackageName(), uid));
+                    } catch (PackageManager.NameNotFoundException e) {
+                        Slog.w(TAG, "Unknown companion package: " + a.getPackageName(), e);
+                    }
+                }
+                for (Pair<String, Integer> exemptedPackage : exemptedPackages) {
+                    updateAutoRevokeExemption(exemptedPackage.first, exemptedPackage.second, true);
+                }
+            } 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/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 604aaa7..6729231d 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -591,7 +591,6 @@
             }
         }
 
-
         @Override // Binder call
         public int getDeviceIdForDisplayId(int displayId) {
             if (displayId == Display.INVALID_DISPLAY || displayId == Display.DEFAULT_DISPLAY) {
@@ -926,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/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
index ea6351b..fd18fa8 100644
--- a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
+++ b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
@@ -24,7 +24,8 @@
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
 import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
-import static android.content.pm.PackageManager.MATCH_FACTORY_ONLY;
+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.PERMISSION_GRANTED;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
@@ -261,24 +262,28 @@
         }
     }
 
-    private Intent getResolvedLaunchIntent() {
+    private Intent getResolvedLaunchIntent(int userId) {
         synchronized (this) {
+            if(DEBUG_USER) Log.d(TAG, "Attempting to getResolvedLaunchIntent");
             // If mTemporaryPackage is not null, use it to get the ContextualSearch intent.
             String csPkgName = getContextualSearchPackageName();
             if (csPkgName.isEmpty()) {
                 // Return null if csPackageName is not specified.
+                if (DEBUG_USER) Log.w(TAG, "getContextualSearchPackageName is empty");
                 return null;
             }
             Intent launchIntent = new Intent(
                     ContextualSearchManager.ACTION_LAUNCH_CONTEXTUAL_SEARCH);
             launchIntent.setPackage(csPkgName);
-            ResolveInfo resolveInfo = mContext.getPackageManager().resolveActivity(
-                    launchIntent, MATCH_FACTORY_ONLY);
+            ResolveInfo resolveInfo = mContext.getPackageManager().resolveActivityAsUser(
+                    launchIntent, MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, userId);
             if (resolveInfo == null) {
+                if (DEBUG_USER) Log.w(TAG, "resolveInfo is null");
                 return null;
             }
             ComponentName componentName = resolveInfo.getComponentInfo().getComponentName();
             if (componentName == null) {
+                if (DEBUG_USER) Log.w(TAG, "componentName is null");
                 return null;
             }
             launchIntent.setComponent(componentName);
@@ -286,9 +291,10 @@
         }
     }
 
-    private Intent getContextualSearchIntent(int entrypoint, CallbackToken mToken) {
-        final Intent launchIntent = getResolvedLaunchIntent();
+    private Intent getContextualSearchIntent(int entrypoint, int userId, CallbackToken mToken) {
+        final Intent launchIntent = getResolvedLaunchIntent(userId);
         if (launchIntent == null) {
+            if (DEBUG_USER) Log.w(TAG, "Failed getContextualSearchIntent: launchIntent is null");
             return null;
         }
 
@@ -341,6 +347,7 @@
                     TYPE_NAVIGATION_BAR_PANEL,
                     TYPE_POINTER));
         } else {
+            if (DEBUG_USER) Log.w(TAG, "Can't capture contextual screenshot: mWmInternal is null");
             shb = null;
         }
         final Bitmap bm = shb != null ? shb.asBitmap() : null;
@@ -444,7 +451,7 @@
         @Override
         public void startContextualSearch(int entrypoint) {
             synchronized (this) {
-                if (DEBUG_USER) Log.d(TAG, "startContextualSearch");
+                if (DEBUG_USER) Log.d(TAG, "startContextualSearch entrypoint: " + entrypoint);
                 enforcePermission("startContextualSearch");
                 final int callingUserId = Binder.getCallingUserHandle().getIdentifier();
 
@@ -455,7 +462,8 @@
                 // server has READ_FRAME_BUFFER permission to get the screenshot and because only
                 // the system server can invoke non-exported activities.
                 Binder.withCleanCallingIdentity(() -> {
-                    Intent launchIntent = getContextualSearchIntent(entrypoint, mToken);
+                    Intent launchIntent =
+                        getContextualSearchIntent(entrypoint, callingUserId, mToken);
                     if (launchIntent != null) {
                         int result = invokeContextualSearchIntent(launchIntent, callingUserId);
                         if (DEBUG_USER) Log.d(TAG, "Launch result: " + result);
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 6cfd44b..3ccad160 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -240,6 +240,7 @@
         "aconfig_new_storage_flags_lib",
         "powerstats_flags_lib",
         "locksettings_flags_lib",
+        "profiling_flags_lib",
     ],
     javac_shard_size: 50,
     javacflags: [
@@ -255,6 +256,13 @@
             "FlaggedApi",
         ],
     },
+    jarjar_rules: ":services-jarjar-rules",
+    apex_available: ["//apex_available:platform"],
+}
+
+filegroup {
+    name: "services-jarjar-rules",
+    srcs: ["services-jarjar-rules.txt"],
 }
 
 java_genrule {
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 9d27731..b7bc4e4 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -96,6 +96,7 @@
 import android.os.Message;
 import android.os.ParcelFileDescriptor;
 import android.os.ParcelableException;
+import android.os.PermissionEnforcer;
 import android.os.PersistableBundle;
 import android.os.Process;
 import android.os.RemoteCallbackList;
@@ -3653,10 +3654,16 @@
         return mInternalStorageSize;
     }
 
-    @EnforcePermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     @Override
     public int getInternalStorageRemainingLifetime() throws RemoteException {
-        super.getInternalStorageRemainingLifetime_enforcePermission();
+        PermissionEnforcer.fromContext(mContext)
+            .enforcePermissionAnyOf(
+                new String[] {
+                    android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+                    android.Manifest.permission.ALLOCATE_AGGRESSIVE
+                },
+                getCallingPid(),
+                getCallingUid());
         return mVold.getStorageRemainingLifetime();
     }
 
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 3e7bcb8..b5dcdb1 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -337,6 +337,8 @@
 import android.os.PowerManager.ServiceType;
 import android.os.PowerManagerInternal;
 import android.os.Process;
+import android.os.ProfilingServiceHelper;
+import android.os.ProfilingTrigger;
 import android.os.RemoteCallback;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
@@ -1089,7 +1091,18 @@
 
         @Override
         public void onReportFullyDrawn(long id, long timestampNanos) {
-            mProcessList.getAppStartInfoTracker().onActivityReportFullyDrawn(id, timestampNanos);
+            ApplicationStartInfo startInfo = mProcessList.getAppStartInfoTracker()
+                    .onActivityReportFullyDrawn(id, timestampNanos);
+
+            if (android.os.profiling.Flags.systemTriggeredProfilingNew()
+                    && startInfo != null
+                    && startInfo.getStartType() == ApplicationStartInfo.START_TYPE_COLD
+                    && startInfo.getPackageName() != null) {
+                ProfilingServiceHelper.getInstance().onProfilingTriggerOccurred(
+                        startInfo.getRealUid(),
+                        startInfo.getPackageName(),
+                        ProfilingTrigger.TRIGGER_TYPE_APP_COLD_START_ACTIVITY);
+            }
         }
     };
 
@@ -11337,7 +11350,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);
@@ -11345,7 +11360,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/AnrHelper.java b/services/core/java/com/android/server/am/AnrHelper.java
index 9fc0bf9..6d594ac 100644
--- a/services/core/java/com/android/server/am/AnrHelper.java
+++ b/services/core/java/com/android/server/am/AnrHelper.java
@@ -20,7 +20,8 @@
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
 
 import android.content.pm.ApplicationInfo;
-import android.os.Process;
+import android.os.ProfilingServiceHelper;
+import android.os.ProfilingTrigger;
 import android.os.SystemClock;
 import android.os.Trace;
 import android.util.ArraySet;
@@ -240,6 +241,15 @@
                         || startTime < SELF_ONLY_AFTER_BOOT_MS;
                 r.appNotResponding(onlyDumpSelf);
                 final long endTime = SystemClock.uptimeMillis();
+
+                if (android.os.profiling.Flags.systemTriggeredProfilingNew() && r.mAppInfo != null
+                        && r.mAppInfo.packageName != null) {
+                    ProfilingServiceHelper.getInstance().onProfilingTriggerOccurred(
+                            r.mUid,
+                            r.mAppInfo.packageName,
+                            ProfilingTrigger.TRIGGER_TYPE_ANR);
+                }
+
                 Slog.d(TAG, "Completed ANR of " + r.mApp.processName + " in "
                         + (endTime - startTime) + "ms, latency " + reportLatency
                         + (onlyDumpSelf ? "ms (expired, only dump ANR app)" : "ms"));
diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java
index aca6d0b..3913d2f 100644
--- a/services/core/java/com/android/server/am/AppStartInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java
@@ -22,6 +22,7 @@
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ApplicationStartInfo;
 import android.app.Flags;
 import android.app.IApplicationStartInfoCompleteListener;
@@ -419,23 +420,25 @@
      * Should only be called for Activity launch sequences from an instance of
      * {@link ActivityMetricsLaunchObserver}.
      */
-    void onActivityReportFullyDrawn(long id, long timestampNanos) {
+    @Nullable
+    ApplicationStartInfo onActivityReportFullyDrawn(long id, long timestampNanos) {
         synchronized (mLock) {
             if (!mEnabled) {
-                return;
+                return null;
             }
             int index = mInProgressRecords.indexOfKey(id);
             if (index < 0) {
-                return;
+                return null;
             }
             ApplicationStartInfo info = mInProgressRecords.valueAt(index);
             if (info == null) {
                 mInProgressRecords.removeAt(index);
-                return;
+                return null;
             }
             info.addStartupTimestamp(ApplicationStartInfo.START_TIMESTAMP_FULLY_DRAWN,
                     timestampNanos);
             mInProgressRecords.removeAt(index);
+            return info;
         }
     }
 
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 7c563ab..23092ed 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -453,7 +453,8 @@
                 com.android.internal.R.integer.config_accumulatedBatteryUsageStatsSpanSize);
         mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context,
                 mPowerAttributor, mPowerProfile, mCpuScalingPolicies,
-                mPowerStatsStore, accumulatedBatteryUsageStatsSpanSize, Clock.SYSTEM_CLOCK);
+                mPowerStatsStore, accumulatedBatteryUsageStatsSpanSize, Clock.SYSTEM_CLOCK,
+                mMonotonicClock);
         mDumpHelper = new BatteryStatsDumpHelperImpl(mBatteryUsageStatsProvider);
         mCpuWakeupStats = new CpuWakeupStats(context, R.xml.irq_device_map, mHandler);
         mConfigFile = new AtomicFile(new File(systemDir, "battery_usage_stats_config"));
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/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/InputDeviceVolumeHelper.java b/services/core/java/com/android/server/audio/InputDeviceVolumeHelper.java
index d094629..d7d1ac9 100644
--- a/services/core/java/com/android/server/audio/InputDeviceVolumeHelper.java
+++ b/services/core/java/com/android/server/audio/InputDeviceVolumeHelper.java
@@ -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;
@@ -48,7 +48,7 @@
     // 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,
@@ -60,20 +60,16 @@
 
         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();
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/compat/CompatChange.java b/services/core/java/com/android/server/compat/CompatChange.java
index a40dd79..c3d88e3 100644
--- a/services/core/java/com/android/server/compat/CompatChange.java
+++ b/services/core/java/com/android/server/compat/CompatChange.java
@@ -50,6 +50,7 @@
  *
  * <p>Note, this class is not thread safe so callers must ensure thread safety.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class CompatChange extends CompatibilityChangeInfo {
 
     /**
diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java
index 79025d0..e89f43b 100644
--- a/services/core/java/com/android/server/compat/CompatConfig.java
+++ b/services/core/java/com/android/server/compat/CompatConfig.java
@@ -42,6 +42,7 @@
 import com.android.internal.compat.CompatibilityOverridesToRemoveConfig;
 import com.android.internal.compat.IOverrideValidator;
 import com.android.internal.compat.OverrideAllowedState;
+import com.android.internal.ravenwood.RavenwoodEnvironment;
 import com.android.server.compat.config.Change;
 import com.android.server.compat.config.Config;
 import com.android.server.compat.overrides.ChangeOverrides;
@@ -63,6 +64,7 @@
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Predicate;
 
 import javax.xml.datatype.DatatypeConfigurationException;
 
@@ -72,12 +74,16 @@
  * <p>It stores the default configuration for each change, and any per-package overrides that have
  * been configured.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 final class CompatConfig {
     private static final String TAG = "CompatConfig";
     private static final String APP_COMPAT_DATA_DIR = "/data/misc/appcompat";
     private static final String STATIC_OVERRIDES_PRODUCT_DIR = "/product/etc/appcompat";
     private static final String OVERRIDES_FILE = "compat_framework_overrides.xml";
 
+    private static final String APP_COMPAT_DATA_DIR_RAVENWOOD = "/ravenwood-data/";
+    private static final String OVERRIDES_FILE_RAVENWOOD = "compat-config.xml";
+
     private final ConcurrentHashMap<Long, CompatChange> mChanges = new ConcurrentHashMap<>();
 
     private final OverrideValidatorImpl mOverrideValidator;
@@ -98,19 +104,32 @@
 
     static CompatConfig create(AndroidBuildClassifier androidBuildClassifier, Context context) {
         CompatConfig config = new CompatConfig(androidBuildClassifier, context);
-        config.initConfigFromLib(Environment.buildPath(
+        config.loadConfigFiles();
+        config.initOverrides();
+        config.invalidateCache();
+        return config;
+    }
+
+    @android.ravenwood.annotation.RavenwoodReplace
+    private void loadConfigFiles() {
+        initConfigFromLib(Environment.buildPath(
                 Environment.getRootDirectory(), "etc", "compatconfig"));
-        config.initConfigFromLib(Environment.buildPath(
+        initConfigFromLib(Environment.buildPath(
                 Environment.getRootDirectory(), "system_ext", "etc", "compatconfig"));
 
         List<ApexManager.ActiveApexInfo> apexes = ApexManager.getInstance().getActiveApexInfos();
         for (ApexManager.ActiveApexInfo apex : apexes) {
-            config.initConfigFromLib(Environment.buildPath(
+            initConfigFromLib(Environment.buildPath(
                     apex.apexDirectory, "etc", "compatconfig"));
         }
-        config.initOverrides();
-        config.invalidateCache();
-        return config;
+    }
+
+    @SuppressWarnings("unused")
+    private void loadConfigFiles$ravenwood() {
+        final var configDir = new File(
+                RavenwoodEnvironment.getInstance().getRavenwoodRuntimePath()
+                        + APP_COMPAT_DATA_DIR_RAVENWOOD);
+        initConfigFromLib(configDir, (file) -> file.getName().endsWith(OVERRIDES_FILE_RAVENWOOD));
     }
 
     /**
@@ -678,12 +697,25 @@
         return changeInfos;
     }
 
+    /**
+     * Load all config files in a given directory.
+     */
     void initConfigFromLib(File libraryDir) {
+        initConfigFromLib(libraryDir, (file) -> true);
+    }
+
+    /**
+     * Load config files in a given directory, but only the ones that match {@code includingFilter}.
+     */
+    void initConfigFromLib(File libraryDir, Predicate<File> includingFilter) {
         if (!libraryDir.exists() || !libraryDir.isDirectory()) {
             Slog.d(TAG, "No directory " + libraryDir + ", skipping");
             return;
         }
         for (File f : libraryDir.listFiles()) {
+            if (!includingFilter.test(f)) {
+                continue;
+            }
             Slog.d(TAG, "Found a config file: " + f.getPath());
             //TODO(b/138222363): Handle duplicate ids across config files.
             readConfig(f);
diff --git a/services/core/java/com/android/server/compat/OverrideValidatorImpl.java b/services/core/java/com/android/server/compat/OverrideValidatorImpl.java
index e3b6d03..362c697 100644
--- a/services/core/java/com/android/server/compat/OverrideValidatorImpl.java
+++ b/services/core/java/com/android/server/compat/OverrideValidatorImpl.java
@@ -45,6 +45,7 @@
 /**
  * Implementation of the policy for allowing compat change overrides.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class OverrideValidatorImpl extends IOverrideValidator.Stub {
 
     private AndroidBuildClassifier mAndroidBuildClassifier;
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index 8d64383..97f4a5c 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -36,6 +36,7 @@
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
+import android.os.PermissionEnforcer;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -65,6 +66,7 @@
 /**
  * System server internal API for gating and reporting compatibility changes.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class PlatformCompat extends IPlatformCompat.Stub {
 
     private static final String TAG = "Compatibility";
@@ -75,6 +77,7 @@
     private final AndroidBuildClassifier mBuildClassifier;
 
     public PlatformCompat(Context context) {
+        super(PermissionEnforcer.fromContext(context));
         mContext = context;
         mChangeReporter = new ChangeReporter(ChangeReporter.SOURCE_SYSTEM_SERVER);
         mBuildClassifier = new AndroidBuildClassifier();
@@ -85,6 +88,7 @@
     PlatformCompat(Context context, CompatConfig compatConfig,
             AndroidBuildClassifier buildClassifier,
             ChangeReporter changeReporter) {
+        super(PermissionEnforcer.fromContext(context));
         mContext = context;
         mChangeReporter = changeReporter;
         mCompatConfig = compatConfig;
@@ -515,6 +519,7 @@
         return appInfo;
     }
 
+    @android.ravenwood.annotation.RavenwoodReplace
     private void killPackage(String packageName) {
         int uid = LocalServices.getService(PackageManagerInternal.class).getPackageUid(packageName,
                 0, UserHandle.myUserId());
@@ -528,6 +533,13 @@
         killUid(UserHandle.getAppId(uid));
     }
 
+    @SuppressWarnings("unused")
+    private void killPackage$ravenwood(String packageName) {
+        // TODO Maybe crash if the package is the self.
+        Slog.w(TAG, "killPackage() is ignored on Ravenwood: packageName=" + packageName);
+    }
+
+    @android.ravenwood.annotation.RavenwoodReplace
     private void killUid(int appId) {
         final long identity = Binder.clearCallingIdentity();
         try {
@@ -542,6 +554,12 @@
         }
     }
 
+    @SuppressWarnings("unused")
+    private void killUid$ravenwood(int appId) {
+        // TODO Maybe crash if the UID is the self.
+        Slog.w(TAG, "killUid() is ignored on Ravenwood: appId=" + appId);
+    }
+
     private void checkAllCompatOverridesAreOverridable(Collection<Long> changeIds) {
         for (Long changeId : changeIds) {
             if (isKnownChangeId(changeId) && !mCompatConfig.isOverridable(changeId)) {
diff --git a/services/core/java/com/android/server/compat/PlatformCompatNative.java b/services/core/java/com/android/server/compat/PlatformCompatNative.java
index 5d7af65..7a3feb5 100644
--- a/services/core/java/com/android/server/compat/PlatformCompatNative.java
+++ b/services/core/java/com/android/server/compat/PlatformCompatNative.java
@@ -23,6 +23,7 @@
 /**
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class PlatformCompatNative extends IPlatformCompatNative.Stub {
     private final PlatformCompat mPlatformCompat;
 
diff --git a/services/core/java/com/android/server/compat/overrides/AppCompatOverridesParser.java b/services/core/java/com/android/server/compat/overrides/AppCompatOverridesParser.java
index e8762a3..0ec6879 100644
--- a/services/core/java/com/android/server/compat/overrides/AppCompatOverridesParser.java
+++ b/services/core/java/com/android/server/compat/overrides/AppCompatOverridesParser.java
@@ -46,6 +46,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 final class AppCompatOverridesParser {
     /**
      * Flag for specifying all compat change IDs owned by a namespace. See {@link
diff --git a/services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java b/services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java
index fe002ce..8637d2d 100644
--- a/services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java
+++ b/services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java
@@ -68,6 +68,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class AppCompatOverridesService {
     private static final String TAG = "AppCompatOverridesService";
 
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 76e5ef0..794eb87 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -64,12 +64,15 @@
 import android.service.dreams.DreamManagerInternal;
 import android.service.dreams.DreamService;
 import android.service.dreams.IDreamManager;
+import android.text.TextUtils;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.view.Display;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.content.PackageMonitor;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.UiEventLoggerImpl;
 import com.android.internal.util.DumpUtils;
@@ -86,6 +89,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.CopyOnWriteArrayList;
@@ -155,6 +159,10 @@
     private ComponentName mDreamOverlayServiceName;
 
     private final AmbientDisplayConfiguration mDozeConfig;
+
+    /** Stores {@link PerUserPackageMonitor} to monitor dream uninstalls. */
+    private final SparseArray<PackageMonitor> mPackageMonitors = new SparseArray<>();
+
     private final ActivityInterceptorCallback mActivityInterceptorCallback =
             new ActivityInterceptorCallback() {
                 @Nullable
@@ -218,6 +226,15 @@
         }
     }
 
+    private final class PerUserPackageMonitor extends PackageMonitor {
+        @Override
+        public void onPackageRemoved(String packageName, int uid) {
+            super.onPackageRemoved(packageName, uid);
+            final int userId = getChangingUserId();
+            updateDreamOnPackageRemoved(packageName, userId);
+        }
+    }
+
     public DreamManagerService(Context context) {
         this(context, new DreamHandler(FgThread.get().getLooper()));
     }
@@ -333,6 +350,33 @@
         });
     }
 
+    @Override
+    public void onUserStarting(@NonNull TargetUser user) {
+        super.onUserStarting(user);
+        mHandler.post(() -> {
+            final int userId = user.getUserIdentifier();
+            if (!mPackageMonitors.contains(userId)) {
+                final PackageMonitor monitor = new PerUserPackageMonitor();
+                monitor.register(mContext, UserHandle.of(userId), mHandler);
+                mPackageMonitors.put(userId, monitor);
+            } else {
+                Slog.w(TAG, "Package monitor already registered for " + userId);
+            }
+        });
+    }
+
+    @Override
+    public void onUserStopping(@NonNull TargetUser user) {
+        super.onUserStopping(user);
+        mHandler.post(() -> {
+            final PackageMonitor monitor = mPackageMonitors.removeReturnOld(
+                    user.getUserIdentifier());
+            if (monitor != null) {
+                monitor.unregister();
+            }
+        });
+    }
+
     private void dumpInternal(PrintWriter pw) {
         synchronized (mLock) {
             pw.println("DREAM MANAGER (dumpsys dreams)");
@@ -664,6 +708,22 @@
         return validComponents.toArray(new ComponentName[validComponents.size()]);
     }
 
+    private void updateDreamOnPackageRemoved(String packageName, int userId) {
+        final ComponentName[] componentNames = componentsFromString(
+                Settings.Secure.getStringForUser(mContext.getContentResolver(),
+                        Settings.Secure.SCREENSAVER_COMPONENTS,
+                        userId));
+        if (componentNames != null) {
+            // Filter out any components in the removed package.
+            final ComponentName[] filteredComponents = Arrays.stream(componentNames).filter(
+                    (componentName -> !TextUtils.equals(componentName.getPackageName(),
+                            packageName))).toArray(ComponentName[]::new);
+            if (filteredComponents.length != componentNames.length) {
+                setDreamComponentsForUser(userId, filteredComponents);
+            }
+        }
+    }
+
     private void setDreamComponentsForUser(int userId, ComponentName[] componentNames) {
         Settings.Secure.putStringForUser(mContext.getContentResolver(),
                 Settings.Secure.SCREENSAVER_COMPONENTS,
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..8cb51ce 100644
--- a/services/core/java/com/android/server/input/InputGestureManager.java
+++ b/services/core/java/com/android/server/input/InputGestureManager.java
@@ -16,11 +16,23 @@
 
 package com.android.server.input;
 
+import static android.hardware.input.InputGestureData.createKeyTrigger;
+
+import static com.android.hardware.input.Flags.enableTalkbackAndMagnifierKeyGestures;
+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 +41,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 +61,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 (enableTalkbackAndMagnifierKeyGestures()) {
+            systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_T,
+                    KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+                    KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK));
+        }
+        if (keyboardA11yShortcutControl()) {
+            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 +312,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 +331,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 +352,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 +363,60 @@
         }
     }
 
+    @Nullable
+    public InputGestureData getCustomGestureForTouchpadGesture(@UserIdInt int userId,
+            int touchpadGestureType) {
+        if (touchpadGestureType == InputGestureData.TOUCHPAD_GESTURE_TYPE_UNKNOWN) {
+            return null;
+        }
+        synchronized (mGestureLock) {
+            Map<InputGestureData.Trigger, InputGestureData> customGestures =
+                    mCustomInputGestures.get(userId);
+            if (customGestures == null) {
+                return null;
+            }
+            return customGestures.get(InputGestureData.createTouchpadTrigger(touchpadGestureType));
+        }
+    }
+
+    @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 eefa15e..e0f3a9b 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -64,6 +64,7 @@
 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;
@@ -129,6 +130,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,7 +2315,8 @@
     // Native callback.
     @SuppressWarnings("unused")
     private void notifyTouchpadThreeFingerTap() {
-        mKeyGestureController.handleTouchpadThreeFingerTap();
+        mKeyGestureController.handleTouchpadGesture(
+                InputGestureData.TOUCHPAD_GESTURE_TYPE_THREE_FINGER_TAP);
     }
 
     // Native callback.
@@ -2994,35 +2997,40 @@
 
     @Override
     @PermissionManuallyEnforced
-    public int addCustomInputGesture(@NonNull AidlInputGestureData inputGestureData) {
+    public int addCustomInputGesture(@UserIdInt int userId,
+            @NonNull AidlInputGestureData inputGestureData) {
         enforceManageKeyGesturePermission();
 
         Objects.requireNonNull(inputGestureData);
-        return mKeyGestureController.addCustomInputGesture(UserHandle.getCallingUserId(),
-                inputGestureData);
+        return mKeyGestureController.addCustomInputGesture(userId, inputGestureData);
     }
 
     @Override
     @PermissionManuallyEnforced
-    public int removeCustomInputGesture(@NonNull AidlInputGestureData inputGestureData) {
+    public int removeCustomInputGesture(@UserIdInt int userId,
+            @NonNull AidlInputGestureData inputGestureData) {
         enforceManageKeyGesturePermission();
 
         Objects.requireNonNull(inputGestureData);
-        return mKeyGestureController.removeCustomInputGesture(UserHandle.getCallingUserId(),
-                inputGestureData);
+        return mKeyGestureController.removeCustomInputGesture(userId, inputGestureData);
     }
 
     @Override
     @PermissionManuallyEnforced
-    public void removeAllCustomInputGestures() {
+    public void removeAllCustomInputGestures(@UserIdInt int userId) {
         enforceManageKeyGesturePermission();
 
-        mKeyGestureController.removeAllCustomInputGestures(UserHandle.getCallingUserId());
+        mKeyGestureController.removeAllCustomInputGestures(userId);
     }
 
     @Override
-    public AidlInputGestureData[] getCustomInputGestures() {
-        return mKeyGestureController.getCustomInputGestures(UserHandle.getCallingUserId());
+    public AidlInputGestureData[] getCustomInputGestures(@UserIdInt int userId) {
+        return mKeyGestureController.getCustomInputGestures(userId);
+    }
+
+    @Override
+    public AidlInputGestureData[] getAppLaunchBookmarks() {
+        return mKeyGestureController.getAppLaunchBookmarks();
     }
 
     private void handleCurrentUserChanged(@UserIdInt int userId) {
@@ -3569,6 +3577,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/KeyGestureController.java b/services/core/java/com/android/server/input/KeyGestureController.java
index 96ef070..fc10640 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) {
@@ -1084,6 +847,13 @@
                 /* appLaunchData = */null);
     }
 
+    private void handleTouchpadGesture(@KeyGestureEvent.KeyGestureType int keyGestureType,
+            @Nullable AppLaunchData appLaunchData) {
+        handleKeyGesture(KeyCharacterMap.VIRTUAL_KEYBOARD, new int[0], /* modifierState= */0,
+                keyGestureType, KeyGestureEvent.ACTION_GESTURE_COMPLETE,
+                Display.DEFAULT_DISPLAY, /* focusedToken = */null, /* flags = */0, appLaunchData);
+    }
+
     @VisibleForTesting
     boolean handleKeyGesture(int deviceId, int[] keycodes, int modifierState,
             @KeyGestureEvent.KeyGestureType int gestureType, int action, int displayId,
@@ -1134,11 +904,18 @@
         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");
+    public void handleTouchpadGesture(int touchpadGestureType) {
+        // Handle custom shortcuts
+        InputGestureData customGesture;
+        synchronized (mUserLock) {
+            customGesture = mInputGestureManager.getCustomGestureForTouchpadGesture(mCurrentUserId,
+                    touchpadGestureType);
         }
+        if (customGesture == null) {
+            return;
+        }
+        handleTouchpadGesture(customGesture.getAction().keyGestureType(),
+                customGesture.getAction().appLaunchData());
     }
 
     @MainThread
@@ -1258,6 +1035,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);
@@ -1329,6 +1116,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);
@@ -1432,6 +1228,7 @@
     public void dump(IndentingPrintWriter ipw) {
         ipw.println("KeyGestureController:");
         ipw.increaseIndent();
+        ipw.println("mCurrentUserId = " + mCurrentUserId);
         ipw.println("mSystemPid = " + mSystemPid);
         ipw.println("mPendingMetaAction = " + mPendingMetaAction);
         ipw.println("mPendingCapsLockToggle = " + mPendingCapsLockToggle);
@@ -1471,6 +1268,7 @@
         }
         ipw.decreaseIndent();
         mKeyCombinationManager.dump("", ipw);
+        mAppLaunchShortcutManager.dump(ipw);
         mInputGestureManager.dump(ipw);
     }
 }
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/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/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/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java
index 6681e36..5febd5c 100644
--- a/services/core/java/com/android/server/notification/GroupHelper.java
+++ b/services/core/java/com/android/server/notification/GroupHelper.java
@@ -25,8 +25,10 @@
 import static android.app.Notification.VISIBILITY_PRIVATE;
 import static android.app.Notification.VISIBILITY_PUBLIC;
 import static android.service.notification.Flags.notificationForceGrouping;
+import static android.service.notification.Flags.notificationRegroupOnClassification;
 
 import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -49,6 +51,9 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashSet;
@@ -83,10 +88,22 @@
     //  with less than this value, they will be forced grouped
     private static final int MIN_CHILD_COUNT_TO_AVOID_FORCE_GROUPING = 3;
 
+    // Regrouping needed because the channel was updated, ie. importance changed
+    static final int REGROUP_REASON_CHANNEL_UPDATE = 0;
+    // Regrouping needed because of notification bundling
+    static final int REGROUP_REASON_BUNDLE = 1;
+
+    @IntDef(prefix = { "REGROUP_REASON_" }, value = {
+        REGROUP_REASON_CHANNEL_UPDATE,
+        REGROUP_REASON_BUNDLE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface RegroupingReason {}
 
     private final Callback mCallback;
     private final int mAutoGroupAtCount;
     private final int mAutogroupSparseGroupsAtCount;
+    private final int mAutoGroupRegroupingAtCount;
     private final Context mContext;
     private final PackageManager mPackageManager;
     private boolean mIsTestHarnessExempted;
@@ -173,6 +190,11 @@
         mContext = context;
         mPackageManager = packageManager;
         mAutogroupSparseGroupsAtCount = autoGroupSparseGroupsAtCount;
+        if (notificationRegroupOnClassification()) {
+            mAutoGroupRegroupingAtCount = 1;
+        } else {
+            mAutoGroupRegroupingAtCount = mAutoGroupAtCount;
+        }
         NOTIFICATION_SHADE_SECTIONS = getNotificationShadeSections();
     }
 
@@ -865,7 +887,8 @@
                 }
             }
 
-            regroupNotifications(userId, pkgName, notificationsToCheck);
+            regroupNotifications(userId, pkgName, notificationsToCheck,
+                    REGROUP_REASON_CHANNEL_UPDATE);
         }
     }
 
@@ -883,13 +906,14 @@
             ArrayMap<String, NotificationRecord> notificationsToCheck = new ArrayMap<>();
             notificationsToCheck.put(record.getKey(), record);
             regroupNotifications(record.getUserId(), record.getSbn().getPackageName(),
-                    notificationsToCheck);
+                    notificationsToCheck, REGROUP_REASON_BUNDLE);
         }
     }
 
     @GuardedBy("mAggregatedNotifications")
     private void regroupNotifications(int userId, String pkgName,
-            ArrayMap<String, NotificationRecord> notificationsToCheck) {
+            ArrayMap<String, NotificationRecord> notificationsToCheck,
+            @RegroupingReason int regroupingReason) {
         // The list of notification operations required after the channel update
         final ArrayList<NotificationMoveOp> notificationsToMove = new ArrayList<>();
 
@@ -904,12 +928,42 @@
         notificationsToMove.addAll(
                 getUngroupedNotificationsMoveOps(userId, pkgName, notificationsToCheck));
 
+        // Handle "grouped correctly" notifications that were re-classified (bundled)
+        if (notificationRegroupOnClassification()) {
+            if (regroupingReason == REGROUP_REASON_BUNDLE) {
+                notificationsToMove.addAll(
+                        getReclassifiedNotificationsMoveOps(userId, pkgName, notificationsToCheck));
+            }
+        }
+
         // Batch move to new section
         if (!notificationsToMove.isEmpty()) {
             moveNotificationsToNewSection(userId, pkgName, notificationsToMove);
         }
     }
 
+    private List<NotificationMoveOp> getReclassifiedNotificationsMoveOps(int userId,
+                String pkgName, ArrayMap<String, NotificationRecord> notificationsToCheck) {
+        final ArrayList<NotificationMoveOp> notificationsToMove = new ArrayList<>();
+        for (NotificationRecord record : notificationsToCheck.values()) {
+            if (isChildOfValidAppGroup(record)) {
+                // Check if section changes
+                NotificationSectioner sectioner = getSection(record);
+                if (sectioner != null) {
+                    FullyQualifiedGroupKey newFullAggregateGroupKey =
+                            new FullyQualifiedGroupKey(userId, pkgName, sectioner);
+                    if (DEBUG) {
+                        Slog.v(TAG, "Regroup after classification: " + record + " to: "
+                                + newFullAggregateGroupKey);
+                    }
+                    notificationsToMove.add(
+                            new NotificationMoveOp(record, null, newFullAggregateGroupKey));
+                }
+            }
+        }
+        return notificationsToMove;
+    }
+
     @GuardedBy("mAggregatedNotifications")
     private List<NotificationMoveOp> getAutogroupedNotificationsMoveOps(int userId, String pkgName,
             ArrayMap<String, NotificationRecord> notificationsToCheck) {
@@ -1010,6 +1064,10 @@
         // Bundled operations to apply to groups affected by the channel update
         ArrayMap<FullyQualifiedGroupKey, GroupUpdateOp> groupsToUpdate = new ArrayMap<>();
 
+        // App-provided (valid) groups of notifications that were classified (bundled).
+        // Summaries will be canceled if all child notifications have been bundled.
+        ArrayMap<String, String> originalGroupsOfBundledNotifications = new ArrayMap<>();
+
         for (NotificationMoveOp moveOp: notificationsToMove) {
             final NotificationRecord record = moveOp.record;
             final FullyQualifiedGroupKey oldFullAggregateGroupKey = moveOp.oldGroup;
@@ -1035,6 +1093,13 @@
                     groupsToUpdate.put(oldFullAggregateGroupKey,
                         new GroupUpdateOp(oldFullAggregateGroupKey, record, true));
                 }
+            } else {
+                if (notificationRegroupOnClassification()) {
+                    // Null "old aggregate group" => this notification was re-classified from
+                    // a valid app-provided group => maybe cancel the original summary
+                    // if no children are left
+                    originalGroupsOfBundledNotifications.put(record.getKey(), record.getGroupKey());
+                }
             }
 
             // Add moved notifications to the ungrouped list for new group and do grouping
@@ -1076,7 +1141,7 @@
             NotificationRecord triggeringNotification = groupsToUpdate.get(groupKey).record;
             boolean hasSummary = groupsToUpdate.get(groupKey).hasSummary;
             //Group needs to be created/updated
-            if (ungrouped.size() >= mAutoGroupAtCount
+            if (ungrouped.size() >= mAutoGroupRegroupingAtCount
                     || (hasSummary && !aggregatedNotificationsAttrs.isEmpty())) {
                 NotificationSectioner sectioner = getSection(triggeringNotification);
                 if (sectioner == null) {
@@ -1092,6 +1157,18 @@
                 }
             }
         }
+
+        if (notificationRegroupOnClassification()) {
+            // Cancel the summary if it's the last notification of the original app-provided group
+            for (String triggeringKey : originalGroupsOfBundledNotifications.keySet()) {
+                NotificationRecord canceledSummary =
+                        mCallback.removeAppProvidedSummaryOnClassification(triggeringKey,
+                        originalGroupsOfBundledNotifications.getOrDefault(triggeringKey, null));
+                if (canceledSummary != null) {
+                    cacheCanceledSummary(canceledSummary);
+                }
+            }
+        }
     }
 
     static String getFullAggregateGroupKey(String pkgName,
@@ -1113,6 +1190,42 @@
         return (record.mOriginalFlags & Notification.FLAG_AUTOGROUP_SUMMARY) != 0;
     }
 
+    private boolean isNotificationAggregatedInSection(NotificationRecord record,
+            NotificationSectioner sectioner) {
+        final FullyQualifiedGroupKey fullAggregateGroupKey = new FullyQualifiedGroupKey(
+                record.getUserId(), record.getSbn().getPackageName(), sectioner);
+        return record.getGroupKey().equals(fullAggregateGroupKey.toString());
+    }
+
+    private boolean isChildOfValidAppGroup(NotificationRecord record) {
+        final StatusBarNotification sbn = record.getSbn();
+        if (!sbn.isAppGroup()) {
+            return false;
+        }
+
+        if (!sbn.getNotification().isGroupChild()) {
+            return false;
+        }
+
+        if (record.isCanceled) {
+            return false;
+        }
+
+        final NotificationSectioner sectioner = getSection(record);
+        if (sectioner == null) {
+            if (DEBUG) {
+                Slog.i(TAG, "Skipping autogrouping for " + record + " no valid section found.");
+            }
+            return false;
+        }
+
+        if (isNotificationAggregatedInSection(record, sectioner)) {
+            return false;
+        }
+
+        return true;
+    }
+
     private static int getNumChildrenForGroup(@NonNull final String groupKey,
             final List<NotificationRecord> notificationList) {
         //TODO (b/349072751): track grouping state in GroupHelper -> do not use notificationList
@@ -1438,6 +1551,48 @@
         }
     }
 
+    protected void dump(PrintWriter pw, String prefix) {
+        synchronized (mAggregatedNotifications) {
+            if (!mUngroupedAbuseNotifications.isEmpty()) {
+                pw.println(prefix + "Ungrouped notifications:");
+                for (FullyQualifiedGroupKey groupKey: mUngroupedAbuseNotifications.keySet()) {
+                    if (!mUngroupedAbuseNotifications.getOrDefault(groupKey, new ArrayMap<>())
+                            .isEmpty()) {
+                        pw.println(prefix + prefix + groupKey.toString());
+                        for (String notifKey : mUngroupedAbuseNotifications.get(groupKey)
+                                .keySet()) {
+                            pw.println(prefix + prefix + prefix + notifKey);
+                        }
+                    }
+                }
+                pw.println("");
+            }
+
+            if (!mAggregatedNotifications.isEmpty()) {
+                pw.println(prefix + "Autogrouped notifications:");
+                for (FullyQualifiedGroupKey groupKey: mAggregatedNotifications.keySet()) {
+                    if (!mAggregatedNotifications.getOrDefault(groupKey, new ArrayMap<>())
+                            .isEmpty()) {
+                        pw.println(prefix + prefix + groupKey.toString());
+                        for (String notifKey : mAggregatedNotifications.get(groupKey).keySet()) {
+                            pw.println(prefix + prefix + prefix + notifKey);
+                        }
+                    }
+                }
+                pw.println("");
+            }
+
+            if (!mCanceledSummaries.isEmpty()) {
+                pw.println(prefix + "Cached canceled summaries:");
+                for (CachedSummary summary: mCanceledSummaries.values()) {
+                    pw.println(prefix + prefix + prefix + summary.key + " -> "
+                            + summary.originalGroupKey);
+                }
+                pw.println("");
+            }
+        }
+    }
+
     protected static class NotificationSectioner {
         final String mName;
         final int mSummaryId;
@@ -1551,5 +1706,16 @@
 
         void removeNotificationFromCanceledGroup(int userId, String pkg, String groupKey,
                 int cancelReason);
+
+        /**
+         * Cancels the group summary of a notification that was regrouped because of classification
+         *  (bundling). Only cancels if the summary is the last notification of the original group.
+         * @param triggeringKey the triggering child notification key
+         * @param groupKey the original group key
+         * @return the canceled group summary or null if the summary was not canceled
+         */
+        @Nullable
+        NotificationRecord removeAppProvidedSummaryOnClassification(String triggeringKey,
+                @Nullable String groupKey);
     }
 }
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..4d0c7ec 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;
@@ -474,6 +474,10 @@
             Adjustment.KEY_TYPE
     };
 
+    static final Integer[] DEFAULT_ALLOWED_ADJUSTMENT_KEY_TYPES = new Integer[] {
+            TYPE_PROMOTION
+    };
+
     static final String[] NON_BLOCKABLE_DEFAULT_ROLES = new String[] {
             RoleManager.ROLE_DIALER,
             RoleManager.ROLE_EMERGENCY
@@ -1929,6 +1933,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) {
@@ -2998,6 +3008,16 @@
                             groupKey, REASON_APP_CANCEL, SystemClock.elapsedRealtime());
                 }
             }
+
+            @Override
+            @Nullable
+            public NotificationRecord removeAppProvidedSummaryOnClassification(String triggeringKey,
+                    @Nullable String oldGroupKey) {
+                synchronized (mNotificationLock) {
+                    return removeAppProvidedSummaryOnClassificationLocked(triggeringKey,
+                            oldGroupKey);
+                }
+            }
         });
     }
 
@@ -4189,6 +4209,22 @@
         }
 
         @Override
+        @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+        public @NonNull int[] getAllowedAdjustmentKeyTypes() {
+            checkCallerIsSystemOrSystemUiOrShell();
+            return mAssistants.getAllowedAdjustmentKeyTypes();
+        }
+
+        @Override
+        @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+        public void setAssistantAdjustmentKeyTypeState(int type, boolean enabled) {
+            checkCallerIsSystemOrSystemUiOrShell();
+            mAssistants.setAssistantAdjustmentKeyTypeState(type, enabled);
+
+            handleSavePolicyFile();
+        }
+
+        @Override
         @FlaggedApi(android.app.Flags.FLAG_API_RICH_ONGOING)
         public boolean appCanBePromoted(String pkg, int uid) {
             checkCallerIsSystemOrSystemUiOrShell();
@@ -6977,19 +7013,30 @@
                 if (!mAssistants.isAdjustmentAllowed(potentialKey)) {
                     toRemove.add(potentialKey);
                 }
+                if (notificationClassification() && adjustments.containsKey(KEY_TYPE)) {
+                    if (!mAssistants.isAdjustmentKeyTypeAllowed(adjustments.getInt(KEY_TYPE))) {
+                        toRemove.add(potentialKey);
+                    }
+                }
             }
             for (String removeKey : toRemove) {
                 adjustments.remove(removeKey);
             }
-            if (android.service.notification.Flags.notificationClassification()
-                    && adjustments.containsKey(KEY_TYPE)) {
+            if (notificationClassification() && adjustments.containsKey(KEY_TYPE)) {
                 final NotificationChannel newChannel = getClassificationChannelLocked(r,
                         adjustments);
                 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);
@@ -7114,6 +7161,50 @@
     }
 
     @GuardedBy("mNotificationLock")
+    @Nullable
+    NotificationRecord removeAppProvidedSummaryOnClassificationLocked(String triggeringKey,
+            @Nullable String oldGroupKey) {
+        NotificationRecord canceledSummary = null;
+        NotificationRecord r = mNotificationsByKey.get(triggeringKey);
+        if (r == null || oldGroupKey == null) {
+            return null;
+        }
+
+        if (r.getSbn().isAppGroup() && r.getNotification().isGroupChild()) {
+            NotificationRecord groupSummary = mSummaryByGroupKey.get(oldGroupKey);
+            // We only care about app-provided valid groups
+            if (groupSummary != null && !GroupHelper.isAggregatedGroup(groupSummary)) {
+                List<NotificationRecord> notificationsInGroup =
+                        findGroupNotificationsLocked(r.getSbn().getPackageName(),
+                            oldGroupKey, r.getUserId());
+                // Remove the app-provided summary if only the summary is left in the
+                // original group, or summary + triggering notification that will be
+                // regrouped
+                boolean isOnlySummaryLeft =
+                        (notificationsInGroup.size() <= 1)
+                            || (notificationsInGroup.size() == 2
+                            && notificationsInGroup.contains(r)
+                            && notificationsInGroup.contains(groupSummary));
+                if (isOnlySummaryLeft) {
+                    if (DBG) {
+                        Slog.i(TAG, "Removing app summary (all children bundled): "
+                                + groupSummary);
+                    }
+                    canceledSummary = groupSummary;
+                    mSummaryByGroupKey.remove(oldGroupKey);
+                    cancelNotification(Binder.getCallingUid(), Binder.getCallingPid(),
+                            groupSummary.getSbn().getPackageName(),
+                            groupSummary.getSbn().getTag(),
+                            groupSummary.getSbn().getId(), 0, 0, false, groupSummary.getUserId(),
+                            NotificationListenerService.REASON_GROUP_OPTIMIZATION, null);
+                }
+            }
+        }
+
+        return canceledSummary;
+    }
+
+    @GuardedBy("mNotificationLock")
     private boolean hasAutoGroupSummaryLocked(NotificationRecord record) {
         final String autbundledGroupKey;
         if (notificationForceGrouping()) {
@@ -7493,6 +7584,11 @@
                     mTtlHelper.dump(pw, "    ");
                 }
             }
+
+            if (notificationForceGrouping()) {
+                pw.println("\n  GroupHelper:");
+                mGroupHelper.dump(pw, "    ");
+            }
         }
     }
 
@@ -7770,10 +7866,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 +8109,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())
@@ -11552,11 +11648,15 @@
 
         private static final String ATT_TYPES = "types";
         private static final String ATT_DENIED = "denied_adjustments";
+        private static final String ATT_ENABLED_TYPES = "enabled_key_types";
         private static final String ATT_NAS_UNSUPPORTED = "unsupported_adjustments";
 
         private final Object mLock = new Object();
 
         @GuardedBy("mLock")
+        private Set<Integer> mAllowedAdjustmentKeyTypes = new ArraySet<>();
+
+        @GuardedBy("mLock")
         private Set<String> mAllowedAdjustments = new ArraySet<>();
 
         @GuardedBy("mLock")
@@ -11639,6 +11739,8 @@
                 for (int i = 0; i < DEFAULT_ALLOWED_ADJUSTMENTS.length; i++) {
                     mAllowedAdjustments.add(DEFAULT_ALLOWED_ADJUSTMENTS[i]);
                 }
+            } else {
+                mAllowedAdjustmentKeyTypes.addAll(List.of(DEFAULT_ALLOWED_ADJUSTMENT_KEY_TYPES));
             }
         }
 
@@ -11726,6 +11828,42 @@
             }
         }
 
+        @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+        protected @NonNull boolean isAdjustmentKeyTypeAllowed(@Adjustment.Types int type) {
+            synchronized (mLock) {
+                if (notificationClassification()) {
+                    return mAllowedAdjustmentKeyTypes.contains(type);
+                }
+            }
+            return false;
+        }
+
+        @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+        protected @NonNull int[] getAllowedAdjustmentKeyTypes() {
+            synchronized (mLock) {
+                if (notificationClassification()) {
+                    return mAllowedAdjustmentKeyTypes.stream()
+                            .mapToInt(Integer::intValue).toArray();
+                }
+            }
+            return new int[]{};
+        }
+
+        @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+        public void setAssistantAdjustmentKeyTypeState(@Adjustment.Types int type,
+                boolean enabled) {
+            if (!android.service.notification.Flags.notificationClassification()) {
+                return;
+            }
+            synchronized (mLock) {
+                if (enabled) {
+                    mAllowedAdjustmentKeyTypes.add(type);
+                } else {
+                    mAllowedAdjustmentKeyTypes.remove(type);
+                }
+            }
+        }
+
         protected void onNotificationsSeenLocked(ArrayList<NotificationRecord> records) {
             for (final ManagedServiceInfo info : NotificationAssistants.this.getServices()) {
                 ArrayList<String> keys = new ArrayList<>(records.size());
@@ -12165,27 +12303,46 @@
 
         @Override
         protected void writeExtraXmlTags(TypedXmlSerializer out) throws IOException {
-            if (!android.service.notification.Flags.notificationClassification()) {
+            if (!notificationClassification()) {
                 return;
             }
             synchronized (mLock) {
                 out.startTag(null, ATT_DENIED);
                 out.attribute(null, ATT_TYPES, TextUtils.join(",", mDeniedAdjustments));
                 out.endTag(null, ATT_DENIED);
+                out.startTag(null, ATT_ENABLED_TYPES);
+                out.attribute(null, ATT_TYPES,
+                        TextUtils.join(",", mAllowedAdjustmentKeyTypes));
+                out.endTag(null, ATT_ENABLED_TYPES);
             }
         }
 
         @Override
         protected void readExtraTag(String tag, TypedXmlPullParser parser) throws IOException {
-            if (!android.service.notification.Flags.notificationClassification()) {
+            if (!notificationClassification()) {
                 return;
             }
             if (ATT_DENIED.equals(tag)) {
-                final String types = XmlUtils.readStringAttribute(parser, ATT_TYPES);
+                final String keys = XmlUtils.readStringAttribute(parser, ATT_TYPES);
                 synchronized (mLock) {
                     mDeniedAdjustments.clear();
+                    if (!TextUtils.isEmpty(keys)) {
+                        mDeniedAdjustments.addAll(Arrays.asList(keys.split(",")));
+                    }
+                }
+            } else if (ATT_ENABLED_TYPES.equals(tag)) {
+                final String types = XmlUtils.readStringAttribute(parser, ATT_TYPES);
+                synchronized (mLock) {
+                    mAllowedAdjustmentKeyTypes.clear();
                     if (!TextUtils.isEmpty(types)) {
-                        mDeniedAdjustments.addAll(Arrays.asList(types.split(",")));
+                        List<String> typeList = Arrays.asList(types.split(","));
+                        for (String type : typeList) {
+                            try {
+                                mAllowedAdjustmentKeyTypes.add(Integer.parseInt(type));
+                            } catch (NumberFormatException e) {
+                                Slog.wtf(TAG, "Bad type specified", e);
+                            }
+                        }
                     }
                 }
             }
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index d5f13a8..cfeacdf 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -2422,7 +2422,7 @@
                 || (mSuppressedEffects & SUPPRESSED_EFFECT_NOTIFICATIONS) != 0;
         // call restrictions
         final boolean muteCalls = zenAlarmsOnly
-                || (zenPriorityOnly && !(allowCalls || allowRepeatCallers))
+                || (zenPriorityOnly && (!allowCalls || !allowRepeatCallers))
                 || (mSuppressedEffects & SUPPRESSED_EFFECT_CALLS) != 0;
         // alarm restrictions
         final boolean muteAlarms = zenPriorityOnly && !allowAlarms;
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/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 5653da0..2c09423 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -88,6 +88,7 @@
 import android.content.pm.ShortcutServiceInternal.ShortcutChangeListener;
 import android.content.pm.UserInfo;
 import android.content.pm.UserProperties;
+import android.database.ContentObserver;
 import android.graphics.Rect;
 import android.multiuser.Flags;
 import android.net.Uri;
@@ -95,6 +96,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IInterface;
+import android.os.Looper;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
 import android.os.RemoteCallbackList;
@@ -249,6 +251,7 @@
         private PackageInstallerService mPackageInstallerService;
 
         final LauncherAppsServiceInternal mInternal;
+        private SecureSettingsObserver mSecureSettingsObserver;
 
         @NonNull
         private final RemoteCallbackList<IDumpCallback> mDumpCallbacks =
@@ -278,6 +281,7 @@
             mCallbackHandler = BackgroundThread.getHandler();
             mDpm = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
             mInternal = new LocalService();
+            registerSettingsObserver();
         }
 
         @VisibleForTesting
@@ -2312,6 +2316,13 @@
             }
         }
 
+        void registerSettingsObserver() {
+            if (Flags.addLauncherUserConfig()) {
+                mSecureSettingsObserver = new SecureSettingsObserver();
+                mSecureSettingsObserver.register();
+            }
+        }
+
         public static class ShortcutChangeHandler implements LauncherApps.ShortcutChangeCallback {
             private final UserManagerInternal mUserManagerInternal;
 
@@ -2837,5 +2848,84 @@
                         shortcutId, sourceBounds, startActivityOptions, targetUserId);
             }
         }
+
+        class SecureSettingsObserver extends ContentObserver {
+
+            SecureSettingsObserver() {
+                super(new Handler(Looper.getMainLooper()));
+            }
+
+            @Override
+            public void onChange(boolean selfChange, Uri uri) {
+                super.onChange(selfChange, uri);
+                if (uri.equals(
+                        Settings.Secure.getUriFor(Settings.Secure.HIDE_PRIVATESPACE_ENTRY_POINT))) {
+
+                    // This setting key only apply to private profile at the moment
+                    UserHandle privateProfile = getPrivateProfile();
+                    if (privateProfile.getIdentifier() == UserHandle.USER_NULL) {
+                        return;
+                    }
+
+                    final int n = mListeners.beginBroadcast();
+                    try {
+                        for (int i = 0; i < n; i++) {
+                            final IOnAppsChangedListener listener =
+                                    mListeners.getBroadcastItem(i);
+                            final BroadcastCookie cookie =
+                                    (BroadcastCookie) mListeners.getBroadcastCookie(
+                                            i);
+                            if (!isEnabledProfileOf(cookie, privateProfile,
+                                    "onSecureSettingsChange")) {
+                                Log.d(TAG, "onSecureSettingsChange: Skipping - profile not enabled"
+                                        + " or not accessible for package=" + cookie.packageName
+                                        + ", packageUid=" + cookie.callingUid);
+                            } else {
+                                try {
+                                    Log.d(TAG,
+                                            "onUserConfigChanged: triggering onUserConfigChanged");
+                                    listener.onUserConfigChanged(
+                                            mUserManagerInternal.getLauncherUserInfo(
+                                                    privateProfile.getIdentifier()));
+                                } catch (RemoteException re) {
+                                    Slog.d(TAG, "onUserConfigChanged: Callback failed ", re);
+                                }
+                            }
+                        }
+                    } finally {
+                        mListeners.finishBroadcast();
+                    }
+                }
+            }
+
+            public void register() {
+                UserHandle privateProfile = getPrivateProfile();
+                int parentUserId;
+                if (privateProfile.getIdentifier() == UserHandle.USER_NULL) {
+                    // No private space available, register the observer for the current user
+                    parentUserId = mContext.getUserId();
+                } else {
+                    parentUserId = mUserManagerInternal.getProfileParentId(
+                            privateProfile.getIdentifier());
+                }
+                mContext.getContentResolver().registerContentObserver(
+                        Settings.Secure.getUriFor(Settings.Secure.HIDE_PRIVATESPACE_ENTRY_POINT),
+                        true, this, parentUserId);
+            }
+
+            public void unregister() {
+                mContext.getContentResolver().unregisterContentObserver(this);
+            }
+
+            private UserHandle getPrivateProfile() {
+                UserInfo[] userInfos = mUserManagerInternal.getUserInfos();
+                for (UserInfo u : userInfos) {
+                    if (u.isPrivateProfile()) {
+                        return UserHandle.of(u.id);
+                    }
+                }
+                return UserHandle.of(UserHandle.USER_NULL);
+            }
+        }
     }
 }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 7ecfe7f..06e29c2 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -21,6 +21,7 @@
 import static android.content.Intent.EXTRA_USER_ID;
 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.pm.LauncherUserInfo.PRIVATE_SPACE_ENTRYPOINT_HIDDEN;
 import static android.content.pm.PackageManager.FEATURE_AUTOMOTIVE;
 import static android.content.pm.PackageManager.FEATURE_EMBEDDED;
 import static android.content.pm.PackageManager.FEATURE_LEANBACK;
@@ -32,6 +33,7 @@
 import static android.os.UserManager.USER_OPERATION_ERROR_UNKNOWN;
 import static android.os.UserManager.USER_OPERATION_ERROR_USER_RESTRICTED;
 import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE;
+import static android.provider.Settings.Secure.HIDE_PRIVATESPACE_ENTRY_POINT;
 
 import static com.android.internal.app.SetScreenLockDialogActivity.EXTRA_ORIGIN_USER_ID;
 import static com.android.internal.app.SetScreenLockDialogActivity.LAUNCH_REASON_DISABLE_QUIET_MODE;
@@ -341,6 +343,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 +1397,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() {
@@ -7902,11 +7948,25 @@
             }
             if (userInfo != null) {
                 final UserTypeDetails userDetails = getUserTypeDetails(userInfo);
-                final LauncherUserInfo uiInfo = new LauncherUserInfo.Builder(
-                        userDetails.getName(),
-                        userInfo.serialNumber)
-                        .build();
-                return uiInfo;
+
+                if (Flags.addLauncherUserConfig()) {
+                    Bundle config = new Bundle();
+                    if (userInfo.isPrivateProfile()) {
+                        try {
+                            int parentId = getProfileParentIdUnchecked(userId);
+                            config.putBoolean(PRIVATE_SPACE_ENTRYPOINT_HIDDEN,
+                                    Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                                            HIDE_PRIVATESPACE_ENTRY_POINT, parentId) == 1);
+                        } catch (Settings.SettingNotFoundException e) {
+                            throw new RuntimeException(e);
+                        }
+                    }
+                    return new LauncherUserInfo.Builder(userDetails.getName(),
+                            userInfo.serialNumber, config).build();
+                }
+
+                return new LauncherUserInfo.Builder(userDetails.getName(),
+                        userInfo.serialNumber).build();
             } else {
                 return null;
             }
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/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 24933ca..5fc3e33 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -1015,8 +1015,7 @@
                     permission, attributionSource, message, forDataDelivery, startDataDelivery,
                     fromDatasource, attributedOp);
             // Finish any started op if some step in the attribution chain failed.
-            if (startDataDelivery && result != PermissionChecker.PERMISSION_GRANTED
-                    && result != PermissionChecker.PERMISSION_SOFT_DENIED) {
+            if (startDataDelivery && result != PermissionChecker.PERMISSION_GRANTED) {
                 if (attributedOp == AppOpsManager.OP_NONE) {
                     finishDataDelivery(AppOpsManager.permissionToOpCode(permission),
                             attributionSource.asState(), fromDatasource);
@@ -1245,7 +1244,6 @@
             final boolean hasChain = attributionChainId != ATTRIBUTION_CHAIN_ID_NONE;
             AttributionSource current = attributionSource;
             AttributionSource next = null;
-            AttributionSource prev = null;
             // We consider the chain trusted if the start node has UPDATE_APP_OPS_STATS, and
             // every attributionSource in the chain is registered with the system.
             final boolean isChainStartTrusted = !hasChain || checkPermission(context,
@@ -1312,21 +1310,8 @@
                         selfAccess, singleReceiverFromDatasource, attributedOp,
                         proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
 
-                if (opMode != AppOpsManager.MODE_ALLOWED) {
-                    // Current failed the perm check, so if we are part-way through an attr chain,
-                    // we need to clean up the already started proxy op higher up the chain.  Note,
-                    // proxy ops are verified two by two, which means we have to clear the 2nd next
-                    // from the previous iteration (since it is actually curr.next which failed
-                    // to pass the perm check).
-                    if (prev != null) {
-                        final var cutAttrSourceState = prev.asState();
-                        if (cutAttrSourceState.next.length > 0) {
-                            cutAttrSourceState.next[0].next = new AttributionSourceState[0];
-                        }
-                        finishDataDelivery(context, attributedOp,
-                                cutAttrSourceState, fromDatasource);
-                    }
-                    if (opMode == AppOpsManager.MODE_ERRORED) {
+                switch (opMode) {
+                    case AppOpsManager.MODE_ERRORED: {
                         if (permission.equals(Manifest.permission.BLUETOOTH_CONNECT)) {
                             Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as op"
                                     + " mode is MODE_ERRORED. Permission check was requested for: "
@@ -1334,7 +1319,8 @@
                                     + current);
                         }
                         return PermissionChecker.PERMISSION_HARD_DENIED;
-                    } else {
+                    }
+                    case AppOpsManager.MODE_IGNORED: {
                         return PermissionChecker.PERMISSION_SOFT_DENIED;
                     }
                 }
@@ -1349,8 +1335,6 @@
                     return PermissionChecker.PERMISSION_GRANTED;
                 }
 
-                // an attribution we have already possibly started an op for
-                prev = current;
                 current = next;
             }
         }
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..1af3ec0 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -84,6 +84,7 @@
 import static android.view.contentprotection.flags.Flags.createAccessibilityOverlayAppOpEnabled;
 
 import static com.android.hardware.input.Flags.emojiAndScreenshotKeycodesAvailable;
+import static com.android.hardware.input.Flags.enableTalkbackAndMagnifierKeyGestures;
 import static com.android.hardware.input.Flags.keyboardA11yShortcutControl;
 import static com.android.hardware.input.Flags.modifierShortcutDump;
 import static com.android.hardware.input.Flags.useKeyGestureEventHandler;
@@ -3414,6 +3415,10 @@
 
     @Override
     public KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId) {
+        if (useKeyGestureEventHandler()) {
+            return mModifierShortcutManager.getApplicationLaunchKeyboardShortcuts(deviceId,
+                    mInputManager.getAppLaunchBookmarks());
+        }
         return mModifierShortcutManager.getApplicationLaunchKeyboardShortcuts(deviceId);
     }
 
@@ -3608,7 +3613,7 @@
                 }
                 break;
             case KeyEvent.KEYCODE_T:
-                if (keyboardA11yShortcutControl()) {
+                if (enableTalkbackAndMagnifierKeyGestures()) {
                     if (firstDown && event.isMetaPressed() && event.isAltPressed()) {
                         mTalkbackShortcutController.toggleTalkback(mCurrentUserId,
                                 TalkbackShortcutController.ShortcutSource.KEYBOARD);
@@ -4004,14 +4009,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);
@@ -4115,7 +4113,7 @@
                         return mDefaultDisplayPolicy.isAwake() && mAccessibilityShortcutController
                                 .isAccessibilityShortcutAvailable(false);
                     case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK:
-                        return keyboardA11yShortcutControl();
+                        return enableTalkbackAndMagnifierKeyGestures();
                     case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS:
                         return InputSettings.isAccessibilitySlowKeysFeatureFlagEnabled()
                                 && keyboardA11yShortcutControl();
@@ -4348,7 +4346,7 @@
                 }
                 return true;
             case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK:
-                if (keyboardA11yShortcutControl()) {
+                if (enableTalkbackAndMagnifierKeyGestures()) {
                     if (complete) {
                         mTalkbackShortcutController.toggleTalkback(mCurrentUserId,
                                 TalkbackShortcutController.ShortcutSource.KEYBOARD);
@@ -4753,6 +4751,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/ThermalManagerService.java b/services/core/java/com/android/server/power/ThermalManagerService.java
index 78bc06c..42dbb79 100644
--- a/services/core/java/com/android/server/power/ThermalManagerService.java
+++ b/services/core/java/com/android/server/power/ThermalManagerService.java
@@ -43,6 +43,7 @@
 import android.os.HwBinder;
 import android.os.IBinder;
 import android.os.IThermalEventListener;
+import android.os.IThermalHeadroomListener;
 import android.os.IThermalService;
 import android.os.IThermalStatusListener;
 import android.os.PowerManager;
@@ -59,6 +60,7 @@
 import android.util.ArrayMap;
 import android.util.EventLog;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.util.StatsEvent;
 
 import com.android.internal.annotations.GuardedBy;
@@ -96,6 +98,15 @@
     /** Input range limits for getThermalHeadroom API */
     public static final int MIN_FORECAST_SEC = 0;
     public static final int MAX_FORECAST_SEC = 60;
+    public static final int DEFAULT_FORECAST_SECONDS = 10;
+    public static final int HEADROOM_CALLBACK_MIN_INTERVAL_MILLIS = 5000;
+    // headroom to temperature conversion: 3C every 0.1 headroom difference
+    // if no throttling event, the temperature difference should be at least 0.9C (or 0.03 headroom)
+    // to make a callback
+    public static final float HEADROOM_CALLBACK_MIN_DIFFERENCE = 0.03f;
+    // if no throttling event, the threshold headroom difference should be at least 0.01 (or 0.3C)
+    // to make a callback
+    public static final float HEADROOM_THRESHOLD_CALLBACK_MIN_DIFFERENCE = 0.01f;
 
     /** Lock to protect listen list. */
     private final Object mLock = new Object();
@@ -113,6 +124,15 @@
     private final RemoteCallbackList<IThermalStatusListener> mThermalStatusListeners =
             new RemoteCallbackList<>();
 
+    /** Registered observers of the thermal headroom. */
+    @GuardedBy("mLock")
+    private final RemoteCallbackList<IThermalHeadroomListener> mThermalHeadroomListeners =
+            new RemoteCallbackList<>();
+    @GuardedBy("mLock")
+    private long mLastHeadroomCallbackTimeMillis;
+    @GuardedBy("mLock")
+    private HeadroomCallbackData mLastHeadroomCallbackData = null;
+
     /** Current thermal status */
     @GuardedBy("mLock")
     private int mStatus;
@@ -133,7 +153,7 @@
 
     /** Watches temperatures to forecast when throttling will occur */
     @VisibleForTesting
-    final TemperatureWatcher mTemperatureWatcher = new TemperatureWatcher();
+    final TemperatureWatcher mTemperatureWatcher;
 
     private final ThermalHalWrapper.WrapperThermalChangedCallback mWrapperCallback =
             new ThermalHalWrapper.WrapperThermalChangedCallback() {
@@ -151,8 +171,14 @@
                 public void onThresholdChanged(TemperatureThreshold threshold) {
                     final long token = Binder.clearCallingIdentity();
                     try {
+                        final HeadroomCallbackData data;
                         synchronized (mTemperatureWatcher.mSamples) {
+                            Slog.d(TAG, "Updating skin threshold: " + threshold);
                             mTemperatureWatcher.updateTemperatureThresholdLocked(threshold, true);
+                            data = mTemperatureWatcher.getHeadroomCallbackDataLocked();
+                        }
+                        synchronized (mLock) {
+                            checkAndNotifyHeadroomListenersLocked(data);
                         }
                     } finally {
                         Binder.restoreCallingIdentity(token);
@@ -175,6 +201,7 @@
             halWrapper.setCallback(mWrapperCallback);
         }
         mStatus = Temperature.THROTTLING_NONE;
+        mTemperatureWatcher = new TemperatureWatcher();
     }
 
     @Override
@@ -231,32 +258,79 @@
         }
     }
 
-    private void postStatusListener(IThermalStatusListener listener) {
+    @GuardedBy("mLock")
+    private void postStatusListenerLocked(IThermalStatusListener listener) {
         final boolean thermalCallbackQueued = FgThread.getHandler().post(() -> {
             try {
                 listener.onStatusChange(mStatus);
             } catch (RemoteException | RuntimeException e) {
-                Slog.e(TAG, "Thermal callback failed to call", e);
+                Slog.e(TAG, "Thermal status callback failed to call", e);
             }
         });
         if (!thermalCallbackQueued) {
-            Slog.e(TAG, "Thermal callback failed to queue");
+            Slog.e(TAG, "Thermal status callback failed to queue");
         }
     }
 
+    @GuardedBy("mLock")
     private void notifyStatusListenersLocked() {
         final int length = mThermalStatusListeners.beginBroadcast();
         try {
             for (int i = 0; i < length; i++) {
                 final IThermalStatusListener listener =
                         mThermalStatusListeners.getBroadcastItem(i);
-                postStatusListener(listener);
+                postStatusListenerLocked(listener);
             }
         } finally {
             mThermalStatusListeners.finishBroadcast();
         }
     }
 
+    @GuardedBy("mLock")
+    private void postHeadroomListenerLocked(IThermalHeadroomListener listener,
+            HeadroomCallbackData data) {
+        if (!mHalReady.get()) {
+            return;
+        }
+        final boolean thermalCallbackQueued = FgThread.getHandler().post(() -> {
+            try {
+                if (Float.isNaN(data.mHeadroom)) {
+                    return;
+                }
+                listener.onHeadroomChange(data.mHeadroom, data.mForecastHeadroom,
+                        data.mForecastSeconds, data.mHeadroomThresholds);
+            } catch (RemoteException | RuntimeException e) {
+                Slog.e(TAG, "Thermal headroom callback failed to call", e);
+            }
+        });
+        if (!thermalCallbackQueued) {
+            Slog.e(TAG, "Thermal headroom callback failed to queue");
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void checkAndNotifyHeadroomListenersLocked(HeadroomCallbackData data) {
+        if (!data.isSignificantDifferentFrom(mLastHeadroomCallbackData)
+                && System.currentTimeMillis()
+                < mLastHeadroomCallbackTimeMillis + HEADROOM_CALLBACK_MIN_INTERVAL_MILLIS) {
+            // skip notifying the client with similar data within a short period
+            return;
+        }
+        mLastHeadroomCallbackTimeMillis = System.currentTimeMillis();
+        mLastHeadroomCallbackData = data;
+        final int length = mThermalHeadroomListeners.beginBroadcast();
+        try {
+            for (int i = 0; i < length; i++) {
+                final IThermalHeadroomListener listener =
+                        mThermalHeadroomListeners.getBroadcastItem(i);
+                postHeadroomListenerLocked(listener, data);
+            }
+        } finally {
+            mThermalHeadroomListeners.finishBroadcast();
+        }
+    }
+
+    @GuardedBy("mLock")
     private void onTemperatureMapChangedLocked() {
         int newStatus = Temperature.THROTTLING_NONE;
         final int count = mTemperatureMap.size();
@@ -272,6 +346,7 @@
         }
     }
 
+    @GuardedBy("mLock")
     private void setStatusLocked(int newStatus) {
         if (newStatus != mStatus) {
             Trace.traceCounter(Trace.TRACE_TAG_POWER, "ThermalManagerService.status", newStatus);
@@ -280,18 +355,18 @@
         }
     }
 
-    private void postEventListenerCurrentTemperatures(IThermalEventListener listener,
+    @GuardedBy("mLock")
+    private void postEventListenerCurrentTemperaturesLocked(IThermalEventListener listener,
             @Nullable Integer type) {
-        synchronized (mLock) {
-            final int count = mTemperatureMap.size();
-            for (int i = 0; i < count; i++) {
-                postEventListener(mTemperatureMap.valueAt(i), listener,
-                        type);
-            }
+        final int count = mTemperatureMap.size();
+        for (int i = 0; i < count; i++) {
+            postEventListenerLocked(mTemperatureMap.valueAt(i), listener,
+                    type);
         }
     }
 
-    private void postEventListener(Temperature temperature,
+    @GuardedBy("mLock")
+    private void postEventListenerLocked(Temperature temperature,
             IThermalEventListener listener,
             @Nullable Integer type) {
         // Skip if listener registered with a different type
@@ -302,14 +377,15 @@
             try {
                 listener.notifyThrottling(temperature);
             } catch (RemoteException | RuntimeException e) {
-                Slog.e(TAG, "Thermal callback failed to call", e);
+                Slog.e(TAG, "Thermal event callback failed to call", e);
             }
         });
         if (!thermalCallbackQueued) {
-            Slog.e(TAG, "Thermal callback failed to queue");
+            Slog.e(TAG, "Thermal event callback failed to queue");
         }
     }
 
+    @GuardedBy("mLock")
     private void notifyEventListenersLocked(Temperature temperature) {
         final int length = mThermalEventListeners.beginBroadcast();
         try {
@@ -318,7 +394,7 @@
                         mThermalEventListeners.getBroadcastItem(i);
                 final Integer type =
                         (Integer) mThermalEventListeners.getBroadcastCookie(i);
-                postEventListener(temperature, listener, type);
+                postEventListenerLocked(temperature, listener, type);
             }
         } finally {
             mThermalEventListeners.finishBroadcast();
@@ -348,17 +424,31 @@
         }
     }
 
-    private void onTemperatureChanged(Temperature temperature, boolean sendStatus) {
+    private void onTemperatureChanged(Temperature temperature, boolean sendCallback) {
         shutdownIfNeeded(temperature);
         synchronized (mLock) {
             Temperature old = mTemperatureMap.put(temperature.getName(), temperature);
             if (old == null || old.getStatus() != temperature.getStatus()) {
                 notifyEventListenersLocked(temperature);
             }
-            if (sendStatus) {
+            if (sendCallback) {
                 onTemperatureMapChangedLocked();
             }
         }
+        if (sendCallback && Flags.allowThermalThresholdsCallback()
+                && temperature.getType() == Temperature.TYPE_SKIN) {
+            final HeadroomCallbackData data;
+            synchronized (mTemperatureWatcher.mSamples) {
+                Slog.d(TAG, "Updating new temperature: " + temperature);
+                mTemperatureWatcher.updateTemperatureSampleLocked(System.currentTimeMillis(),
+                        temperature);
+                mTemperatureWatcher.mCachedHeadrooms.clear();
+                data = mTemperatureWatcher.getHeadroomCallbackDataLocked();
+            }
+            synchronized (mLock) {
+                checkAndNotifyHeadroomListenersLocked(data);
+            }
+        }
     }
 
     private void registerStatsCallbacks() {
@@ -399,7 +489,7 @@
                         return false;
                     }
                     // Notify its callback after new client registered.
-                    postEventListenerCurrentTemperatures(listener, null);
+                    postEventListenerCurrentTemperaturesLocked(listener, null);
                     return true;
                 } finally {
                     Binder.restoreCallingIdentity(token);
@@ -415,11 +505,11 @@
             synchronized (mLock) {
                 final long token = Binder.clearCallingIdentity();
                 try {
-                    if (!mThermalEventListeners.register(listener, new Integer(type))) {
+                    if (!mThermalEventListeners.register(listener, type)) {
                         return false;
                     }
                     // Notify its callback after new client registered.
-                    postEventListenerCurrentTemperatures(listener, new Integer(type));
+                    postEventListenerCurrentTemperaturesLocked(listener, type);
                     return true;
                 } finally {
                     Binder.restoreCallingIdentity(token);
@@ -484,7 +574,7 @@
                         return false;
                     }
                     // Notify its callback after new client registered.
-                    postStatusListener(listener);
+                    postStatusListenerLocked(listener);
                     return true;
                 } finally {
                     Binder.restoreCallingIdentity(token);
@@ -557,11 +647,50 @@
         }
 
         @Override
+        public boolean registerThermalHeadroomListener(IThermalHeadroomListener listener) {
+            if (!mHalReady.get()) {
+                return false;
+            }
+            synchronized (mLock) {
+                // Notify its callback after new client registered.
+                final long token = Binder.clearCallingIdentity();
+                try {
+                    if (!mThermalHeadroomListeners.register(listener)) {
+                        return false;
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            }
+            final HeadroomCallbackData data;
+            synchronized (mTemperatureWatcher.mSamples) {
+                data = mTemperatureWatcher.getHeadroomCallbackDataLocked();
+            }
+            // Notify its callback after new client registered.
+            synchronized (mLock) {
+                postHeadroomListenerLocked(listener, data);
+            }
+            return true;
+        }
+
+        @Override
+        public boolean unregisterThermalHeadroomListener(IThermalHeadroomListener listener) {
+            synchronized (mLock) {
+                final long token = Binder.clearCallingIdentity();
+                try {
+                    return mThermalHeadroomListeners.unregister(listener);
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            }
+        }
+
+        @Override
         public float getThermalHeadroom(int forecastSeconds) {
             if (!mHalReady.get()) {
                 FrameworkStatsLog.write(FrameworkStatsLog.THERMAL_HEADROOM_CALLED, getCallingUid(),
-                            FrameworkStatsLog.THERMAL_HEADROOM_CALLED__API_STATUS__HAL_NOT_READY,
-                            Float.NaN, forecastSeconds);
+                        FrameworkStatsLog.THERMAL_HEADROOM_CALLED__API_STATUS__HAL_NOT_READY,
+                        Float.NaN, forecastSeconds);
                 return Float.NaN;
             }
 
@@ -570,8 +699,8 @@
                     Slog.d(TAG, "Invalid forecastSeconds: " + forecastSeconds);
                 }
                 FrameworkStatsLog.write(FrameworkStatsLog.THERMAL_HEADROOM_CALLED, getCallingUid(),
-                            FrameworkStatsLog.THERMAL_HEADROOM_CALLED__API_STATUS__INVALID_ARGUMENT,
-                            Float.NaN, forecastSeconds);
+                        FrameworkStatsLog.THERMAL_HEADROOM_CALLED__API_STATUS__INVALID_ARGUMENT,
+                        Float.NaN, forecastSeconds);
                 return Float.NaN;
             }
 
@@ -592,13 +721,10 @@
                         THERMAL_HEADROOM_THRESHOLDS_CALLED__API_STATUS__FEATURE_NOT_SUPPORTED);
                 throw new UnsupportedOperationException("Thermal headroom thresholds not enabled");
             }
-            synchronized (mTemperatureWatcher.mSamples) {
-                FrameworkStatsLog.write(FrameworkStatsLog.THERMAL_HEADROOM_THRESHOLDS_CALLED,
-                        Binder.getCallingUid(),
-                        THERMAL_HEADROOM_THRESHOLDS_CALLED__API_STATUS__SUCCESS);
-                return Arrays.copyOf(mTemperatureWatcher.mHeadroomThresholds,
-                        mTemperatureWatcher.mHeadroomThresholds.length);
-            }
+            FrameworkStatsLog.write(FrameworkStatsLog.THERMAL_HEADROOM_THRESHOLDS_CALLED,
+                    Binder.getCallingUid(),
+                    THERMAL_HEADROOM_THRESHOLDS_CALLED__API_STATUS__SUCCESS);
+            return mTemperatureWatcher.getHeadroomThresholds();
         }
 
         @Override
@@ -711,7 +837,7 @@
     class ThermalShellCommand extends ShellCommand {
         @Override
         public int onCommand(String cmd) {
-            switch(cmd != null ? cmd : "") {
+            switch (cmd != null ? cmd : "") {
                 case "inject-temperature":
                     return runInjectTemperature();
                 case "override-status":
@@ -1112,7 +1238,8 @@
         }
 
         @Override
-        @NonNull protected List<TemperatureThreshold> getTemperatureThresholds(
+        @NonNull
+        protected List<TemperatureThreshold> getTemperatureThresholds(
                 boolean shouldFilter, int type) {
             synchronized (mHalLock) {
                 final List<TemperatureThreshold> ret = new ArrayList<>();
@@ -1631,14 +1758,68 @@
         }
     }
 
+    private static final class HeadroomCallbackData {
+        float mHeadroom;
+        float mForecastHeadroom;
+        int mForecastSeconds;
+        float[] mHeadroomThresholds;
+
+        HeadroomCallbackData(float headroom, float forecastHeadroom, int forecastSeconds,
+                @NonNull float[] headroomThresholds) {
+            mHeadroom = headroom;
+            mForecastHeadroom = forecastHeadroom;
+            mForecastSeconds = forecastSeconds;
+            mHeadroomThresholds = headroomThresholds;
+        }
+
+        private boolean isSignificantDifferentFrom(HeadroomCallbackData other) {
+            if (other == null) return true;
+            // currently this is always the same as DEFAULT_FORECAST_SECONDS, when it's retried
+            // from thermal HAL, we may want to adjust this.
+            if (this.mForecastSeconds != other.mForecastSeconds) return true;
+            if (Math.abs(this.mHeadroom - other.mHeadroom)
+                    >= HEADROOM_CALLBACK_MIN_DIFFERENCE) return true;
+            if (Math.abs(this.mForecastHeadroom - other.mForecastHeadroom)
+                    >= HEADROOM_CALLBACK_MIN_DIFFERENCE) return true;
+            for (int i = 0; i < this.mHeadroomThresholds.length; i++) {
+                if (Float.isNaN(this.mHeadroomThresholds[i]) != Float.isNaN(
+                        other.mHeadroomThresholds[i])) {
+                    return true;
+                }
+                if (Math.abs(this.mHeadroomThresholds[i] - other.mHeadroomThresholds[i])
+                        >= HEADROOM_THRESHOLD_CALLBACK_MIN_DIFFERENCE) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        @Override
+        public String toString() {
+            return "HeadroomCallbackData[mHeadroom=" + mHeadroom + ", mForecastHeadroom="
+                    + mForecastHeadroom + ", mForecastSeconds=" + mForecastSeconds
+                    + ", mHeadroomThresholds=" + Arrays.toString(mHeadroomThresholds) + "]";
+        }
+    }
+
     @VisibleForTesting
     class TemperatureWatcher {
+        private static final int RING_BUFFER_SIZE = 30;
+        private static final int INACTIVITY_THRESHOLD_MILLIS = 10000;
+        @VisibleForTesting
+        long mInactivityThresholdMillis = INACTIVITY_THRESHOLD_MILLIS;
+
         private final Handler mHandler = BackgroundThread.getHandler();
 
-        /** Map of skin temperature sensor name to a corresponding list of samples */
+        /**
+         * Map of skin temperature sensor name to a corresponding list of samples
+         * Updates to the samples should also clear the headroom cache.
+         */
         @GuardedBy("mSamples")
         @VisibleForTesting
         final ArrayMap<String, ArrayList<Sample>> mSamples = new ArrayMap<>();
+        @GuardedBy("mSamples")
+        private final SparseArray<Float> mCachedHeadrooms = new SparseArray<>(2);
 
         /** Map of skin temperature sensor name to the corresponding SEVERE temperature threshold */
         @GuardedBy("mSamples")
@@ -1650,13 +1831,9 @@
         @GuardedBy("mSamples")
         private long mLastForecastCallTimeMillis = 0;
 
-        private static final int INACTIVITY_THRESHOLD_MILLIS = 10000;
-        @VisibleForTesting
-        long mInactivityThresholdMillis = INACTIVITY_THRESHOLD_MILLIS;
-
         void getAndUpdateThresholds() {
             List<TemperatureThreshold> thresholds =
-                        mHalWrapper.getTemperatureThresholds(true, Temperature.TYPE_SKIN);
+                    mHalWrapper.getTemperatureThresholds(true, Temperature.TYPE_SKIN);
             synchronized (mSamples) {
                 if (Flags.allowThermalHeadroomThresholds()) {
                     Arrays.fill(mHeadroomThresholds, Float.NaN);
@@ -1684,6 +1861,8 @@
                 return;
             }
             if (override) {
+                Slog.d(TAG, "Headroom cache cleared on threshold update " + threshold);
+                mCachedHeadrooms.clear();
                 Arrays.fill(mHeadroomThresholds, Float.NaN);
             }
             for (int severity = ThrottlingSeverity.LIGHT;
@@ -1693,62 +1872,61 @@
                     if (Float.isNaN(t)) {
                         continue;
                     }
-                    synchronized (mSamples) {
-                        if (severity == ThrottlingSeverity.SEVERE) {
-                            mHeadroomThresholds[severity] = 1.0f;
-                            continue;
-                        }
-                        float headroom = normalizeTemperature(t, severeThreshold);
-                        if (Float.isNaN(mHeadroomThresholds[severity])) {
-                            mHeadroomThresholds[severity] = headroom;
-                        } else {
-                            float lastHeadroom = mHeadroomThresholds[severity];
-                            mHeadroomThresholds[severity] = Math.min(lastHeadroom, headroom);
-                        }
+                    if (severity == ThrottlingSeverity.SEVERE) {
+                        mHeadroomThresholds[severity] = 1.0f;
+                        continue;
+                    }
+                    float headroom = normalizeTemperature(t, severeThreshold);
+                    if (Float.isNaN(mHeadroomThresholds[severity])) {
+                        mHeadroomThresholds[severity] = headroom;
+                    } else {
+                        float lastHeadroom = mHeadroomThresholds[severity];
+                        mHeadroomThresholds[severity] = Math.min(lastHeadroom, headroom);
                     }
                 }
             }
         }
 
-        private static final int RING_BUFFER_SIZE = 30;
-
-        private void updateTemperature() {
+        private void getAndUpdateTemperatureSamples() {
             synchronized (mSamples) {
                 if (SystemClock.elapsedRealtime() - mLastForecastCallTimeMillis
                         < mInactivityThresholdMillis) {
                     // Trigger this again after a second as long as forecast has been called more
                     // recently than the inactivity timeout
-                    mHandler.postDelayed(this::updateTemperature, 1000);
+                    mHandler.postDelayed(this::getAndUpdateTemperatureSamples, 1000);
                 } else {
                     // Otherwise, we've been idle for at least 10 seconds, so we should
                     // shut down
                     mSamples.clear();
+                    mCachedHeadrooms.clear();
                     return;
                 }
 
                 long now = SystemClock.elapsedRealtime();
-                List<Temperature> temperatures = mHalWrapper.getCurrentTemperatures(true,
+                final List<Temperature> temperatures = mHalWrapper.getCurrentTemperatures(true,
                         Temperature.TYPE_SKIN);
-
-                for (int t = 0; t < temperatures.size(); ++t) {
-                    Temperature temperature = temperatures.get(t);
-
-                    // Filter out invalid temperatures. If this results in no values being stored at
-                    // all, the mSamples.empty() check in getForecast() will catch it.
-                    if (Float.isNaN(temperature.getValue())) {
-                        continue;
-                    }
-
-                    ArrayList<Sample> samples = mSamples.computeIfAbsent(temperature.getName(),
-                            k -> new ArrayList<>(RING_BUFFER_SIZE));
-                    if (samples.size() == RING_BUFFER_SIZE) {
-                        samples.removeFirst();
-                    }
-                    samples.add(new Sample(now, temperature.getValue()));
+                for (Temperature temperature : temperatures) {
+                    updateTemperatureSampleLocked(now, temperature);
                 }
+                mCachedHeadrooms.clear();
             }
         }
 
+        @GuardedBy("mSamples")
+        private void updateTemperatureSampleLocked(long timeNow, Temperature temperature) {
+            // Filter out invalid temperatures. If this results in no values being stored at
+            // all, the mSamples.empty() check in getForecast() will catch it.
+            if (Float.isNaN(temperature.getValue())) {
+                return;
+            }
+            ArrayList<Sample> samples = mSamples.computeIfAbsent(temperature.getName(),
+                    k -> new ArrayList<>(RING_BUFFER_SIZE));
+            if (samples.size() == RING_BUFFER_SIZE) {
+                samples.removeFirst();
+            }
+            samples.add(new Sample(timeNow, temperature.getValue()));
+        }
+
         /**
          * Calculates the trend using a linear regression. As the samples are degrees Celsius with
          * associated timestamps in milliseconds, the slope is in degrees Celsius per millisecond.
@@ -1801,7 +1979,7 @@
             synchronized (mSamples) {
                 mLastForecastCallTimeMillis = SystemClock.elapsedRealtime();
                 if (mSamples.isEmpty()) {
-                    updateTemperature();
+                    getAndUpdateTemperatureSamples();
                 }
 
                 // If somehow things take much longer than expected or there are no temperatures
@@ -1826,6 +2004,14 @@
                     return Float.NaN;
                 }
 
+                if (mCachedHeadrooms.contains(forecastSeconds)) {
+                    // TODO(b/360486877): replace with metrics
+                    Slog.d(TAG,
+                            "Headroom forecast in " + forecastSeconds + "s served from cache: "
+                                    + mCachedHeadrooms.get(forecastSeconds));
+                    return mCachedHeadrooms.get(forecastSeconds);
+                }
+
                 float maxNormalized = Float.NaN;
                 int noThresholdSampleCount = 0;
                 for (Map.Entry<String, ArrayList<Sample>> entry : mSamples.entrySet()) {
@@ -1842,6 +2028,12 @@
                     float currentTemperature = samples.getLast().temperature;
 
                     if (samples.size() < MINIMUM_SAMPLE_COUNT) {
+                        if (mSamples.size() == 1 && mCachedHeadrooms.contains(0)) {
+                            // if only one sensor name exists, then try reading the cache
+                            // TODO(b/360486877): replace with metrics
+                            Slog.d(TAG, "Headroom forecast cached: " + mCachedHeadrooms.get(0));
+                            return mCachedHeadrooms.get(0);
+                        }
                         // Don't try to forecast, just use the latest one we have
                         float normalized = normalizeTemperature(currentTemperature, threshold);
                         if (Float.isNaN(maxNormalized) || normalized > maxNormalized) {
@@ -1849,8 +2041,10 @@
                         }
                         continue;
                     }
-
-                    float slope = getSlopeOf(samples);
+                    float slope = 0.0f;
+                    if (forecastSeconds > 0) {
+                        slope = getSlopeOf(samples);
+                    }
                     float normalized = normalizeTemperature(
                             currentTemperature + slope * forecastSeconds * 1000, threshold);
                     if (Float.isNaN(maxNormalized) || normalized > maxNormalized) {
@@ -1868,10 +2062,28 @@
                             FrameworkStatsLog.THERMAL_HEADROOM_CALLED__API_STATUS__SUCCESS,
                             maxNormalized, forecastSeconds);
                 }
+                mCachedHeadrooms.put(forecastSeconds, maxNormalized);
                 return maxNormalized;
             }
         }
 
+        float[] getHeadroomThresholds() {
+            synchronized (mSamples) {
+                return Arrays.copyOf(mHeadroomThresholds, mHeadroomThresholds.length);
+            }
+        }
+
+        @GuardedBy("mSamples")
+        HeadroomCallbackData getHeadroomCallbackDataLocked() {
+            final HeadroomCallbackData data = new HeadroomCallbackData(
+                    getForecast(0),
+                    getForecast(DEFAULT_FORECAST_SECONDS),
+                    DEFAULT_FORECAST_SECONDS,
+                    Arrays.copyOf(mHeadroomThresholds, mHeadroomThresholds.length));
+            Slog.d(TAG, "New headroom callback data: " + data);
+            return data;
+        }
+
         @VisibleForTesting
         // Since Sample is inside an inner class, we can't make it static
         // This allows test code to create Sample objects via ThermalManagerService
@@ -1880,7 +2092,7 @@
         }
 
         @VisibleForTesting
-        class Sample {
+        static class Sample {
             public long time;
             public float temperature;
 
@@ -1888,6 +2100,11 @@
                 this.time = time;
                 this.temperature = temperature;
             }
+
+            @Override
+            public String toString() {
+                return "Sample[temperature=" + temperature + ", time=" + time + "]";
+            }
         }
     }
 }
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
index 600fe59..606bd1d 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
@@ -51,6 +51,7 @@
     private final CpuScalingPolicies mCpuScalingPolicies;
     private final int mAccumulatedBatteryUsageStatsSpanSize;
     private final Clock mClock;
+    private final MonotonicClock mMonotonicClock;
     private final Object mLock = new Object();
     private List<PowerCalculator> mPowerCalculators;
     private UserPowerCalculator mUserPowerCalculator;
@@ -67,7 +68,7 @@
             @NonNull PowerAttributor powerAttributor,
             @NonNull PowerProfile powerProfile, @NonNull CpuScalingPolicies cpuScalingPolicies,
             @NonNull PowerStatsStore powerStatsStore, int accumulatedBatteryUsageStatsSpanSize,
-            @NonNull Clock clock) {
+            @NonNull Clock clock, @NonNull MonotonicClock monotonicClock) {
         mContext = context;
         mPowerAttributor = powerAttributor;
         mPowerStatsStore = powerStatsStore;
@@ -75,6 +76,7 @@
         mCpuScalingPolicies = cpuScalingPolicies;
         mAccumulatedBatteryUsageStatsSpanSize = accumulatedBatteryUsageStatsSpanSize;
         mClock = clock;
+        mMonotonicClock = monotonicClock;
         mUserPowerCalculator = new UserPowerCalculator();
 
         mPowerStatsStore.addSectionReader(new BatteryUsageStatsSection.Reader());
@@ -213,7 +215,7 @@
         powerStatsSpan.addTimeFrame(accumulatedStats.startMonotonicTime,
                 accumulatedStats.startWallClockTime,
                 accumulatedStats.endMonotonicTime - accumulatedStats.startMonotonicTime);
-        stats.commitMonotonicClock();
+        mMonotonicClock.write();
         mPowerStatsStore.storePowerStatsSpanAsync(powerStatsSpan,
                 accumulatedStats.builder::discard);
     }
@@ -308,23 +310,29 @@
 
     private void updateAccumulatedBatteryUsageStats(AccumulatedBatteryUsageStats accumulatedStats,
             BatteryStatsImpl stats, BatteryUsageStatsQuery query) {
-        // TODO(b/366493365): add the current batteryusagestats directly into
-        //  `accumulatedStats.builder` to avoid allocating a second CursorWindow
-        BatteryUsageStats.Builder remainingBatteryUsageStats = computeBatteryUsageStats(stats,
-                query, accumulatedStats.endMonotonicTime, query.getMonotonicEndTime(),
-                mClock.currentTimeMillis());
+        long startMonotonicTime = accumulatedStats.endMonotonicTime;
+        if (startMonotonicTime == MonotonicClock.UNDEFINED) {
+            startMonotonicTime = stats.getMonotonicStartTime();
+        }
+        long endWallClockTime = mClock.currentTimeMillis();
+        long endMonotonicTime = mMonotonicClock.monotonicTime();
 
         if (accumulatedStats.builder == null) {
-            accumulatedStats.builder = remainingBatteryUsageStats;
+            accumulatedStats.builder = new BatteryUsageStats.Builder(
+                    stats.getCustomEnergyConsumerNames(), false, true, true, true, 0);
             accumulatedStats.startWallClockTime = stats.getStartClockTime();
-            accumulatedStats.startMonotonicTime = stats.getMonotonicStartTime();
-            accumulatedStats.endMonotonicTime = accumulatedStats.startMonotonicTime
-                    + accumulatedStats.builder.getStatsDuration();
-        } else {
-            accumulatedStats.builder.add(remainingBatteryUsageStats.build());
-            accumulatedStats.endMonotonicTime += remainingBatteryUsageStats.getStatsDuration();
-            remainingBatteryUsageStats.discard();
+            accumulatedStats.builder.setStatsStartTimestamp(accumulatedStats.startWallClockTime);
         }
+
+        accumulatedStats.endMonotonicTime = endMonotonicTime;
+
+        accumulatedStats.builder.setStatsEndTimestamp(endWallClockTime);
+        accumulatedStats.builder.setStatsDuration(endWallClockTime - startMonotonicTime);
+
+        mPowerAttributor.estimatePowerConsumption(accumulatedStats.builder, stats.getHistory(),
+                startMonotonicTime, MonotonicClock.UNDEFINED);
+
+        populateGeneralInfo(accumulatedStats.builder, stats);
     }
 
     private BatteryUsageStats.Builder computeBatteryUsageStats(BatteryStatsImpl stats,
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index cf17804..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.
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/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index ee07d2e..76e8a70 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1736,7 +1736,7 @@
 
         // Show IME over the keyguard if the target allows it.
         final boolean showImeOverKeyguard =
-                imeTarget != null && imeTarget.isOnScreen() && win.mIsImWindow && (
+                imeTarget != null && win.mIsImWindow && imeTarget.isDisplayed() && (
                         imeTarget.canShowWhenLocked() || !imeTarget.canBeHiddenByKeyguard());
         if (showImeOverKeyguard) {
             return false;
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 72d45e4..a603466 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -3586,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/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/services-jarjar-rules.txt b/services/core/services-jarjar-rules.txt
new file mode 100644
index 0000000..0d296b2
--- /dev/null
+++ b/services/core/services-jarjar-rules.txt
@@ -0,0 +1,2 @@
+# For profiling flags
+rule android.os.profiling.** android.internal.os.profiling.@1
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 6cb1756..f1711f5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -337,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<>();
 
@@ -383,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);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index 1454162..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;
@@ -210,6 +211,7 @@
     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();
@@ -435,4 +437,43 @@
             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..be698b2 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",
     ],
 
@@ -77,7 +77,10 @@
         "flag-junit",
         "am_flags_lib",
         "device_policy_aconfig_flags_lib",
-    ],
+    ] + select(soong_config_variable("ANDROID", "release_crashrecovery_module"), {
+        "true": ["service-crashrecovery.impl"],
+        default: [],
+    }),
 
     libs: [
         "android.test.mock.stubs.system",
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/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
index f2acbc3..f40d803 100644
--- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
@@ -45,13 +45,11 @@
 import android.os.RecoverySystem;
 import android.os.SystemProperties;
 import android.os.UserHandle;
-import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.FlagsParameterization;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
-import android.util.ArraySet;
 
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
 import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
@@ -74,11 +72,9 @@
 import org.mockito.stubbing.Answer;
 
 import java.lang.reflect.Field;
-import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
-import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 
 
@@ -250,37 +246,6 @@
     }
 
     @Test
-    @DisableFlags({Flags.FLAG_RECOVERABILITY_DETECTION,
-            Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS})
-    public void testBootLoop() {
-        // this is old test where the flag needs to be disabled
-        RescueParty.onSettingsProviderPublished(mMockContext);
-        verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
-                any(Executor.class),
-                mMonitorCallbackCaptor.capture()));
-        HashMap<String, Integer> verifiedTimesMap = new HashMap<String, Integer>();
-
-        noteBoot(1);
-
-        // Record DeviceConfig accesses
-        RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
-        DeviceConfig.MonitorCallback monitorCallback = mMonitorCallbackCaptor.getValue();
-        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE1);
-        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE2);
-
-        final String[] expectedAllResetNamespaces = new String[]{NAMESPACE1, NAMESPACE2};
-
-        noteBoot(2);
-        noteBoot(3);
-
-        noteBoot(4);
-        assertTrue(RescueParty.isRebootPropertySet());
-
-        setCrashRecoveryPropAttemptingReboot(false);
-        noteBoot(5);
-        assertTrue(RescueParty.isFactoryResetPropertySet());
-    }
-    @Test
     @EnableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
     public void testBootLoopNoFlags() {
         // this is old test where the flag needs to be disabled
@@ -293,61 +258,6 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_RECOVERABILITY_DETECTION)
-    @DisableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
-    public void testBootLoopRecoverability() {
-        RescueParty.onSettingsProviderPublished(mMockContext);
-        verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
-                any(Executor.class),
-                mMonitorCallbackCaptor.capture()));
-        HashMap<String, Integer> verifiedTimesMap = new HashMap<String, Integer>();
-
-        // Record DeviceConfig accesses
-        DeviceConfig.MonitorCallback monitorCallback = mMonitorCallbackCaptor.getValue();
-        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE1);
-        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE2);
-
-        final String[] expectedAllResetNamespaces = new String[]{NAMESPACE1, NAMESPACE2};
-
-
-        noteBoot(1);
-
-        noteBoot(2);
-        assertTrue(RescueParty.isRebootPropertySet());
-
-        noteBoot(3);
-
-        verifyOnlySettingsReset(Settings.RESET_MODE_UNTRUSTED_DEFAULTS);
-
-        noteBoot(4);
-        verifyOnlySettingsReset(Settings.RESET_MODE_UNTRUSTED_CHANGES);
-
-        noteBoot(5);
-        verifyOnlySettingsReset(Settings.RESET_MODE_TRUSTED_DEFAULTS);
-
-        setCrashRecoveryPropAttemptingReboot(false);
-        noteBoot(6);
-        assertTrue(RescueParty.isFactoryResetPropertySet());
-    }
-
-    @Test
-    @DisableFlags({Flags.FLAG_RECOVERABILITY_DETECTION,
-            Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS})
-    public void testPersistentAppCrash() {
-        // this is old test where the flag needs to be disabled
-        noteAppCrash(1, true);
-        noteAppCrash(2, true);
-        noteAppCrash(3, true);
-
-        noteAppCrash(4, true);
-        assertTrue(RescueParty.isRebootPropertySet());
-
-        setCrashRecoveryPropAttemptingReboot(false);
-        noteAppCrash(5, true);
-        assertTrue(RescueParty.isFactoryResetPropertySet());
-    }
-
-    @Test
     @EnableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
     public void testPersistentAppCrashNoFlags() {
         // this is old test where the flag needs to be disabled
@@ -360,98 +270,6 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_RECOVERABILITY_DETECTION)
-    @DisableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
-    public void testPersistentAppCrashRecoverability() {
-        RescueParty.onSettingsProviderPublished(mMockContext);
-        verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
-                any(Executor.class),
-                mMonitorCallbackCaptor.capture()));
-        HashMap<String, Integer> verifiedTimesMap = new HashMap<String, Integer>();
-
-        // Record DeviceConfig accesses
-        DeviceConfig.MonitorCallback monitorCallback = mMonitorCallbackCaptor.getValue();
-        monitorCallback.onDeviceConfigAccess(PERSISTENT_PACKAGE, NAMESPACE1);
-        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE1);
-        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE2);
-
-        final String[] expectedResetNamespaces = new String[]{NAMESPACE1};
-        final String[] expectedAllResetNamespaces = new String[]{NAMESPACE1, NAMESPACE2};
-
-        noteAppCrash(1, true);
-
-        noteAppCrash(2, true);
-
-        noteAppCrash(3, true);
-        assertTrue(RescueParty.isRebootPropertySet());
-
-        noteAppCrash(4, true);
-        verifyOnlySettingsReset(Settings.RESET_MODE_UNTRUSTED_DEFAULTS);
-
-        noteAppCrash(5, true);
-        verifyOnlySettingsReset(Settings.RESET_MODE_UNTRUSTED_CHANGES);
-
-        noteAppCrash(6, true);
-        verifyOnlySettingsReset(Settings.RESET_MODE_TRUSTED_DEFAULTS);
-
-        setCrashRecoveryPropAttemptingReboot(false);
-        noteAppCrash(7, true);
-        assertTrue(RescueParty.isFactoryResetPropertySet());
-    }
-
-    @Test
-    @DisableFlags({Flags.FLAG_RECOVERABILITY_DETECTION,
-            Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS})
-    public void testNonPersistentApp() {
-        // this is old test where the flag needs to be disabled
-        noteAppCrash(1, false);
-        noteAppCrash(2, false);
-        noteAppCrash(3, false);
-        assertFalse(RescueParty.isRebootPropertySet());
-
-        noteAppCrash(5, false);
-        assertFalse(RescueParty.isFactoryResetPropertySet());
-    }
-
-    @Test
-    @EnableFlags(Flags.FLAG_RECOVERABILITY_DETECTION)
-    @DisableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
-    public void testNonPersistentAppOnlyPerformsFlagResetsRecoverabilityDetection() {
-        RescueParty.onSettingsProviderPublished(mMockContext);
-        verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
-                any(Executor.class),
-                mMonitorCallbackCaptor.capture()));
-        HashMap<String, Integer> verifiedTimesMap = new HashMap<String, Integer>();
-
-        // Record DeviceConfig accesses
-        DeviceConfig.MonitorCallback monitorCallback = mMonitorCallbackCaptor.getValue();
-        monitorCallback.onDeviceConfigAccess(NON_PERSISTENT_PACKAGE, NAMESPACE1);
-        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE1);
-        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE2);
-
-        final String[] expectedResetNamespaces = new String[]{NAMESPACE1};
-        final String[] expectedAllResetNamespaces = new String[]{NAMESPACE1, NAMESPACE2};
-
-        noteAppCrash(1, false);
-
-        noteAppCrash(2, false);
-
-        noteAppCrash(3, false);
-        assertFalse(RescueParty.isRebootPropertySet());
-
-        noteAppCrash(4, false);
-        verifyNoSettingsReset(Settings.RESET_MODE_UNTRUSTED_DEFAULTS);
-        noteAppCrash(5, false);
-        verifyNoSettingsReset(Settings.RESET_MODE_UNTRUSTED_CHANGES);
-        noteAppCrash(6, false);
-        verifyNoSettingsReset(Settings.RESET_MODE_TRUSTED_DEFAULTS);
-
-        setCrashRecoveryPropAttemptingReboot(false);
-        noteAppCrash(7, false);
-        assertFalse(RescueParty.isFactoryResetPropertySet());
-    }
-
-    @Test
     public void testIsRecoveryTriggeredReboot() {
         for (int i = 0; i < LEVEL_FACTORY_RESET; i++) {
             noteBoot(i + 1);
@@ -522,6 +340,7 @@
 
     @Test
     public void testNotThrottlingAfterTimeoutOnAppCrash() {
+        when(mMockContext.getPackageManager()).thenReturn(mPackageManager);
         setCrashRecoveryPropAttemptingReboot(false);
         long now = System.currentTimeMillis();
         long afterTimeout = now - TimeUnit.MINUTES.toMillis(
@@ -534,30 +353,15 @@
     }
 
     @Test
-    @DisableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
-    public void testNativeRescuePartyResets() {
-        doReturn(true).when(() -> SettingsToPropertiesMapper.isNativeFlagsResetPerformed());
-        doReturn(FAKE_RESET_NATIVE_NAMESPACES).when(
-                () -> SettingsToPropertiesMapper.getResetNativeCategories());
-
-        RescueParty.onSettingsProviderPublished(mMockContext);
-
-        verify(() -> DeviceConfig.resetToDefaults(Settings.RESET_MODE_TRUSTED_DEFAULTS,
-                FAKE_NATIVE_NAMESPACE1));
-        verify(() -> DeviceConfig.resetToDefaults(Settings.RESET_MODE_TRUSTED_DEFAULTS,
-                FAKE_NATIVE_NAMESPACE2));
-    }
-
-    @Test
     public void testExplicitlyEnablingAndDisablingRescue() {
         SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(false));
         SystemProperties.set(PROP_DISABLE_RESCUE, Boolean.toString(true));
-        assertEquals(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage,
-                PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1), false);
+        assertEquals(RescuePartyObserver.getInstance(mMockContext).onExecuteHealthCheckMitigation(
+                sFailingPackage, PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1), false);
 
         SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true));
-        assertTrue(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage,
-                PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1));
+        assertTrue(RescuePartyObserver.getInstance(mMockContext).onExecuteHealthCheckMitigation(
+                sFailingPackage, PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1));
     }
 
     @Test
@@ -565,8 +369,8 @@
         SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(false));
         SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(true));
 
-        assertEquals(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage,
-                PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1), false);
+        assertEquals(RescuePartyObserver.getInstance(mMockContext).onExecuteHealthCheckMitigation(
+                sFailingPackage, PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1), false);
 
         // Restore the property value initialized in SetUp()
         SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true));
@@ -587,75 +391,6 @@
     }
 
     @Test
-    @DisableFlags({Flags.FLAG_RECOVERABILITY_DETECTION,
-            Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS})
-    public void testHealthCheckLevels() {
-        // this is old test where the flag needs to be disabled
-        RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
-
-        // Ensure that no action is taken for cases where the failure reason is unknown
-        assertEquals(observer.onHealthCheckFailed(null, PackageWatchdog.FAILURE_REASON_UNKNOWN, 1),
-                PackageHealthObserverImpact.USER_IMPACT_LEVEL_0);
-
-        // Ensure the correct user impact is returned for each mitigation count.
-        assertEquals(observer.onHealthCheckFailed(null,
-                PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1),
-                PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
-
-        assertEquals(observer.onHealthCheckFailed(null,
-                PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 2),
-                PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
-
-        assertEquals(observer.onHealthCheckFailed(null,
-                PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 3),
-                PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
-
-        assertEquals(observer.onHealthCheckFailed(null,
-                PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 4),
-                PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
-    }
-
-    @Test
-    @DisableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
-    @EnableFlags(Flags.FLAG_RECOVERABILITY_DETECTION)
-    public void testHealthCheckLevelsRecoverabilityDetection() {
-        RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
-
-        // Ensure that no action is taken for cases where the failure reason is unknown
-        assertEquals(observer.onHealthCheckFailed(sFailingPackage,
-                PackageWatchdog.FAILURE_REASON_UNKNOWN, 1),
-                PackageHealthObserverImpact.USER_IMPACT_LEVEL_0);
-
-        // Ensure the correct user impact is returned for each mitigation count.
-        assertEquals(observer.onHealthCheckFailed(sFailingPackage,
-                PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1),
-                PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
-
-        assertEquals(observer.onHealthCheckFailed(sFailingPackage,
-                PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 2),
-                PackageHealthObserverImpact.USER_IMPACT_LEVEL_40);
-
-        assertEquals(observer.onHealthCheckFailed(sFailingPackage,
-                PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 3),
-                PackageHealthObserverImpact.USER_IMPACT_LEVEL_40);
-
-        assertEquals(observer.onHealthCheckFailed(sFailingPackage,
-                PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 4),
-                PackageHealthObserverImpact.USER_IMPACT_LEVEL_40);
-
-        assertEquals(observer.onHealthCheckFailed(sFailingPackage,
-                PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 5),
-                PackageHealthObserverImpact.USER_IMPACT_LEVEL_40);
-
-        assertEquals(observer.onHealthCheckFailed(sFailingPackage,
-                PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 6),
-                PackageHealthObserverImpact.USER_IMPACT_LEVEL_40);
-
-        assertEquals(observer.onHealthCheckFailed(sFailingPackage,
-                PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 7),
-                PackageHealthObserverImpact.USER_IMPACT_LEVEL_40);
-    }
-    @Test
     @EnableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
     public void testHealthCheckLevelsNoFlags() {
         // this is old test where the flag needs to be disabled
@@ -674,36 +409,6 @@
                         PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 2),
                 PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
     }
-    @Test
-    @DisableFlags({Flags.FLAG_RECOVERABILITY_DETECTION,
-            Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS})
-    public void testBootLoopLevels() {
-        // this is old test where the flag needs to be disabled
-
-
-        RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
-
-        assertEquals(observer.onBootLoop(0), PackageHealthObserverImpact.USER_IMPACT_LEVEL_0);
-        assertEquals(observer.onBootLoop(1), PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
-        assertEquals(observer.onBootLoop(2), PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
-        assertEquals(observer.onBootLoop(3), PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
-        assertEquals(observer.onBootLoop(4), PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
-        assertEquals(observer.onBootLoop(5), PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
-    }
-
-    @Test
-    @DisableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
-    @EnableFlags(Flags.FLAG_RECOVERABILITY_DETECTION)
-    public void testBootLoopLevelsRecoverabilityDetection() {
-        RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
-
-        assertEquals(observer.onBootLoop(1), PackageHealthObserverImpact.USER_IMPACT_LEVEL_40);
-        assertEquals(observer.onBootLoop(2), PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
-        assertEquals(observer.onBootLoop(3), PackageHealthObserverImpact.USER_IMPACT_LEVEL_71);
-        assertEquals(observer.onBootLoop(4), PackageHealthObserverImpact.USER_IMPACT_LEVEL_75);
-        assertEquals(observer.onBootLoop(5), PackageHealthObserverImpact.USER_IMPACT_LEVEL_80);
-        assertEquals(observer.onBootLoop(6), PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
-    }
 
     @Test
     @EnableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
@@ -714,129 +419,6 @@
         assertEquals(observer.onBootLoop(2), PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
     }
 
-    @Test
-    @DisableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
-    public void testResetDeviceConfigForPackagesOnlyRuntimeMap() {
-        RescueParty.onSettingsProviderPublished(mMockContext);
-        verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
-                any(Executor.class),
-                mMonitorCallbackCaptor.capture()));
-
-        // Record DeviceConfig accesses
-        RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
-        DeviceConfig.MonitorCallback monitorCallback = mMonitorCallbackCaptor.getValue();
-        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE1);
-        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE2);
-        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE2, NAMESPACE2);
-        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE2, NAMESPACE3);
-        // Fake DeviceConfig value changes
-        monitorCallback.onNamespaceUpdate(NAMESPACE1);
-        monitorCallback.onNamespaceUpdate(NAMESPACE2);
-        monitorCallback.onNamespaceUpdate(NAMESPACE3);
-
-        doReturn("").when(() -> DeviceConfig.getString(
-                eq(RescueParty.NAMESPACE_CONFIGURATION),
-                eq(RescueParty.NAMESPACE_TO_PACKAGE_MAPPING_FLAG),
-                eq("")));
-
-        RescueParty.resetDeviceConfigForPackages(Arrays.asList(new String[]{CALLING_PACKAGE1}));
-        ArraySet<String> expectedNamespacesWiped = new ArraySet<String>(
-                Arrays.asList(new String[]{NAMESPACE1, NAMESPACE2}));
-        assertEquals(mNamespacesWiped, expectedNamespacesWiped);
-    }
-
-    @Test
-    @DisableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
-    public void testResetDeviceConfigForPackagesOnlyPresetMap() {
-        RescueParty.onSettingsProviderPublished(mMockContext);
-        verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
-                any(Executor.class),
-                mMonitorCallbackCaptor.capture()));
-
-        String presetMapping = NAMESPACE1 + ":" + CALLING_PACKAGE1 + ","
-                + NAMESPACE2 + ":" + CALLING_PACKAGE2 + ","
-                + NAMESPACE3 + ":" + CALLING_PACKAGE1;
-        doReturn(presetMapping).when(() -> DeviceConfig.getString(
-                eq(RescueParty.NAMESPACE_CONFIGURATION),
-                eq(RescueParty.NAMESPACE_TO_PACKAGE_MAPPING_FLAG),
-                eq("")));
-
-        RescueParty.resetDeviceConfigForPackages(Arrays.asList(new String[]{CALLING_PACKAGE1}));
-        ArraySet<String> expectedNamespacesWiped = new ArraySet<String>(
-                Arrays.asList(new String[]{NAMESPACE1, NAMESPACE3}));
-        assertEquals(mNamespacesWiped, expectedNamespacesWiped);
-    }
-
-    @Test
-    @DisableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
-    public void testResetDeviceConfigForPackagesBothMaps() {
-        RescueParty.onSettingsProviderPublished(mMockContext);
-        verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
-                any(Executor.class),
-                mMonitorCallbackCaptor.capture()));
-
-        // Record DeviceConfig accesses
-        RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
-        DeviceConfig.MonitorCallback monitorCallback = mMonitorCallbackCaptor.getValue();
-        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE1);
-        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE2);
-        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE2, NAMESPACE2);
-        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE2, NAMESPACE3);
-        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE3, NAMESPACE4);
-        // Fake DeviceConfig value changes
-        monitorCallback.onNamespaceUpdate(NAMESPACE1);
-        monitorCallback.onNamespaceUpdate(NAMESPACE2);
-        monitorCallback.onNamespaceUpdate(NAMESPACE3);
-        monitorCallback.onNamespaceUpdate(NAMESPACE4);
-
-        String presetMapping = NAMESPACE1 + ":" + CALLING_PACKAGE1 + ","
-                + NAMESPACE2 + ":" + CALLING_PACKAGE2 + ","
-                + NAMESPACE4 + ":" + CALLING_PACKAGE3;
-        doReturn(presetMapping).when(() -> DeviceConfig.getString(
-                eq(RescueParty.NAMESPACE_CONFIGURATION),
-                eq(RescueParty.NAMESPACE_TO_PACKAGE_MAPPING_FLAG),
-                eq("")));
-
-        RescueParty.resetDeviceConfigForPackages(
-                Arrays.asList(new String[]{CALLING_PACKAGE1, CALLING_PACKAGE2}));
-        ArraySet<String> expectedNamespacesWiped = new ArraySet<String>(
-                Arrays.asList(new String[]{NAMESPACE1, NAMESPACE2, NAMESPACE3}));
-        assertEquals(mNamespacesWiped, expectedNamespacesWiped);
-    }
-
-    @Test
-    @DisableFlags(Flags.FLAG_DEPRECATE_FLAGS_AND_SETTINGS_RESETS)
-    public void testResetDeviceConfigNoExceptionWhenFlagMalformed() {
-        RescueParty.onSettingsProviderPublished(mMockContext);
-        verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
-                any(Executor.class),
-                mMonitorCallbackCaptor.capture()));
-
-        // Record DeviceConfig accesses
-        RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
-        DeviceConfig.MonitorCallback monitorCallback = mMonitorCallbackCaptor.getValue();
-        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE1);
-        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE2, NAMESPACE3);
-        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE3, NAMESPACE4);
-        // Fake DeviceConfig value changes
-        monitorCallback.onNamespaceUpdate(NAMESPACE1);
-        monitorCallback.onNamespaceUpdate(NAMESPACE2);
-        monitorCallback.onNamespaceUpdate(NAMESPACE3);
-        monitorCallback.onNamespaceUpdate(NAMESPACE4);
-
-        String invalidPresetMapping = NAMESPACE2 + ":" + CALLING_PACKAGE2 + ","
-                + NAMESPACE1 + "." + CALLING_PACKAGE2;
-        doReturn(invalidPresetMapping).when(() -> DeviceConfig.getString(
-                eq(RescueParty.NAMESPACE_CONFIGURATION),
-                eq(RescueParty.NAMESPACE_TO_PACKAGE_MAPPING_FLAG),
-                eq("")));
-
-        RescueParty.resetDeviceConfigForPackages(
-                Arrays.asList(new String[]{CALLING_PACKAGE1, CALLING_PACKAGE2}));
-        ArraySet<String> expectedNamespacesWiped = new ArraySet<String>(
-                Arrays.asList(new String[]{NAMESPACE1, NAMESPACE3}));
-        assertEquals(mNamespacesWiped, expectedNamespacesWiped);
-    }
 
     private void verifySettingsResets(int resetMode, String[] resetNamespaces,
             HashMap<String, Integer> configResetVerifiedTimesMap) {
@@ -858,13 +440,14 @@
     }
 
     private void noteBoot(int mitigationCount) {
-        RescuePartyObserver.getInstance(mMockContext).executeBootLoopMitigation(mitigationCount);
+        RescuePartyObserver.getInstance(mMockContext).onExecuteBootLoopMitigation(mitigationCount);
     }
 
     private void noteAppCrash(int mitigationCount, boolean isPersistent) {
         String packageName = isPersistent ? PERSISTENT_PACKAGE : NON_PERSISTENT_PACKAGE;
-        RescuePartyObserver.getInstance(mMockContext).execute(new VersionedPackage(
-                packageName, 1), PackageWatchdog.FAILURE_REASON_APP_CRASH, mitigationCount);
+        RescuePartyObserver.getInstance(mMockContext).onExecuteHealthCheckMitigation(
+                new VersionedPackage(packageName, 1), PackageWatchdog.FAILURE_REASON_APP_CRASH,
+                mitigationCount);
     }
 
     // Mock CrashRecoveryProperties as they cannot be accessed due to SEPolicy restrictions
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/crashrecovery/Android.bp b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp
index 7ac7aca..1f88c29 100644
--- a/services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp
+++ b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp
@@ -36,7 +36,10 @@
         "services.core",
         "truth",
         "flag-junit",
-    ],
+    ] + select(soong_config_variable("ANDROID", "release_crashrecovery_module"), {
+        "true": ["service-crashrecovery.impl"],
+        default: [],
+    }),
 
     libs: [
         "android.test.mock.stubs.system",
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/mockingservicestests/src/com/android/server/rollback/Android.bp b/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp
index 677ecf4..2f23e02 100644
--- a/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp
@@ -34,7 +34,10 @@
         "services.core",
         "truth",
         "flag-junit",
-    ],
+    ] + select(soong_config_variable("ANDROID", "release_crashrecovery_module"), {
+        "true": ["service-crashrecovery.impl"],
+        default: [],
+    }),
 
     libs: [
         "android.test.mock.stubs.system",
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java b/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
index e0c7bfe..347dc81 100644
--- a/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
@@ -109,14 +109,11 @@
     private static final String PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG =
             "persist.device_config.configuration.disable_high_impact_rollback";
 
-    private SystemConfig mSysConfig;
 
     @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
 
     @Before
     public void setup() {
-        mSysConfig = new SystemConfigTestClass();
-
         mSession = ExtendedMockito.mockitoSession()
                 .initMocks(this)
                 .strictness(Strictness.LENIENT)
@@ -184,7 +181,7 @@
     @Test
     public void testHealthCheckLevels() {
         RollbackPackageHealthObserver observer =
-                spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+                spy(new RollbackPackageHealthObserver(mMockContext));
         VersionedPackage testFailedPackage = new VersionedPackage(APP_A, VERSION_CODE);
         VersionedPackage secondFailedPackage = new VersionedPackage(APP_B, VERSION_CODE);
 
@@ -228,14 +225,14 @@
     @Test
     public void testIsPersistent() {
         RollbackPackageHealthObserver observer =
-                spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+                spy(new RollbackPackageHealthObserver(mMockContext));
         assertTrue(observer.isPersistent());
     }
 
     @Test
     public void testMayObservePackage_withoutAnyRollback() {
         RollbackPackageHealthObserver observer =
-                spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+                spy(new RollbackPackageHealthObserver(mMockContext));
         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
         when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of());
         assertFalse(observer.mayObservePackage(APP_A));
@@ -245,7 +242,7 @@
     public void testMayObservePackage_forPersistentApp()
             throws PackageManager.NameNotFoundException {
         RollbackPackageHealthObserver observer =
-                spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+                spy(new RollbackPackageHealthObserver(mMockContext));
         ApplicationInfo info = new ApplicationInfo();
         info.flags = ApplicationInfo.FLAG_PERSISTENT | ApplicationInfo.FLAG_SYSTEM;
         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
@@ -260,7 +257,7 @@
     public void testMayObservePackage_forNonPersistentApp()
             throws PackageManager.NameNotFoundException {
         RollbackPackageHealthObserver observer =
-                spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+                spy(new RollbackPackageHealthObserver(mMockContext));
         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
         when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(mRollbackInfo));
         when(mRollbackInfo.getPackages()).thenReturn(List.of(mPackageRollbackInfo));
@@ -286,7 +283,7 @@
                 false, null, 111,
                 PackageManager.ROLLBACK_USER_IMPACT_LOW);
         RollbackPackageHealthObserver observer =
-                spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+                spy(new RollbackPackageHealthObserver(mMockContext));
         VersionedPackage secondFailedPackage = new VersionedPackage(APP_B, VERSION_CODE);
 
         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
@@ -317,7 +314,7 @@
                 false, null, 111,
                 PackageManager.ROLLBACK_USER_IMPACT_HIGH);
         RollbackPackageHealthObserver observer =
-                spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+                spy(new RollbackPackageHealthObserver(mMockContext));
         VersionedPackage secondFailedPackage = new VersionedPackage(APP_B, VERSION_CODE);
 
         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
@@ -348,7 +345,7 @@
                 false, null, 111,
                 PackageManager.ROLLBACK_USER_IMPACT_ONLY_MANUAL);
         RollbackPackageHealthObserver observer =
-                spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+                spy(new RollbackPackageHealthObserver(mMockContext));
         VersionedPackage secondFailedPackage = new VersionedPackage(APP_B, VERSION_CODE);
 
         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
@@ -386,7 +383,7 @@
                 false, null, 222,
                 PackageManager.ROLLBACK_USER_IMPACT_HIGH);
         RollbackPackageHealthObserver observer =
-                spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+                spy(new RollbackPackageHealthObserver(mMockContext));
         VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE);
 
         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
@@ -419,7 +416,7 @@
                 PackageManager.ROLLBACK_USER_IMPACT_LOW);
         VersionedPackage secondFailedPackage = new VersionedPackage(APP_B, VERSION_CODE);
         RollbackPackageHealthObserver observer =
-                spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+                spy(new RollbackPackageHealthObserver(mMockContext));
 
         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
         // Make the rollbacks available
@@ -427,7 +424,7 @@
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
         when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
 
-        observer.execute(secondFailedPackage,
+        observer.onExecuteHealthCheckMitigation(secondFailedPackage,
                 PackageWatchdog.FAILURE_REASON_NATIVE_CRASH, 1);
         waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
 
@@ -461,7 +458,7 @@
                 false, null, 222,
                 PackageManager.ROLLBACK_USER_IMPACT_LOW);
         RollbackPackageHealthObserver observer =
-                spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+                spy(new RollbackPackageHealthObserver(mMockContext));
         ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
 
         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
@@ -471,7 +468,8 @@
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
         when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
 
-        observer.execute(appBFrom, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+        observer.onExecuteHealthCheckMitigation(appBFrom, PackageWatchdog.FAILURE_REASON_APP_CRASH,
+                1);
         waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
 
         verify(mRollbackManager).commitRollback(argument.capture(), any(), any());
@@ -506,7 +504,7 @@
                 PackageManager.ROLLBACK_USER_IMPACT_LOW);
         VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE);
         RollbackPackageHealthObserver observer =
-                spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+                spy(new RollbackPackageHealthObserver(mMockContext));
         ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
 
         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
@@ -516,7 +514,8 @@
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
         when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
 
-        observer.execute(failedPackage, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+        observer.onExecuteHealthCheckMitigation(failedPackage,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
         waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
 
         verify(mRollbackManager, times(2)).commitRollback(
@@ -552,7 +551,7 @@
                 PackageManager.ROLLBACK_USER_IMPACT_HIGH);
         VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE);
         RollbackPackageHealthObserver observer =
-                spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+                spy(new RollbackPackageHealthObserver(mMockContext));
         ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
 
         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
@@ -562,7 +561,8 @@
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
         when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
 
-        observer.execute(failedPackage, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+        observer.onExecuteHealthCheckMitigation(failedPackage,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
         waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
 
         verify(mRollbackManager, times(1)).commitRollback(
@@ -590,7 +590,7 @@
                 PackageManager.ROLLBACK_USER_IMPACT_HIGH);
         VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE);
         RollbackPackageHealthObserver observer =
-                spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+                spy(new RollbackPackageHealthObserver(mMockContext));
         ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
 
         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
@@ -599,7 +599,8 @@
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
         when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
 
-        observer.execute(failedPackage, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+        observer.onExecuteHealthCheckMitigation(failedPackage,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
         waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
 
         verify(mRollbackManager, never()).commitRollback(argument.capture(), any(), any());
@@ -621,7 +622,7 @@
                 false, null, 111,
                 PackageManager.ROLLBACK_USER_IMPACT_LOW);
         RollbackPackageHealthObserver observer =
-                spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+                spy(new RollbackPackageHealthObserver(mMockContext));
 
         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
         // Make the rollbacks available
@@ -646,7 +647,7 @@
                 false, null, 111,
                 PackageManager.ROLLBACK_USER_IMPACT_HIGH);
         RollbackPackageHealthObserver observer =
-                spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+                spy(new RollbackPackageHealthObserver(mMockContext));
 
         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
         // Make the rollbacks available
@@ -672,7 +673,7 @@
                 false, null, 111,
                 PackageManager.ROLLBACK_USER_IMPACT_HIGH);
         RollbackPackageHealthObserver observer =
-                spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+                spy(new RollbackPackageHealthObserver(mMockContext));
 
         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
         // Make the rollbacks available
@@ -701,7 +702,7 @@
                 false, null, 111,
                 PackageManager.ROLLBACK_USER_IMPACT_ONLY_MANUAL);
         RollbackPackageHealthObserver observer =
-                spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+                spy(new RollbackPackageHealthObserver(mMockContext));
 
         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
         // Make the rollbacks available
@@ -737,7 +738,7 @@
                 false, null, 222,
                 PackageManager.ROLLBACK_USER_IMPACT_HIGH);
         RollbackPackageHealthObserver observer =
-                spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+                spy(new RollbackPackageHealthObserver(mMockContext));
 
         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
         // Make the rollbacks available
@@ -776,7 +777,7 @@
                 false, null, 222,
                 PackageManager.ROLLBACK_USER_IMPACT_LOW);
         RollbackPackageHealthObserver observer =
-                spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+                spy(new RollbackPackageHealthObserver(mMockContext));
         ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
 
         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
@@ -786,7 +787,7 @@
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
         when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
 
-        observer.executeBootLoopMitigation(1);
+        observer.onExecuteBootLoopMitigation(1);
         waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
 
         verify(mRollbackManager, times(2)).commitRollback(
@@ -821,7 +822,7 @@
                 false, null, 222,
                 PackageManager.ROLLBACK_USER_IMPACT_HIGH);
         RollbackPackageHealthObserver observer =
-                spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+                spy(new RollbackPackageHealthObserver(mMockContext));
         ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
 
         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
@@ -831,7 +832,7 @@
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
         when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
 
-        observer.executeBootLoopMitigation(1);
+        observer.onExecuteBootLoopMitigation(1);
         waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
 
         verify(mRollbackManager, times(1)).commitRollback(
@@ -857,7 +858,7 @@
                 false, null, 111,
                 PackageManager.ROLLBACK_USER_IMPACT_HIGH);
         RollbackPackageHealthObserver observer =
-                spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+                spy(new RollbackPackageHealthObserver(mMockContext));
         ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
 
         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
@@ -866,7 +867,7 @@
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
         when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
 
-        observer.executeBootLoopMitigation(1);
+        observer.onExecuteBootLoopMitigation(1);
         waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
 
         verify(mRollbackManager, times(1)).commitRollback(
@@ -902,7 +903,7 @@
                 PackageManager.ROLLBACK_USER_IMPACT_ONLY_MANUAL);
         VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE);
         RollbackPackageHealthObserver observer =
-                spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+                spy(new RollbackPackageHealthObserver(mMockContext));
         ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
 
         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
@@ -912,7 +913,8 @@
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
         when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
 
-        observer.execute(failedPackage, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+        observer.onExecuteHealthCheckMitigation(failedPackage,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
         waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
 
         verify(mRollbackManager, times(1)).commitRollback(
@@ -938,7 +940,7 @@
                 PackageManager.ROLLBACK_USER_IMPACT_ONLY_MANUAL);
         VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE);
         RollbackPackageHealthObserver observer =
-                spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+                spy(new RollbackPackageHealthObserver(mMockContext));
         ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
 
         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
@@ -947,7 +949,8 @@
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
         when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
 
-        observer.execute(failedPackage, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+        observer.onExecuteHealthCheckMitigation(failedPackage,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
         waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
 
         verify(mRollbackManager, never()).commitRollback(argument.capture(), any(), any());
@@ -980,7 +983,7 @@
                 PackageManager.ROLLBACK_USER_IMPACT_HIGH);
         VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE);
         RollbackPackageHealthObserver observer =
-                spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+                spy(new RollbackPackageHealthObserver(mMockContext));
         ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
 
         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
@@ -990,7 +993,7 @@
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
         when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
 
-        observer.executeBootLoopMitigation(1);
+        observer.onExecuteBootLoopMitigation(1);
         waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
 
         verify(mRollbackManager, times(1)).commitRollback(
@@ -1026,7 +1029,7 @@
                 false, null, 111,
                 PackageManager.ROLLBACK_USER_IMPACT_HIGH);
         RollbackPackageHealthObserver observer =
-                spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+                spy(new RollbackPackageHealthObserver(mMockContext));
         ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
 
         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
@@ -1036,7 +1039,7 @@
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
         when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
 
-        observer.executeBootLoopMitigation(1);
+        observer.onExecuteBootLoopMitigation(1);
         waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
 
         verify(mRollbackManager, never()).commitRollback(
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 f02a389..d83dc11 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
@@ -67,6 +67,7 @@
 import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader;
 import com.android.internal.os.KernelSingleUidTimeReader;
 import com.android.internal.os.LongArrayMultiStateCounter;
+import com.android.internal.os.MonotonicClock;
 import com.android.internal.os.PowerProfile;
 import com.android.server.power.feature.flags.Flags;
 
@@ -120,6 +121,7 @@
             }});
 
     private final MockClock mMockClock = new MockClock();
+    private final MonotonicClock mMonotonicClock = new MonotonicClock(777666, mMockClock);
     private MockBatteryStatsImpl mBatteryStatsImpl;
     private Handler mHandler;
     private PowerStatsStore mPowerStatsStore;
@@ -160,7 +162,7 @@
         mPowerStatsStore = new PowerStatsStore(systemDir, mHandler);
         mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mPowerAttributor,
                 mPowerProfile, mBatteryStatsImpl.getCpuScalingPolicies(), mPowerStatsStore, 0,
-                mMockClock);
+                mMockClock, mMonotonicClock);
     }
 
     @Test
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java
index 813dd84..5d50e6c 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java
@@ -191,7 +191,7 @@
                 "cpu",
                 1650.0f,
                 9300.0f,
-                8400L
+                8300L
         );
         verify(statsLogger).buildStatsEvent(
                 1000L,
@@ -205,7 +205,7 @@
                 "cpu",
                 1650.0f,
                 9400.0f,
-                0L
+                8400L
         );
         verify(statsLogger).buildStatsEvent(
                 1000L,
@@ -502,17 +502,17 @@
                 .setPackageWithHighestDrain("myPackage0")
                 .setTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_FOREGROUND, 1000)
                 .setTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_BACKGROUND, 2000)
-                .setConsumedPower(
+                .addConsumedPower(
                         BatteryConsumer.POWER_COMPONENT_SCREEN, 300)
-                .setConsumedPower(
+                .addConsumedPower(
                         BatteryConsumer.POWER_COMPONENT_CPU, 400)
-                .setConsumedPower(
+                .addConsumedPower(
                         BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 450)
-                .setConsumedPower(
+                .addConsumedPower(
                         BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + 1, 500)
-                .setUsageDurationMillis(
+                .addUsageDurationMillis(
                         BatteryConsumer.POWER_COMPONENT_CPU, 600)
-                .setUsageDurationMillis(
+                .addUsageDurationMillis(
                         BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + 1, 800);
 
         final BatteryConsumer.Key keyFg = uidBuilder.getKey(BatteryConsumer.POWER_COMPONENT_CPU,
@@ -524,14 +524,14 @@
         final BatteryConsumer.Key keyCached = uidBuilder.getKey(BatteryConsumer.POWER_COMPONENT_CPU,
                 BatteryConsumer.PROCESS_STATE_CACHED);
 
-        uidBuilder.setConsumedPower(keyFg, 9100, BatteryConsumer.POWER_MODEL_POWER_PROFILE)
-                .setUsageDurationMillis(keyFg, 8100)
-                .setConsumedPower(keyBg, 9200, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION)
-                .setUsageDurationMillis(keyBg, 8200)
-                .setConsumedPower(keyFgs, 9300, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION)
-                .setUsageDurationMillis(keyFgs, 8300)
-                .setConsumedPower(keyCached, 9400, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION)
-                .setUsageDurationMillis(keyFgs, 8400);
+        uidBuilder.addConsumedPower(keyFg, 9100, BatteryConsumer.POWER_MODEL_POWER_PROFILE)
+                .addUsageDurationMillis(keyFg, 8100)
+                .addConsumedPower(keyBg, 9200, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION)
+                .addUsageDurationMillis(keyBg, 8200)
+                .addConsumedPower(keyFgs, 9300, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION)
+                .addUsageDurationMillis(keyFgs, 8300)
+                .addConsumedPower(keyCached, 9400, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION)
+                .addUsageDurationMillis(keyCached, 8400);
 
         final BatteryConsumer.Key keyCustomFg = uidBuilder.getKey(
                 BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID,
@@ -539,9 +539,9 @@
         final BatteryConsumer.Key keyCustomBg = uidBuilder.getKey(
                 BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID,
                 BatteryConsumer.PROCESS_STATE_BACKGROUND);
-        uidBuilder.setConsumedPower(
+        uidBuilder.addConsumedPower(
                 keyCustomFg, 100, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
-        uidBuilder.setConsumedPower(
+        uidBuilder.addConsumedPower(
                 keyCustomBg, 350, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
         builder.getOrCreateUidBatteryConsumerBuilder(UID_1)
@@ -549,36 +549,36 @@
                 .setTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_FOREGROUND, 1234);
 
         builder.getOrCreateUidBatteryConsumerBuilder(UID_2)
-                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN,
+                .addConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN,
                         766);
 
         builder.getOrCreateUidBatteryConsumerBuilder(UID_3);
 
         builder.getAggregateBatteryConsumerBuilder(AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
-                .setConsumedPower(30000)
-                .setConsumedPower(
+                .addConsumedPower(30000)
+                .addConsumedPower(
                         BatteryConsumer.POWER_COMPONENT_CPU, 20100,
                         BatteryConsumer.POWER_MODEL_POWER_PROFILE)
-                .setConsumedPower(
+                .addConsumedPower(
                         BatteryConsumer.POWER_COMPONENT_AUDIO, 0,
                         BatteryConsumer.POWER_MODEL_POWER_PROFILE) // Empty
-                .setConsumedPower(
+                .addConsumedPower(
                         BatteryConsumer.POWER_COMPONENT_CAMERA, 20150,
                         BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION)
-                .setConsumedPower(
+                .addConsumedPower(
                         BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 20200)
-                .setUsageDurationMillis(
+                .addUsageDurationMillis(
                         BatteryConsumer.POWER_COMPONENT_CPU, 20300)
-                .setUsageDurationMillis(
+                .addUsageDurationMillis(
                         BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 20400);
 
         // Not used; just to make sure extraneous data doesn't mess things up.
         builder.getAggregateBatteryConsumerBuilder(
                         BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
-                .setConsumedPower(
+                .addConsumedPower(
                         BatteryConsumer.POWER_COMPONENT_CPU, 10100,
                         BatteryConsumer.POWER_MODEL_POWER_PROFILE)
-                .setConsumedPower(
+                .addConsumedPower(
                         BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10200);
 
         return builder.build();
@@ -596,8 +596,8 @@
                             BatteryConsumer.PROCESS_STATE_FOREGROUND, 1 * 60 * 1000)
                     .setTimeInProcessStateMs(
                             BatteryConsumer.PROCESS_STATE_BACKGROUND, 2 * 60 * 1000)
-                    .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, 30)
-                    .setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 40);
+                    .addConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, 30)
+                    .addConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 40);
         }
 
         // Add a UID with much larger battery footprint
@@ -605,16 +605,16 @@
         builder.getOrCreateUidBatteryConsumerBuilder(largeConsumerUid)
                 .setTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_FOREGROUND, 10 * 60 * 1000)
                 .setTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_BACKGROUND, 20 * 60 * 1000)
-                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, 300)
-                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 400);
+                .addConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, 300)
+                .addConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 400);
 
         // Add a UID with much larger usage duration
         final int highUsageUid = 3002;
         builder.getOrCreateUidBatteryConsumerBuilder(highUsageUid)
                 .setTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_FOREGROUND, 60 * 60 * 1000)
                 .setTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_BACKGROUND, 120 * 60 * 1000)
-                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, 3)
-                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 4);
+                .addConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, 3)
+                .addConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 4);
 
         BatteryUsageStats batteryUsageStats = builder.build();
         final byte[] bytes = batteryUsageStats.getStatsProto();
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
index 8239e09..709f83b 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
@@ -47,6 +47,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.os.BatteryStatsHistoryIterator;
+import com.android.internal.os.MonotonicClock;
 import com.android.internal.os.PowerProfile;
 import com.android.server.power.stats.processor.MultiStatePowerAttributor;
 
@@ -80,6 +81,7 @@
                     .setAveragePower(PowerProfile.POWER_BATTERY_CAPACITY, 4000.0);
 
     private MockClock mMockClock = mStatsRule.getMockClock();
+    private MonotonicClock mMonotonicClock = new MonotonicClock(666777, mMockClock);
     private Context mContext;
 
     @Before
@@ -146,7 +148,8 @@
 
         BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
                 mock(PowerAttributor.class), mStatsRule.getPowerProfile(),
-                mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), 0, mMockClock);
+                mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), 0, mMockClock,
+                mMonotonicClock);
 
         final BatteryUsageStats batteryUsageStats =
                 provider.getBatteryUsageStats(batteryStats,
@@ -273,7 +276,8 @@
 
         BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
                 powerAttributor, mStatsRule.getPowerProfile(),
-                mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), 0, mMockClock);
+                mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), 0, mMockClock,
+                mMonotonicClock);
 
         return provider.getBatteryUsageStats(batteryStats, BatteryUsageStatsQuery.DEFAULT);
     }
@@ -303,7 +307,8 @@
 
         BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
                 mock(PowerAttributor.class), mStatsRule.getPowerProfile(),
-                mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), 0, mMockClock);
+                mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), 0, mMockClock,
+                mMonotonicClock);
 
         final BatteryUsageStats batteryUsageStats =
                 provider.getBatteryUsageStats(batteryStats,
@@ -396,7 +401,8 @@
 
         BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
                 mock(PowerAttributor.class), mStatsRule.getPowerProfile(),
-                mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), 0, mMockClock);
+                mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), 0, mMockClock,
+                mMonotonicClock);
 
         final BatteryUsageStats batteryUsageStats =
                 provider.getBatteryUsageStats(batteryStats,
@@ -487,7 +493,8 @@
 
         BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
                 mock(PowerAttributor.class), mStatsRule.getPowerProfile(),
-                mStatsRule.getCpuScalingPolicies(), powerStatsStore, 0, mMockClock);
+                mStatsRule.getCpuScalingPolicies(), powerStatsStore, 0, mMockClock,
+                mMonotonicClock);
 
         batteryStats.saveBatteryUsageStatsOnReset(provider, powerStatsStore,
                 /* accumulateBatteryUsageStats */ false);
@@ -590,7 +597,10 @@
 
     private void accumulateBatteryUsageStats(int accumulatedBatteryUsageStatsSpanSize,
             int expectedNumberOfUpdates) throws Throwable {
-        BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
+        BatteryStatsImpl batteryStats = spy(mStatsRule.getBatteryStats());
+        // Note - these two are in microseconds
+        when(batteryStats.computeBatteryTimeRemaining(anyLong())).thenReturn(111_000L);
+        when(batteryStats.computeChargeTimeRemaining(anyLong())).thenReturn(777_000L);
         batteryStats.forceRecordAllHistory();
 
         setTime(5 * MINUTE_IN_MS);
@@ -623,7 +633,7 @@
         BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
                 powerAttributor, mStatsRule.getPowerProfile(),
                 mStatsRule.getCpuScalingPolicies(), powerStatsStore,
-                accumulatedBatteryUsageStatsSpanSize, mMockClock);
+                accumulatedBatteryUsageStatsSpanSize, mMockClock, mMonotonicClock);
 
         provider.accumulateBatteryUsageStatsAsync(batteryStats, mStatsRule.getHandler());
 
@@ -677,9 +687,14 @@
 
         BatteryUsageStats stats = provider.getBatteryUsageStats(batteryStats,
                 new BatteryUsageStatsQuery.Builder().accumulated().build());
+
         assertThat(stats.getStatsStartTimestamp()).isEqualTo(5 * MINUTE_IN_MS);
         assertThat(stats.getStatsEndTimestamp()).isEqualTo(115 * MINUTE_IN_MS);
 
+        assertThat(stats.getBatteryTimeRemainingMs()).isEqualTo(111);
+        assertThat(stats.getChargeTimeRemainingMs()).isEqualTo(777);
+        assertThat(stats.getBatteryCapacity()).isEqualTo(4000);  // from PowerProfile
+
         // Total: 10 + 20 + 30 = 60
         assertThat(stats.getAggregateBatteryConsumer(
                         BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
@@ -729,7 +744,8 @@
 
         BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
                 mock(PowerAttributor.class), mStatsRule.getPowerProfile(),
-                mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), 0, mMockClock);
+                mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), 0, mMockClock,
+                mMonotonicClock);
 
         PowerStatsStore powerStatsStore = mock(PowerStatsStore.class);
         doAnswer(invocation -> {
@@ -796,7 +812,8 @@
 
         BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
                 mock(PowerAttributor.class), mStatsRule.getPowerProfile(),
-                mStatsRule.getCpuScalingPolicies(), powerStatsStore, 0, mMockClock);
+                mStatsRule.getCpuScalingPolicies(), powerStatsStore, 0, mMockClock,
+                mMonotonicClock);
 
         BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder()
                 .aggregateSnapshots(0, 3000)
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
index 1b6b8c4..9771da5 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
@@ -119,7 +119,7 @@
             BatteryStatsImpl.Uid mockUid = mock(BatteryStatsImpl.Uid.class);
             when(mockUid.getUid()).thenReturn(i);
             builder.getOrCreateUidBatteryConsumerBuilder(mockUid)
-                    .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, i * 100);
+                    .addConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, i * 100);
         }
 
         BatteryUsageStats outBatteryUsageStats = builder.build();
@@ -355,13 +355,13 @@
 
         if (includeUserBatteryConsumer) {
             builder.getOrCreateUserBatteryConsumerBuilder(USER_ID)
-                    .setConsumedPower(
+                    .addConsumedPower(
                             BatteryConsumer.POWER_COMPONENT_CPU, 10)
-                    .setConsumedPower(
+                    .addConsumedPower(
                             BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 20)
-                    .setUsageDurationMillis(
+                    .addUsageDurationMillis(
                             BatteryConsumer.POWER_COMPONENT_CPU, 30)
-                    .setUsageDurationMillis(
+                    .addUsageDurationMillis(
                             BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 40);
         }
         return builder;
@@ -422,15 +422,15 @@
                 .setTimeInProcessStateMs(PROCESS_STATE_BACKGROUND, timeInProcessStateBackground)
                 .setTimeInProcessStateMs(PROCESS_STATE_FOREGROUND_SERVICE,
                         timeInProcessStateForegroundService)
-                .setConsumedPower(
+                .addConsumedPower(
                         BatteryConsumer.POWER_COMPONENT_SCREEN, screenPower, screenPowerModel)
-                .setConsumedPower(
+                .addConsumedPower(
                         BatteryConsumer.POWER_COMPONENT_CPU, cpuPower, cpuPowerModel)
-                .setConsumedPower(
+                .addConsumedPower(
                         BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, customComponentPower)
-                .setUsageDurationMillis(
+                .addUsageDurationMillis(
                         BatteryConsumer.POWER_COMPONENT_CPU, cpuDuration)
-                .setUsageDurationMillis(
+                .addUsageDurationMillis(
                         BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, customComponentDuration);
         if (builder.isProcessStateDataNeeded()) {
             final BatteryConsumer.Key cpuFgKey = builder.isScreenStateDataNeeded()
@@ -461,21 +461,21 @@
                             BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID,
                             BatteryConsumer.PROCESS_STATE_BACKGROUND);
             uidBuilder
-                    .setConsumedPower(cpuFgKey, cpuPowerForeground,
+                    .addConsumedPower(cpuFgKey, cpuPowerForeground,
                             BatteryConsumer.POWER_MODEL_POWER_PROFILE)
-                    .setUsageDurationMillis(cpuFgKey, cpuDurationForeground)
-                    .setConsumedPower(cpuBgKey, cpuPowerBackground,
+                    .addUsageDurationMillis(cpuFgKey, cpuDurationForeground)
+                    .addConsumedPower(cpuBgKey, cpuPowerBackground,
                             BatteryConsumer.POWER_MODEL_POWER_PROFILE)
-                    .setUsageDurationMillis(cpuBgKey, cpuDurationBackground)
-                    .setConsumedPower(cpuFgsKey, cpuPowerFgs,
+                    .addUsageDurationMillis(cpuBgKey, cpuDurationBackground)
+                    .addConsumedPower(cpuFgsKey, cpuPowerFgs,
                             BatteryConsumer.POWER_MODEL_POWER_PROFILE)
-                    .setUsageDurationMillis(cpuFgsKey, cpuDurationFgs)
-                    .setConsumedPower(cachedKey, cpuPowerCached,
+                    .addUsageDurationMillis(cpuFgsKey, cpuDurationFgs)
+                    .addConsumedPower(cachedKey, cpuPowerCached,
                             BatteryConsumer.POWER_MODEL_POWER_PROFILE)
-                    .setUsageDurationMillis(cachedKey, cpuDurationCached)
-                    .setConsumedPower(customBgKey, customComponentPower,
+                    .addUsageDurationMillis(cachedKey, cpuDurationCached)
+                    .addConsumedPower(customBgKey, customComponentPower,
                             BatteryConsumer.POWER_MODEL_UNDEFINED)
-                    .setUsageDurationMillis(customBgKey, customComponentDuration);
+                    .addUsageDurationMillis(customBgKey, customComponentDuration);
         }
     }
 
@@ -486,15 +486,15 @@
             long cpuDurationChgScrOn, double cpuPowerChgScrOff, long cpuDurationChgScrOff) {
         final AggregateBatteryConsumer.Builder aggBuilder =
                 builder.getAggregateBatteryConsumerBuilder(scope)
-                        .setConsumedPower(consumedPower)
-                        .setConsumedPower(
+                        .addConsumedPower(consumedPower)
+                        .addConsumedPower(
                                 BatteryConsumer.POWER_COMPONENT_CPU, cpuPower)
-                        .setConsumedPower(
+                        .addConsumedPower(
                                 BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID,
                                 customComponentPower)
-                        .setUsageDurationMillis(
+                        .addUsageDurationMillis(
                                 BatteryConsumer.POWER_COMPONENT_CPU, cpuDuration)
-                        .setUsageDurationMillis(
+                        .addUsageDurationMillis(
                                 BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID,
                                 customComponentDuration);
         if (builder.isPowerStateDataNeeded() || builder.isScreenStateDataNeeded()) {
@@ -519,18 +519,18 @@
                     BatteryConsumer.SCREEN_STATE_OTHER,
                     BatteryConsumer.POWER_STATE_OTHER);
             aggBuilder
-                    .setConsumedPower(cpuBatScrOn, cpuPowerBatScrOn,
+                    .addConsumedPower(cpuBatScrOn, cpuPowerBatScrOn,
                             BatteryConsumer.POWER_MODEL_POWER_PROFILE)
-                    .setUsageDurationMillis(cpuBatScrOn, cpuDurationBatScrOn)
-                    .setConsumedPower(cpuBatScrOff, cpuPowerBatScrOff,
+                    .addUsageDurationMillis(cpuBatScrOn, cpuDurationBatScrOn)
+                    .addConsumedPower(cpuBatScrOff, cpuPowerBatScrOff,
                             BatteryConsumer.POWER_MODEL_POWER_PROFILE)
-                    .setUsageDurationMillis(cpuBatScrOff, cpuDurationBatScrOff)
-                    .setConsumedPower(cpuChgScrOn, cpuPowerChgScrOn,
+                    .addUsageDurationMillis(cpuBatScrOff, cpuDurationBatScrOff)
+                    .addConsumedPower(cpuChgScrOn, cpuPowerChgScrOn,
                             BatteryConsumer.POWER_MODEL_POWER_PROFILE)
-                    .setUsageDurationMillis(cpuChgScrOn, cpuDurationChgScrOn)
-                    .setConsumedPower(cpuChgScrOff, cpuPowerChgScrOff,
+                    .addUsageDurationMillis(cpuChgScrOn, cpuDurationChgScrOn)
+                    .addConsumedPower(cpuChgScrOff, cpuPowerChgScrOff,
                             BatteryConsumer.POWER_MODEL_POWER_PROFILE)
-                    .setUsageDurationMillis(cpuChgScrOff, cpuDurationChgScrOff);
+                    .addUsageDurationMillis(cpuChgScrOff, cpuDurationChgScrOff);
         }
     }
 
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 359755a..f165667 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -96,7 +96,10 @@
         "CtsVirtualDeviceCommonLib",
         "com_android_server_accessibility_flags_lib",
         "locksettings_flags_lib",
-    ],
+    ] + select(soong_config_variable("ANDROID", "release_crashrecovery_module"), {
+        "true": ["service-crashrecovery.impl"],
+        default: [],
+    }),
 
     libs: [
         "android.hardware.power-V1-java",
@@ -157,21 +160,49 @@
     resource_zips: [":FrameworksServicesTests_apks_as_resources"],
 }
 
-android_ravenwood_test {
-    name: "FrameworksServicesTestsRavenwood",
+java_defaults {
+    name: "FrameworksServicesTestsRavenwood-defaults",
     libs: [
         "android.test.mock.stubs.system",
     ],
     static_libs: [
         "androidx.annotation_annotation",
         "androidx.test.rules",
-        "services.core",
         "flag-junit",
     ],
+    auto_gen_config: true,
+}
+
+// Unit tests for UriGrantManager, running on ravenwood.
+// Note UriGrantManager does not support Ravenwood (yet). We're just running the original
+// unit tests as is on Ravenwood. So here, we use the original "services.core", because
+// "services.core.ravenwood" doesn't have the target code.
+// (Compare to FrameworksServicesTestsRavenwood_Compat, which does support Ravenwood.)
+android_ravenwood_test {
+    name: "FrameworksServicesTestsRavenwood_Uri",
+    defaults: ["FrameworksServicesTestsRavenwood-defaults"],
+    team: "trendy_team_ravenwood",
+    static_libs: [
+        "services.core",
+    ],
     srcs: [
         "src/com/android/server/uri/**/*.java",
     ],
-    auto_gen_config: true,
+}
+
+// Unit tests for compat-framework.
+// Compat-framework does support Ravenwood, and it uses the ravenwood anottations,
+// so we link "services.core.ravenwood".
+android_ravenwood_test {
+    name: "FrameworksServicesTestsRavenwood_Compat",
+    defaults: ["FrameworksServicesTestsRavenwood-defaults"],
+    team: "trendy_team_ravenwood",
+    static_libs: [
+        "services.core.ravenwood",
+    ],
+    srcs: [
+        "src/com/android/server/compat/**/*.java",
+    ],
 }
 
 java_library {
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/compat/CompatConfigTest.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
index 36b163e..3d695a6 100644
--- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
@@ -18,12 +18,12 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
-import static org.testng.Assert.assertThrows;
 
 import android.app.compat.ChangeIdStateCache;
 import android.app.compat.PackageOverride;
diff --git a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
index 1d07540..95d601f 100644
--- a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
@@ -18,6 +18,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyLong;
@@ -26,9 +27,9 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import static org.mockito.internal.verification.VerificationModeFactory.times;
-import static org.testng.Assert.assertThrows;
 
 import android.compat.Compatibility.ChangeConfig;
+import android.content.AttributionSource;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
@@ -39,6 +40,8 @@
 import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.SetFlagsRule;
+import android.os.PermissionEnforcer;
+import android.permission.PermissionCheckerManager;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -90,6 +93,22 @@
             .thenReturn(-1);
         when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
             .thenThrow(new PackageManager.NameNotFoundException());
+
+        var allGrantingPermissionEnforcer = new PermissionEnforcer() {
+            @Override
+            protected int checkPermission(String permission, AttributionSource source) {
+                return PermissionCheckerManager.PERMISSION_GRANTED;
+            }
+
+            @Override
+            protected int checkPermission(String permission, int pid, int uid) {
+                return PermissionCheckerManager.PERMISSION_GRANTED;
+            }
+        };
+
+        when(mContext.getSystemService(eq(Context.PERMISSION_ENFORCER_SERVICE)))
+                .thenReturn(allGrantingPermissionEnforcer);
+
         mCompatConfig = new CompatConfig(mBuildClassifier, mContext);
         mPlatformCompat =
                 new PlatformCompat(mContext, mCompatConfig, mBuildClassifier, mChangeReporter);
diff --git a/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java
index cfe3d84..2ed71ce 100644
--- a/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java
@@ -22,10 +22,12 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.AdditionalMatchers.aryEq;
 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.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.reset;
@@ -39,14 +41,18 @@
 import android.hardware.thermal.TemperatureThreshold;
 import android.hardware.thermal.ThrottlingSeverity;
 import android.os.CoolingDevice;
+import android.os.Flags;
 import android.os.IBinder;
 import android.os.IPowerManager;
 import android.os.IThermalEventListener;
+import android.os.IThermalHeadroomListener;
 import android.os.IThermalService;
 import android.os.IThermalStatusListener;
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.Temperature;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -56,6 +62,8 @@
 import com.android.server.power.ThermalManagerService.ThermalHalWrapper;
 
 import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -78,6 +86,11 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class ThermalManagerServiceTest {
+    @ClassRule
+    public static final SetFlagsRule.ClassRule mSetFlagsClassRule = new SetFlagsRule.ClassRule();
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = mSetFlagsClassRule.createSetFlagsRule();
+
     private static final long CALLBACK_TIMEOUT_MILLI_SEC = 5000;
     private ThermalManagerService mService;
     private ThermalHalFake mFakeHal;
@@ -89,6 +102,8 @@
     @Mock
     private IThermalService mIThermalServiceMock;
     @Mock
+    private IThermalHeadroomListener mHeadroomListener;
+    @Mock
     private IThermalEventListener mEventListener1;
     @Mock
     private IThermalEventListener mEventListener2;
@@ -102,22 +117,23 @@
      */
     private class ThermalHalFake extends ThermalHalWrapper {
         private static final int INIT_STATUS = Temperature.THROTTLING_NONE;
-        private ArrayList<Temperature> mTemperatureList = new ArrayList<>();
-        private ArrayList<CoolingDevice> mCoolingDeviceList = new ArrayList<>();
-        private ArrayList<TemperatureThreshold> mTemperatureThresholdList = initializeThresholds();
+        private List<Temperature> mTemperatureList = new ArrayList<>();
+        private List<Temperature> mOverrideTemperatures = null;
+        private List<CoolingDevice> mCoolingDeviceList = new ArrayList<>();
+        private List<TemperatureThreshold> mTemperatureThresholdList = initializeThresholds();
 
-        private Temperature mSkin1 = new Temperature(0, Temperature.TYPE_SKIN, "skin1",
+        private Temperature mSkin1 = new Temperature(28, Temperature.TYPE_SKIN, "skin1",
                 INIT_STATUS);
-        private Temperature mSkin2 = new Temperature(0, Temperature.TYPE_SKIN, "skin2",
+        private Temperature mSkin2 = new Temperature(31, Temperature.TYPE_SKIN, "skin2",
                 INIT_STATUS);
-        private Temperature mBattery = new Temperature(0, Temperature.TYPE_BATTERY, "batt",
+        private Temperature mBattery = new Temperature(34, Temperature.TYPE_BATTERY, "batt",
                 INIT_STATUS);
-        private Temperature mUsbPort = new Temperature(0, Temperature.TYPE_USB_PORT, "usbport",
+        private Temperature mUsbPort = new Temperature(37, Temperature.TYPE_USB_PORT, "usbport",
                 INIT_STATUS);
-        private CoolingDevice mCpu = new CoolingDevice(0, CoolingDevice.TYPE_BATTERY, "cpu");
-        private CoolingDevice mGpu = new CoolingDevice(0, CoolingDevice.TYPE_BATTERY, "gpu");
+        private CoolingDevice mCpu = new CoolingDevice(40, CoolingDevice.TYPE_BATTERY, "cpu");
+        private CoolingDevice mGpu = new CoolingDevice(43, CoolingDevice.TYPE_BATTERY, "gpu");
 
-        private ArrayList<TemperatureThreshold> initializeThresholds() {
+        private List<TemperatureThreshold> initializeThresholds() {
             ArrayList<TemperatureThreshold> thresholds = new ArrayList<>();
 
             TemperatureThreshold skinThreshold = new TemperatureThreshold();
@@ -157,6 +173,14 @@
             mCoolingDeviceList.add(mGpu);
         }
 
+        void setOverrideTemperatures(List<Temperature> temperatures) {
+            mOverrideTemperatures = temperatures;
+        }
+
+        void resetOverrideTemperatures() {
+            mOverrideTemperatures = null;
+        }
+
         @Override
         protected List<Temperature> getCurrentTemperatures(boolean shouldFilter, int type) {
             List<Temperature> ret = new ArrayList<>();
@@ -221,22 +245,36 @@
         when(mContext.getSystemService(PowerManager.class)).thenReturn(mPowerManager);
         resetListenerMock();
         mService = new ThermalManagerService(mContext, mFakeHal);
-        // Register callbacks before AMS ready and no callback sent
+        mService.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY);
+    }
+
+    private void resetListenerMock() {
+        reset(mEventListener1);
+        reset(mStatusListener1);
+        reset(mEventListener2);
+        reset(mStatusListener2);
+        reset(mHeadroomListener);
+        doReturn(mock(IBinder.class)).when(mEventListener1).asBinder();
+        doReturn(mock(IBinder.class)).when(mStatusListener1).asBinder();
+        doReturn(mock(IBinder.class)).when(mEventListener2).asBinder();
+        doReturn(mock(IBinder.class)).when(mStatusListener2).asBinder();
+        doReturn(mock(IBinder.class)).when(mHeadroomListener).asBinder();
+    }
+
+    @Test
+    public void testRegister() throws Exception {
+        mService = new ThermalManagerService(mContext, mFakeHal);
+        // Register callbacks before AMS ready and verify they are called after AMS is ready
         assertTrue(mService.mService.registerThermalEventListener(mEventListener1));
         assertTrue(mService.mService.registerThermalStatusListener(mStatusListener1));
         assertTrue(mService.mService.registerThermalEventListenerWithType(mEventListener2,
                 Temperature.TYPE_SKIN));
         assertTrue(mService.mService.registerThermalStatusListener(mStatusListener2));
-        verify(mEventListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
-                .times(0)).notifyThrottling(any(Temperature.class));
-        verify(mStatusListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
-                .times(1)).onStatusChange(Temperature.THROTTLING_NONE);
-        verify(mEventListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
-                .times(0)).notifyThrottling(any(Temperature.class));
-        verify(mStatusListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
-                .times(1)).onStatusChange(Temperature.THROTTLING_NONE);
+        Thread.sleep(CALLBACK_TIMEOUT_MILLI_SEC);
         resetListenerMock();
         mService.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY);
+        assertTrue(mService.mService.registerThermalHeadroomListener(mHeadroomListener));
+
         ArgumentCaptor<Temperature> captor = ArgumentCaptor.forClass(Temperature.class);
         verify(mEventListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
                 .times(4)).notifyThrottling(captor.capture());
@@ -251,31 +289,18 @@
                 captor.getAllValues());
         verify(mStatusListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
                 .times(0)).onStatusChange(Temperature.THROTTLING_NONE);
-    }
-
-    private void resetListenerMock() {
-        reset(mEventListener1);
-        reset(mStatusListener1);
-        reset(mEventListener2);
-        reset(mStatusListener2);
-        doReturn(mock(IBinder.class)).when(mEventListener1).asBinder();
-        doReturn(mock(IBinder.class)).when(mStatusListener1).asBinder();
-        doReturn(mock(IBinder.class)).when(mEventListener2).asBinder();
-        doReturn(mock(IBinder.class)).when(mStatusListener2).asBinder();
-    }
-
-    @Test
-    public void testRegister() throws RemoteException {
         resetListenerMock();
-        // Register callbacks and verify they are called
+
+        // Register callbacks after AMS ready and verify they are called
         assertTrue(mService.mService.registerThermalEventListener(mEventListener1));
         assertTrue(mService.mService.registerThermalStatusListener(mStatusListener1));
-        ArgumentCaptor<Temperature> captor = ArgumentCaptor.forClass(Temperature.class);
+        captor = ArgumentCaptor.forClass(Temperature.class);
         verify(mEventListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
                 .times(4)).notifyThrottling(captor.capture());
         assertListEqualsIgnoringOrder(mFakeHal.mTemperatureList, captor.getAllValues());
         verify(mStatusListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
                 .times(1)).onStatusChange(Temperature.THROTTLING_NONE);
+
         // Register new callbacks and verify old ones are not called (remained same) while new
         // ones are called
         assertTrue(mService.mService.registerThermalEventListenerWithType(mEventListener2,
@@ -296,7 +321,15 @@
     }
 
     @Test
-    public void testNotifyThrottling() throws RemoteException {
+    public void testNotifyThrottling() throws Exception {
+        assertTrue(mService.mService.registerThermalEventListener(mEventListener1));
+        assertTrue(mService.mService.registerThermalStatusListener(mStatusListener1));
+        assertTrue(mService.mService.registerThermalEventListenerWithType(mEventListener2,
+                Temperature.TYPE_SKIN));
+        assertTrue(mService.mService.registerThermalStatusListener(mStatusListener2));
+        Thread.sleep(CALLBACK_TIMEOUT_MILLI_SEC);
+        resetListenerMock();
+
         int status = Temperature.THROTTLING_SEVERE;
         // Should only notify event not status
         Temperature newBattery = new Temperature(50, Temperature.TYPE_BATTERY, "batt", status);
@@ -349,6 +382,57 @@
     }
 
     @Test
+    @EnableFlags({Flags.FLAG_ALLOW_THERMAL_THRESHOLDS_CALLBACK})
+    public void testNotifyThrottling_headroomCallback() throws Exception {
+        assertTrue(mService.mService.registerThermalHeadroomListener(mHeadroomListener));
+        Thread.sleep(CALLBACK_TIMEOUT_MILLI_SEC);
+        resetListenerMock();
+        int status = Temperature.THROTTLING_SEVERE;
+        mFakeHal.setOverrideTemperatures(new ArrayList<>());
+
+        // Should not notify on non-skin type
+        Temperature newBattery = new Temperature(37, Temperature.TYPE_BATTERY, "batt", status);
+        mFakeHal.mCallback.onTemperatureChanged(newBattery);
+        verify(mHeadroomListener, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+                .times(0)).onHeadroomChange(anyFloat(), anyFloat(), anyInt(), any());
+        resetListenerMock();
+
+        // Notify headroom on skin temperature change
+        Temperature newSkin = new Temperature(37, Temperature.TYPE_SKIN, "skin1", status);
+        mFakeHal.mCallback.onTemperatureChanged(newSkin);
+        verify(mHeadroomListener, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+                .times(1)).onHeadroomChange(eq(0.9f), anyFloat(), anyInt(),
+                eq(new float[]{Float.NaN, 0.6666667f, 0.8333333f, 1.0f, 1.1666666f, 1.3333334f,
+                        1.5f}));
+        resetListenerMock();
+
+        // Same or similar temperature should not trigger in a short period
+        mFakeHal.mCallback.onTemperatureChanged(newSkin);
+        newSkin = new Temperature(36.9f, Temperature.TYPE_SKIN, "skin1", status);
+        mFakeHal.mCallback.onTemperatureChanged(newSkin);
+        newSkin = new Temperature(37.1f, Temperature.TYPE_SKIN, "skin1", status);
+        mFakeHal.mCallback.onTemperatureChanged(newSkin);
+        verify(mHeadroomListener, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+                .times(0)).onHeadroomChange(anyFloat(), anyFloat(), anyInt(), any());
+        resetListenerMock();
+
+        // Significant temperature should trigger in a short period
+        newSkin = new Temperature(34f, Temperature.TYPE_SKIN, "skin1", status);
+        mFakeHal.mCallback.onTemperatureChanged(newSkin);
+        verify(mHeadroomListener, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+                .times(1)).onHeadroomChange(eq(0.8f), anyFloat(), anyInt(),
+                eq(new float[]{Float.NaN, 0.6666667f, 0.8333333f, 1.0f, 1.1666666f, 1.3333334f,
+                        1.5f}));
+        resetListenerMock();
+        newSkin = new Temperature(40f, Temperature.TYPE_SKIN, "skin1", status);
+        mFakeHal.mCallback.onTemperatureChanged(newSkin);
+        verify(mHeadroomListener, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+                .times(1)).onHeadroomChange(eq(1.0f), anyFloat(), anyInt(),
+                eq(new float[]{Float.NaN, 0.6666667f, 0.8333333f, 1.0f, 1.1666666f, 1.3333334f,
+                        1.5f}));
+    }
+
+    @Test
     public void testGetCurrentTemperatures() throws RemoteException {
         assertListEqualsIgnoringOrder(mFakeHal.getCurrentTemperatures(false, 0),
                 Arrays.asList(mService.mService.getCurrentTemperatures()));
@@ -388,13 +472,28 @@
         // Do no call onActivityManagerReady to skip connect HAL
         assertTrue(mService.mService.registerThermalEventListener(mEventListener1));
         assertTrue(mService.mService.registerThermalStatusListener(mStatusListener1));
-        assertTrue(mService.mService.unregisterThermalEventListener(mEventListener1));
-        assertTrue(mService.mService.unregisterThermalStatusListener(mStatusListener1));
+        assertTrue(mService.mService.registerThermalEventListenerWithType(mEventListener2,
+                Temperature.TYPE_SKIN));
+        assertFalse(mService.mService.registerThermalHeadroomListener(mHeadroomListener));
+        verify(mEventListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+                .times(0)).notifyThrottling(any(Temperature.class));
+        verify(mStatusListener1, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+                .times(1)).onStatusChange(Temperature.THROTTLING_NONE);
+        verify(mEventListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+                .times(0)).notifyThrottling(any(Temperature.class));
+        verify(mHeadroomListener, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+                .times(0)).onHeadroomChange(anyFloat(), anyFloat(), anyInt(), any());
+
         assertEquals(0, Arrays.asList(mService.mService.getCurrentTemperatures()).size());
         assertEquals(0, Arrays.asList(mService.mService.getCurrentTemperaturesWithType(
-                        Temperature.TYPE_SKIN)).size());
+                Temperature.TYPE_SKIN)).size());
         assertEquals(Temperature.THROTTLING_NONE, mService.mService.getCurrentThermalStatus());
         assertTrue(Float.isNaN(mService.mService.getThermalHeadroom(0)));
+
+        assertTrue(mService.mService.unregisterThermalEventListener(mEventListener1));
+        assertTrue(mService.mService.unregisterThermalEventListener(mEventListener2));
+        assertTrue(mService.mService.unregisterThermalStatusListener(mStatusListener1));
+        assertFalse(mService.mService.unregisterThermalHeadroomListener(mHeadroomListener));
     }
 
     @Test
@@ -419,35 +518,45 @@
     }
 
     @Test
-    public void testTemperatureWatcherUpdateSevereThresholds() {
+    @EnableFlags({Flags.FLAG_ALLOW_THERMAL_THRESHOLDS_CALLBACK,
+            Flags.FLAG_ALLOW_THERMAL_HEADROOM_THRESHOLDS})
+    public void testTemperatureWatcherUpdateSevereThresholds() throws Exception {
+        assertTrue(mService.mService.registerThermalHeadroomListener(mHeadroomListener));
+        verify(mHeadroomListener, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+                .times(1)).onHeadroomChange(eq(0.6f), eq(0.6f), anyInt(),
+                aryEq(new float[]{Float.NaN, 0.6666667f, 0.8333333f, 1.0f, 1.1666666f, 1.3333334f,
+                        1.5f}));
+        resetListenerMock();
         TemperatureWatcher watcher = mService.mTemperatureWatcher;
+        TemperatureThreshold newThreshold = new TemperatureThreshold();
+        newThreshold.name = "skin1";
+        newThreshold.type = Temperature.TYPE_SKIN;
+        // significant change in threshold (> 0.3C) should trigger a callback
+        newThreshold.hotThrottlingThresholds = new float[]{
+                Float.NaN, 43.0f, 46.0f, 49.0f, Float.NaN, Float.NaN, Float.NaN
+        };
+        mFakeHal.mCallback.onThresholdChanged(newThreshold);
         synchronized (watcher.mSamples) {
-            watcher.mSevereThresholds.erase();
-            watcher.getAndUpdateThresholds();
-            assertEquals(1, watcher.mSevereThresholds.size());
-            assertEquals("skin1", watcher.mSevereThresholds.keyAt(0));
             Float threshold = watcher.mSevereThresholds.get("skin1");
             assertNotNull(threshold);
-            assertEquals(40.0f, threshold, 0.0f);
+            assertEquals(49.0f, threshold, 0.0f);
             assertArrayEquals("Got" + Arrays.toString(watcher.mHeadroomThresholds),
-                    new float[]{Float.NaN, 0.6667f, 0.8333f, 1.0f, 1.166f, 1.3333f,
-                            1.5f},
-                    watcher.mHeadroomThresholds, 0.01f);
-
-            TemperatureThreshold newThreshold = new TemperatureThreshold();
-            newThreshold.name = "skin1";
-            newThreshold.hotThrottlingThresholds = new float[] {
-                    Float.NaN, 44.0f, 47.0f, 50.0f, Float.NaN, Float.NaN, Float.NaN
-            };
-            mFakeHal.mCallback.onThresholdChanged(newThreshold);
-            threshold = watcher.mSevereThresholds.get("skin1");
-            assertNotNull(threshold);
-            assertEquals(50.0f, threshold, 0.0f);
-            assertArrayEquals("Got" + Arrays.toString(watcher.mHeadroomThresholds),
-                    new float[]{Float.NaN, 0.8f, 0.9f, 1.0f, Float.NaN, Float.NaN,
-                            Float.NaN},
+                    new float[]{Float.NaN, 0.8f, 0.9f, 1.0f, Float.NaN, Float.NaN, Float.NaN},
                     watcher.mHeadroomThresholds, 0.01f);
         }
+        verify(mHeadroomListener, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+                .times(1)).onHeadroomChange(eq(0.3f), eq(0.3f), anyInt(),
+                aryEq(new float[]{Float.NaN, 0.8f, 0.9f, 1.0f, Float.NaN, Float.NaN, Float.NaN}));
+        resetListenerMock();
+
+        // same or similar threshold callback data within a second should not trigger callback
+        mFakeHal.mCallback.onThresholdChanged(newThreshold);
+        newThreshold.hotThrottlingThresholds = new float[]{
+                Float.NaN, 43.1f, 45.9f, 49.0f, Float.NaN, Float.NaN, Float.NaN
+        };
+        mFakeHal.mCallback.onThresholdChanged(newThreshold);
+        verify(mHeadroomListener, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
+                .times(0)).onHeadroomChange(anyFloat(), anyFloat(), anyInt(), any());
     }
 
     @Test
@@ -475,28 +584,34 @@
     }
 
     @Test
-    public void testGetThermalHeadroomThresholdsOnlyReadOnce() throws Exception {
+    public void testGetThermalHeadroomThresholds() throws Exception {
         float[] expected = new float[]{Float.NaN, 0.1f, 0.2f, 0.3f, 0.4f, Float.NaN, 0.6f};
         when(mIThermalServiceMock.getThermalHeadroomThresholds()).thenReturn(expected);
         Map<Integer, Float> thresholds1 = mPowerManager.getThermalHeadroomThresholds();
         verify(mIThermalServiceMock, times(1)).getThermalHeadroomThresholds();
+        checkHeadroomThresholds(expected, thresholds1);
+
+        reset(mIThermalServiceMock);
+        expected = new float[]{Float.NaN, 0.2f, 0.3f, 0.4f, 0.4f, Float.NaN, 0.6f};
+        when(mIThermalServiceMock.getThermalHeadroomThresholds()).thenReturn(expected);
+        Map<Integer, Float> thresholds2 = mPowerManager.getThermalHeadroomThresholds();
+        verify(mIThermalServiceMock, times(1)).getThermalHeadroomThresholds();
+        checkHeadroomThresholds(expected, thresholds2);
+    }
+
+    private void checkHeadroomThresholds(float[] expected, Map<Integer, Float> thresholds) {
         for (int status = PowerManager.THERMAL_STATUS_LIGHT;
                 status <= PowerManager.THERMAL_STATUS_SHUTDOWN; status++) {
             if (Float.isNaN(expected[status])) {
-                assertFalse(thresholds1.containsKey(status));
+                assertFalse(thresholds.containsKey(status));
             } else {
-                assertEquals(expected[status], thresholds1.get(status), 0.01f);
+                assertEquals(expected[status], thresholds.get(status), 0.01f);
             }
         }
-        reset(mIThermalServiceMock);
-        Map<Integer, Float> thresholds2 = mPowerManager.getThermalHeadroomThresholds();
-        verify(mIThermalServiceMock, times(0)).getThermalHeadroomThresholds();
-        assertNotSame(thresholds1, thresholds2);
-        assertEquals(thresholds1, thresholds2);
     }
 
     @Test
-    public void testGetThermalHeadroomThresholdsOnDefaultHalResult() throws Exception  {
+    public void testGetThermalHeadroomThresholdsOnDefaultHalResult() throws Exception {
         TemperatureWatcher watcher = mService.mTemperatureWatcher;
         ArrayList<TemperatureThreshold> thresholds = new ArrayList<>();
         mFakeHal.mTemperatureThresholdList = thresholds;
@@ -510,8 +625,8 @@
         TemperatureThreshold nanThresholds = new TemperatureThreshold();
         nanThresholds.name = "nan";
         nanThresholds.type = Temperature.TYPE_SKIN;
-        nanThresholds.hotThrottlingThresholds = new float[ThrottlingSeverity.SHUTDOWN  + 1];
-        nanThresholds.coldThrottlingThresholds = new float[ThrottlingSeverity.SHUTDOWN  + 1];
+        nanThresholds.hotThrottlingThresholds = new float[ThrottlingSeverity.SHUTDOWN + 1];
+        nanThresholds.coldThrottlingThresholds = new float[ThrottlingSeverity.SHUTDOWN + 1];
         Arrays.fill(nanThresholds.hotThrottlingThresholds, Float.NaN);
         Arrays.fill(nanThresholds.coldThrottlingThresholds, Float.NaN);
         thresholds.add(nanThresholds);
@@ -607,7 +722,13 @@
     }
 
     @Test
-    public void testDump() {
+    public void testDump() throws Exception {
+        assertTrue(mService.mService.registerThermalEventListener(mEventListener1));
+        assertTrue(mService.mService.registerThermalStatusListener(mStatusListener1));
+        assertTrue(mService.mService.registerThermalEventListenerWithType(mEventListener2,
+                Temperature.TYPE_SKIN));
+        assertTrue(mService.mService.registerThermalStatusListener(mStatusListener2));
+
         when(mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP))
                 .thenReturn(PackageManager.PERMISSION_GRANTED);
         final StringWriter out = new StringWriter();
@@ -628,22 +749,22 @@
         assertThat(dumpStr).contains("Thermal Status: 0");
         assertThat(dumpStr).contains(
                 "Cached temperatures:\n"
-                + "\tTemperature{mValue=0.0, mType=4, mName=usbport, mStatus=0}\n"
-                + "\tTemperature{mValue=0.0, mType=2, mName=batt, mStatus=0}\n"
-                + "\tTemperature{mValue=0.0, mType=3, mName=skin1, mStatus=0}\n"
-                + "\tTemperature{mValue=0.0, mType=3, mName=skin2, mStatus=0}"
+                        + "\tTemperature{mValue=37.0, mType=4, mName=usbport, mStatus=0}\n"
+                        + "\tTemperature{mValue=34.0, mType=2, mName=batt, mStatus=0}\n"
+                        + "\tTemperature{mValue=28.0, mType=3, mName=skin1, mStatus=0}\n"
+                        + "\tTemperature{mValue=31.0, mType=3, mName=skin2, mStatus=0}"
         );
         assertThat(dumpStr).contains("HAL Ready: true\n"
                 + "HAL connection:\n"
                 + "\tThermalHAL AIDL 1  connected: yes");
         assertThat(dumpStr).contains("Current temperatures from HAL:\n"
-                + "\tTemperature{mValue=0.0, mType=3, mName=skin1, mStatus=0}\n"
-                + "\tTemperature{mValue=0.0, mType=3, mName=skin2, mStatus=0}\n"
-                + "\tTemperature{mValue=0.0, mType=2, mName=batt, mStatus=0}\n"
-                + "\tTemperature{mValue=0.0, mType=4, mName=usbport, mStatus=0}\n");
+                + "\tTemperature{mValue=28.0, mType=3, mName=skin1, mStatus=0}\n"
+                + "\tTemperature{mValue=31.0, mType=3, mName=skin2, mStatus=0}\n"
+                + "\tTemperature{mValue=34.0, mType=2, mName=batt, mStatus=0}\n"
+                + "\tTemperature{mValue=37.0, mType=4, mName=usbport, mStatus=0}\n");
         assertThat(dumpStr).contains("Current cooling devices from HAL:\n"
-                + "\tCoolingDevice{mValue=0, mType=1, mName=cpu}\n"
-                + "\tCoolingDevice{mValue=0, mType=1, mName=gpu}\n");
+                + "\tCoolingDevice{mValue=40, mType=1, mName=cpu}\n"
+                + "\tCoolingDevice{mValue=43, mType=1, mName=gpu}\n");
         assertThat(dumpStr).contains("Temperature static thresholds from HAL:\n"
                 + "\tTemperatureThreshold{mType=3, mName=skin1, mHotThrottlingThresholds=[25.0, "
                 + "30.0, 35.0, 40.0, 45.0, 50.0, 55.0], mColdThrottlingThresholds=[0.0, 0.0, 0.0,"
diff --git a/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java
index 24abc18..f549453 100644
--- a/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java
@@ -61,7 +61,6 @@
 import android.os.UserHandle;
 import android.platform.test.flag.junit.FlagsParameterization;
 import android.platform.test.flag.junit.SetFlagsRule;
-import android.platform.test.ravenwood.RavenwoodRule;
 import android.util.ArraySet;
 
 import org.junit.Before;
@@ -77,9 +76,6 @@
 
 @RunWith(Parameterized.class)
 public class UriGrantsManagerServiceTest {
-    @Rule
-    public final RavenwoodRule mRavenwood = new RavenwoodRule();
-
     /**
      * Why this class needs to test all combinations of
      * {@link android.security.Flags#FLAG_CONTENT_URI_PERMISSION_APIS}:
diff --git a/services/tests/servicestests/src/com/android/server/uri/UriPermissionTest.java b/services/tests/servicestests/src/com/android/server/uri/UriPermissionTest.java
index 611c514..fe66f73 100644
--- a/services/tests/servicestests/src/com/android/server/uri/UriPermissionTest.java
+++ b/services/tests/servicestests/src/com/android/server/uri/UriPermissionTest.java
@@ -37,18 +37,13 @@
 import static org.junit.Assert.assertTrue;
 
 import android.os.SystemClock;
-import android.platform.test.ravenwood.RavenwoodRule;
 
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 public class UriPermissionTest {
-    @Rule
-    public final RavenwoodRule mRavenwood = new RavenwoodRule();
-
     @Mock
     private UriGrantsManagerInternal mService;
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
index 22a4f85..0b89c11 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
@@ -34,10 +34,12 @@
 import static android.app.NotificationManager.IMPORTANCE_LOW;
 import static android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION;
 import static android.service.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUPING;
+import static android.service.notification.Flags.FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION;
 import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
 import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
 
 import static com.android.server.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUP_CONVERSATIONS;
+import static com.android.server.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS;
 import static com.android.server.notification.GroupHelper.AGGREGATE_GROUP_KEY;
 import static com.android.server.notification.GroupHelper.AUTOGROUP_KEY;
 import static com.android.server.notification.GroupHelper.BASE_FLAGS;
@@ -2217,6 +2219,7 @@
 
     @Test
     @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+    @DisableFlags(FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION)
     public void testMoveAggregateGroups_updateChannel_multipleChannels() {
         final String pkg = "package";
         final String expectedGroupKey_alerting = GroupHelper.getFullAggregateGroupKey(pkg,
@@ -2265,16 +2268,17 @@
         mGroupHelper.onChannelUpdated(UserHandle.SYSTEM.getIdentifier(), pkg, channel1,
                 notificationList);
 
-        // Check that channel1's notifications are moved to the silent section group
-        // But not enough to auto-group => remove override group key
-        verify(mCallback, never()).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
-                anyString(), anyInt(), any());
-        verify(mCallback, never()).addAutoGroup(anyString(), anyString(), anyBoolean());
+        // Check that the override group key was cleared
         for (NotificationRecord record: notificationList) {
             if (record.getChannel().getId().equals(channel1.getId())) {
                 assertThat(record.getSbn().getOverrideGroupKey()).isNull();
             }
         }
+        // Check that channel1's notifications are moved to the silent section group
+        // and a group summary is created + notifications are added to the group
+        verify(mCallback, never()).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), anyString(),
+                anyInt(), any());
+        verify(mCallback, never()).addAutoGroup(anyString(), anyString(), anyBoolean());
 
         // Check that the alerting section group is not removed, only updated
         expectedSummaryAttr = new NotificationAttributes(BASE_FLAGS,
@@ -2287,6 +2291,357 @@
     }
 
     @Test
+    @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION})
+    public void testMoveAggregateGroups_updateChannel_multipleChannels_regroupOnClassifEnabled() {
+        final String pkg = "package";
+        final String expectedGroupKey_alerting = GroupHelper.getFullAggregateGroupKey(pkg,
+                AGGREGATE_GROUP_KEY + "AlertingSection", UserHandle.SYSTEM.getIdentifier());
+        int numNotificationChannel1 = 0;
+        final NotificationChannel channel1 = new NotificationChannel("TEST_CHANNEL_ID1",
+                "TEST_CHANNEL_ID1", IMPORTANCE_DEFAULT);
+        final NotificationChannel channel2 = new NotificationChannel("TEST_CHANNEL_ID2",
+                "TEST_CHANNEL_ID2", IMPORTANCE_DEFAULT);
+        final List<NotificationRecord> notificationList = new ArrayList<>();
+        final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+        // Post notifications with different channels that autogroup within the same section
+        NotificationRecord r;
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            if (i % 2 == 0) {
+                r = getNotificationRecord(pkg, i, String.valueOf(i),
+                        UserHandle.SYSTEM, "testGrp " + i, false, channel1);
+                numNotificationChannel1++;
+            } else {
+                r = getNotificationRecord(pkg, i, String.valueOf(i),
+                        UserHandle.SYSTEM, "testGrp " + i, false, channel2);
+            }
+            notificationList.add(r);
+            mGroupHelper.onNotificationPostedWithDelay(r, notificationList, summaryByGroup);
+        }
+        NotificationAttributes expectedSummaryAttr = new NotificationAttributes(BASE_FLAGS,
+                mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT,
+                "TEST_CHANNEL_ID1");
+        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+                eq(expectedGroupKey_alerting), anyInt(), eq(expectedSummaryAttr));
+        verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(),
+                eq(expectedGroupKey_alerting), eq(true));
+        verify(mCallback, never()).removeAutoGroup(anyString());
+        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+                any());
+        Mockito.reset(mCallback);
+
+        // Update channel1's importance
+        final String expectedGroupKey_silent = GroupHelper.getFullAggregateGroupKey(pkg,
+                AGGREGATE_GROUP_KEY + "SilentSection", UserHandle.SYSTEM.getIdentifier());
+        channel1.setImportance(IMPORTANCE_LOW);
+        for (NotificationRecord record: notificationList) {
+            if (record.getChannel().getId().equals(channel1.getId())) {
+                record.updateNotificationChannel(channel1);
+            }
+        }
+        mGroupHelper.onChannelUpdated(UserHandle.SYSTEM.getIdentifier(), pkg, channel1,
+                notificationList);
+
+        // Check that the override group key was cleared
+        for (NotificationRecord record: notificationList) {
+            if (record.getChannel().getId().equals(channel1.getId())) {
+                assertThat(record.getSbn().getOverrideGroupKey()).isNull();
+            }
+        }
+        // Check that channel1's notifications are moved to the silent section group
+        // and a group summary is created + notifications are added to the group
+        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+                eq(expectedGroupKey_silent), anyInt(), any());
+        verify(mCallback, times(numNotificationChannel1)).addAutoGroup(anyString(),
+                eq(expectedGroupKey_silent), anyBoolean());
+
+        // Check that the alerting section group is not removed, only updated
+        expectedSummaryAttr = new NotificationAttributes(BASE_FLAGS,
+                mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT,
+                "TEST_CHANNEL_ID2");
+        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), eq(pkg),
+                eq(expectedGroupKey_alerting));
+        verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), eq(pkg),
+                eq(expectedGroupKey_alerting), eq(expectedSummaryAttr));
+    }
+
+    @Test
+    @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION})
+    public void testMoveSections_notificationBundled() {
+        final List<NotificationRecord> notificationList = new ArrayList<>();
+        final String pkg = "package";
+        final int summaryId = 0;
+        final int numChildNotif = 4;
+
+        // Create an app-provided group: summary + child notifications
+        final NotificationChannel channel1 = new NotificationChannel("TEST_CHANNEL_ID1",
+                "TEST_CHANNEL_ID1", IMPORTANCE_DEFAULT);
+        NotificationRecord summary = getNotificationRecord(pkg, summaryId,
+                String.valueOf(summaryId), UserHandle.SYSTEM, "testGrp " + summaryId,
+                true, channel1);
+        notificationList.add(summary);
+        final String originalAppGroupKey = summary.getGroupKey();
+        for (int i = 0; i < numChildNotif; i++) {
+            NotificationRecord child = getNotificationRecord(pkg, i + 42, String.valueOf(i + 42),
+                    UserHandle.SYSTEM, "testGrp " + summaryId, false, channel1);
+            notificationList.add(child);
+        }
+
+        // Classify/bundle child notifications
+        final NotificationChannel socialChannel = new NotificationChannel(
+                NotificationChannel.SOCIAL_MEDIA_ID, NotificationChannel.SOCIAL_MEDIA_ID,
+                IMPORTANCE_DEFAULT);
+        final String expectedGroupKey_social = GroupHelper.getFullAggregateGroupKey(pkg,
+                AGGREGATE_GROUP_KEY + "SocialSection", UserHandle.SYSTEM.getIdentifier());
+        final NotificationAttributes expectedSummaryAttr_social = new NotificationAttributes(
+                BASE_FLAGS, mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT,
+                NotificationChannel.SOCIAL_MEDIA_ID);
+        final NotificationChannel newsChannel = new NotificationChannel(
+                NotificationChannel.NEWS_ID, NotificationChannel.NEWS_ID,
+                IMPORTANCE_DEFAULT);
+        final String expectedGroupKey_news = GroupHelper.getFullAggregateGroupKey(pkg,
+                AGGREGATE_GROUP_KEY + "NewsSection", UserHandle.SYSTEM.getIdentifier());
+        final NotificationAttributes expectedSummaryAttr_news = new NotificationAttributes(
+                BASE_FLAGS, mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT,
+                NotificationChannel.NEWS_ID);
+        for (NotificationRecord record: notificationList) {
+            if (record.getChannel().getId().equals(channel1.getId())
+                    && record.getSbn().getId() % 2 == 0) {
+                record.updateNotificationChannel(socialChannel);
+                mGroupHelper.onChannelUpdated(record);
+            }
+            if (record.getChannel().getId().equals(channel1.getId())
+                    && record.getSbn().getId() % 2 != 0) {
+                record.updateNotificationChannel(newsChannel);
+                mGroupHelper.onChannelUpdated(record);
+            }
+        }
+
+        // Check that 2 autogroup summaries were created for the news & social sections
+        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+                eq(expectedGroupKey_social), anyInt(), eq(expectedSummaryAttr_social));
+        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+                eq(expectedGroupKey_news), anyInt(), eq(expectedSummaryAttr_news));
+        // Check that half of the child notifications were grouped in each new section
+        verify(mCallback, times(numChildNotif / 2)).addAutoGroup(anyString(),
+                eq(expectedGroupKey_news), eq(true));
+        verify(mCallback, times(numChildNotif / 2)).addAutoGroup(anyString(),
+                eq(expectedGroupKey_social), eq(true));
+        verify(mCallback, never()).removeAutoGroup(anyString());
+        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+        verify(mCallback, times(numChildNotif / 2)).updateAutogroupSummary(anyInt(), anyString(),
+                anyString(), any());
+        verify(mCallback, times(numChildNotif)).removeAppProvidedSummaryOnClassification(
+                anyString(), eq(originalAppGroupKey));
+    }
+
+    @Test
+    @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION})
+    public void testCacheAndCancelAppSummary_notificationBundled() {
+        // check that the original app summary is canceled & cached on classification regrouping
+        final List<NotificationRecord> notificationList = new ArrayList<>();
+        final String pkg = "package";
+        final int summaryId = 0;
+        final int numChildNotif = 4;
+
+        // Create an app-provided group: summary + child notifications
+        final NotificationChannel channel1 = new NotificationChannel("TEST_CHANNEL_ID1",
+                "TEST_CHANNEL_ID1", IMPORTANCE_DEFAULT);
+        NotificationRecord summary = getNotificationRecord(pkg, summaryId,
+                String.valueOf(summaryId), UserHandle.SYSTEM, "testGrp " + summaryId,
+                true, channel1);
+        notificationList.add(summary);
+        final String originalAppGroupKey = summary.getGroupKey();
+        final String originalAppGroupName = summary.getNotification().getGroup();
+        for (int i = 0; i < numChildNotif; i++) {
+            NotificationRecord child = getNotificationRecord(pkg, i + 42, String.valueOf(i + 42),
+                    UserHandle.SYSTEM, "testGrp " + summaryId, false, channel1);
+            notificationList.add(child);
+        }
+
+        // Last regrouped notification will trigger summary cancellation in NMS
+        when(mCallback.removeAppProvidedSummaryOnClassification(anyString(),
+                eq(originalAppGroupKey))).thenReturn(summary);
+
+        // Classify/bundle child notifications
+        final NotificationChannel socialChannel = new NotificationChannel(
+                NotificationChannel.SOCIAL_MEDIA_ID, NotificationChannel.SOCIAL_MEDIA_ID,
+                IMPORTANCE_DEFAULT);
+        for (NotificationRecord record: notificationList) {
+            if (record.getChannel().getId().equals(channel1.getId())) {
+                record.updateNotificationChannel(socialChannel);
+                mGroupHelper.onChannelUpdated(record);
+            }
+        }
+
+        // Check that the original app summary was cached
+        CachedSummary cachedSummary = mGroupHelper.findCanceledSummary(pkg,
+                String.valueOf(summaryId), summaryId, UserHandle.SYSTEM.getIdentifier());
+        assertThat(cachedSummary.originalGroupKey()).isEqualTo(originalAppGroupName);
+        assertThat(cachedSummary.key()).isEqualTo(summary.getKey());
+
+        // App cancels the original summary
+        reset(mCallback);
+        mGroupHelper.maybeCancelGroupChildrenForCanceledSummary(pkg, String.valueOf(summaryId),
+                summaryId, UserHandle.SYSTEM.getIdentifier(), REASON_APP_CANCEL);
+        // Check that child notifications are removed and cache is cleared
+        verify(mCallback, times(1)).removeNotificationFromCanceledGroup(
+                eq(UserHandle.SYSTEM.getIdentifier()), eq(pkg), eq(originalAppGroupName),
+                eq(REASON_APP_CANCEL));
+        cachedSummary = mGroupHelper.findCanceledSummary(pkg, String.valueOf(summaryId), summaryId,
+                UserHandle.SYSTEM.getIdentifier());
+        assertThat(cachedSummary).isNull();
+    }
+
+    @Test
+    @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+            FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION,
+            FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS})
+    public void testSingletonGroupsRegrouped_notificationBundledBeforeDelayTimeout() {
+        // Check that singleton group notifications are regrouped if classification is done
+        // before onNotificationPostedWithDelay
+        final List<NotificationRecord> notificationList = new ArrayList<>();
+        final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+        final String pkg = "package";
+
+        // Post singleton groups, above forced group limit
+        for (int i = 0; i < AUTOGROUP_SINGLETONS_AT_COUNT; i++) {
+            NotificationRecord summary = getNotificationRecord(pkg, i,
+                    String.valueOf(i), UserHandle.SYSTEM, "testGrp " + i, true);
+            notificationList.add(summary);
+            NotificationRecord child = getNotificationRecord(pkg, i + 42, String.valueOf(i + 42),
+                    UserHandle.SYSTEM, "testGrp " + i, false);
+            notificationList.add(child);
+            summaryByGroup.put(summary.getGroupKey(), summary);
+        }
+
+        // Classify/bundle child notifications
+        final NotificationChannel socialChannel = new NotificationChannel(
+                NotificationChannel.SOCIAL_MEDIA_ID, NotificationChannel.SOCIAL_MEDIA_ID,
+                IMPORTANCE_DEFAULT);
+        final String expectedGroupKey_social = GroupHelper.getFullAggregateGroupKey(pkg,
+                AGGREGATE_GROUP_KEY + "SocialSection", UserHandle.SYSTEM.getIdentifier());
+        final NotificationAttributes expectedSummaryAttr_social = new NotificationAttributes(
+                BASE_FLAGS, mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT,
+                NotificationChannel.SOCIAL_MEDIA_ID);
+        for (NotificationRecord record: notificationList) {
+            if (record.getOriginalGroupKey().contains("testGrp")) {
+                record.updateNotificationChannel(socialChannel);
+                mGroupHelper.onChannelUpdated(record);
+            }
+        }
+
+        // Check that notifications are forced grouped and app-provided summaries are canceled
+        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+                eq(expectedGroupKey_social), anyInt(), eq(expectedSummaryAttr_social));
+        verify(mCallback, times(AUTOGROUP_SINGLETONS_AT_COUNT)).addAutoGroup(anyString(),
+                eq(expectedGroupKey_social), eq(true));
+        verify(mCallback, never()).removeAutoGroup(anyString());
+        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+        verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+                any());
+        verify(mCallback, times(2)).removeAppProvidedSummaryOnClassification(
+                anyString(), anyString());
+
+        // Adjust group key and cancel summaries
+        for (NotificationRecord record: notificationList) {
+            if (record.getNotification().isGroupSummary()) {
+                record.isCanceled = true;
+            } else {
+                record.setOverrideGroupKey(expectedGroupKey_social);
+            }
+        }
+
+        // Check that after onNotificationPostedWithDelay there is no change in the grouping
+        reset(mCallback);
+        for (NotificationRecord record: notificationList) {
+            mGroupHelper.onNotificationPostedWithDelay(record, notificationList, summaryByGroup);
+        }
+
+        verify(mCallback, never()).addAutoGroupSummary(anyInt(), anyString(), anyString(),
+                anyString(), anyInt(), any());
+        verify(mCallback, never()).addAutoGroup(anyString(), anyString(), anyBoolean());
+        verify(mCallback, never()).removeAutoGroup(anyString());
+        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+                any());
+    }
+
+    @Test
+    @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+            FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION,
+            FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS})
+    public void testSingletonGroupsRegrouped_notificationBundledAfterDelayTimeout() {
+        // Check that singleton group notifications are regrouped if classification is done
+        // after onNotificationPostedWithDelay
+        final List<NotificationRecord> notificationList = new ArrayList<>();
+        final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+        final String pkg = "package";
+        final String expectedGroupKey_alerting = GroupHelper.getFullAggregateGroupKey(pkg,
+                AGGREGATE_GROUP_KEY + "AlertingSection", UserHandle.SYSTEM.getIdentifier());
+        String expectedTriggeringKey = null;
+        // Post singleton groups, above forced group limit
+        for (int i = 0; i < AUTOGROUP_SINGLETONS_AT_COUNT; i++) {
+            NotificationRecord summary = getNotificationRecord(pkg, i,
+                    String.valueOf(i), UserHandle.SYSTEM, "testGrp " + i, true);
+            notificationList.add(summary);
+            NotificationRecord child = getNotificationRecord(pkg, i + 42,
+                    String.valueOf(i + 42), UserHandle.SYSTEM, "testGrp " + i, false);
+            notificationList.add(child);
+            expectedTriggeringKey = child.getKey();
+            summaryByGroup.put(summary.getGroupKey(), summary);
+            mGroupHelper.onNotificationPostedWithDelay(child, notificationList, summaryByGroup);
+            summary.isCanceled = true;  // simulate removing the app summary
+            mGroupHelper.onNotificationPostedWithDelay(summary, notificationList, summaryByGroup);
+        }
+
+        // Check that notifications are forced grouped and app-provided summaries are canceled
+        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg),
+                eq(expectedTriggeringKey), eq(expectedGroupKey_alerting), anyInt(),
+                eq(getNotificationAttributes(BASE_FLAGS)));
+        verify(mCallback, times(AUTOGROUP_SINGLETONS_AT_COUNT)).addAutoGroup(anyString(),
+                eq(expectedGroupKey_alerting), eq(true));
+        verify(mCallback, never()).removeAutoGroup(anyString());
+        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+                any());
+        verify(mCallback, times(AUTOGROUP_SINGLETONS_AT_COUNT)).removeAppProvidedSummary(
+                anyString());
+        assertThat(mGroupHelper.findCanceledSummary(pkg, String.valueOf(0), 0,
+                UserHandle.SYSTEM.getIdentifier())).isNotNull();
+        assertThat(mGroupHelper.findCanceledSummary(pkg, String.valueOf(1), 1,
+                UserHandle.SYSTEM.getIdentifier())).isNotNull();
+
+        // Classify/bundle child notifications
+        reset(mCallback);
+        final NotificationChannel socialChannel = new NotificationChannel(
+                NotificationChannel.SOCIAL_MEDIA_ID, NotificationChannel.SOCIAL_MEDIA_ID,
+                IMPORTANCE_DEFAULT);
+        final String expectedGroupKey_social = GroupHelper.getFullAggregateGroupKey(pkg,
+                AGGREGATE_GROUP_KEY + "SocialSection", UserHandle.SYSTEM.getIdentifier());
+        final NotificationAttributes expectedSummaryAttr_social = new NotificationAttributes(
+                BASE_FLAGS, mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT,
+                NotificationChannel.SOCIAL_MEDIA_ID);
+        for (NotificationRecord record: notificationList) {
+            if (record.getOriginalGroupKey().contains("testGrp")) {
+                record.updateNotificationChannel(socialChannel);
+                mGroupHelper.onChannelUpdated(record);
+            }
+        }
+
+        // Check that all notifications are moved to the social section group
+        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+                eq(expectedGroupKey_social), anyInt(), eq(expectedSummaryAttr_social));
+        verify(mCallback, times(AUTOGROUP_SINGLETONS_AT_COUNT)).addAutoGroup(anyString(),
+                eq(expectedGroupKey_social), eq(true));
+        // Check that the alerting section group is removed
+        verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), eq(pkg),
+                eq(expectedGroupKey_alerting));
+        verify(mCallback, times(AUTOGROUP_SINGLETONS_AT_COUNT)).updateAutogroupSummary(anyInt(),
+                anyString(), anyString(), any());
+    }
+
+    @Test
     @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
     public void testMoveAggregateGroups_updateChannel_groupsUngrouped() {
         final String pkg = "package";
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..6eb2f71 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
@@ -17,6 +17,9 @@
 
 import static android.os.UserHandle.USER_ALL;
 import static android.service.notification.Adjustment.KEY_IMPORTANCE;
+import static android.service.notification.Adjustment.TYPE_CONTENT_RECOMMENDATION;
+import static android.service.notification.Adjustment.TYPE_NEWS;
+import static android.service.notification.Adjustment.TYPE_PROMOTION;
 
 import static com.android.server.notification.NotificationManagerService.DEFAULT_ALLOWED_ADJUSTMENTS;
 
@@ -28,7 +31,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;
@@ -58,6 +60,8 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.IntArray;
+import android.util.Log;
+import android.util.Slog;
 import android.util.Xml;
 
 import androidx.test.runner.AndroidJUnit4;
@@ -198,8 +202,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 +437,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 +582,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 +605,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 +628,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);
@@ -690,4 +685,47 @@
         assertThat(mAssistants.getAllowedAssistantAdjustments())
                 .containsExactlyElementsIn(DEFAULT_ALLOWED_ADJUSTMENTS);
     }
+
+    @Test
+    @EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+    public void testSetAssistantAdjustmentKeyTypeState_allow() {
+        assertThat(mAssistants.getAllowedAdjustmentKeyTypes()).asList()
+                .containsExactly(TYPE_PROMOTION);
+
+        mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_CONTENT_RECOMMENDATION, true);
+
+        assertThat(mAssistants.getAllowedAdjustmentKeyTypes()).asList()
+                .containsExactlyElementsIn(List.of(TYPE_PROMOTION, TYPE_CONTENT_RECOMMENDATION));
+    }
+
+    @Test
+    @EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+    public void testSetAssistantAdjustmentKeyTypeState_disallow() {
+        mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_PROMOTION, false);
+        assertThat(mAssistants.getAllowedAdjustmentKeyTypes()).isEmpty();
+    }
+
+    @Test
+    @EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+    public void testDisallowAdjustmentKeyType_readWriteXml() throws Exception {
+        mAssistants.loadDefaultsFromConfig(true);
+        mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_PROMOTION, false);
+        mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_NEWS, true);
+        mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_CONTENT_RECOMMENDATION, true);
+
+        writeXmlAndReload(USER_ALL);
+
+        assertThat(mAssistants.getAllowedAdjustmentKeyTypes()).asList()
+                .containsExactlyElementsIn(List.of(TYPE_NEWS, TYPE_CONTENT_RECOMMENDATION));
+    }
+
+    @Test
+    public void testDefaultAllowedKeyAdjustments_readWriteXml() throws Exception {
+        mAssistants.loadDefaultsFromConfig(true);
+
+        writeXmlAndReload(USER_ALL);
+
+        assertThat(mAssistants.getAllowedAdjustmentKeyTypes()).asList()
+                .containsExactly(TYPE_PROMOTION);
+    }
 }
\ No newline at end of file
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..48308a4 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;
@@ -114,6 +113,7 @@
 import static android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION;
 import static android.service.notification.Flags.FLAG_NOTIFICATION_CONVERSATION_CHANNEL_MANAGEMENT;
 import static android.service.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUPING;
+import static android.service.notification.Flags.FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION;
 import static android.service.notification.Flags.FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS;
 import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
 import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS;
@@ -336,12 +336,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 +366,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;
@@ -2685,6 +2684,41 @@
     }
 
     @Test
+    @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION})
+    public void testAggregateGroups_RemoveAppSummary_onClassification() throws Exception {
+        final String originalGroupName = "originalGroup";
+        final int summaryId = 0;
+        final NotificationRecord r1 = generateNotificationRecord(mTestNotificationChannel,
+                summaryId + 1, originalGroupName, false);
+        mService.addNotification(r1);
+        final NotificationRecord r2 = generateNotificationRecord(mTestNotificationChannel,
+                summaryId + 2, originalGroupName, false);
+        mService.addNotification(r2);
+        final NotificationRecord summary = generateNotificationRecord(mTestNotificationChannel,
+                summaryId, originalGroupName, true);
+        mService.addNotification(summary);
+        final String originalGroupKey = summary.getGroupKey();
+        assertThat(mService.mSummaryByGroupKey).containsEntry(originalGroupKey, summary);
+
+        // Regroup first child notification
+        r1.setOverrideGroupKey("newGroup");
+        // Check that removeAppProvidedSummaryOnClassificationLocked is null
+        //  => there is still one child left in the original group
+        assertThat(mService.removeAppProvidedSummaryOnClassificationLocked(r1.getKey(),
+                originalGroupKey)).isNull();
+
+        // Regroup last child notification
+        r2.setOverrideGroupKey("newGroup");
+        // Check that removeAppProvidedSummaryOnClassificationLocked returns the original summary
+        //  and that the original app-provided summary is canceled
+        assertThat(mService.removeAppProvidedSummaryOnClassificationLocked(r2.getKey(),
+                originalGroupKey)).isEqualTo(summary);
+        waitForIdle();
+        verify(mWorkerHandler, times(1)).scheduleCancelNotification(any(), eq(summaryId));
+        assertThat(mService.mSummaryByGroupKey).doesNotContainKey(originalGroupKey);
+    }
+
+    @Test
     @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
     public void testUngroupingAggregateSummary() throws Exception {
         final String originalGroupName = "originalGroup";
@@ -7480,6 +7514,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 +14456,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 +14475,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 +14498,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(
@@ -17067,6 +17192,7 @@
         NotificationManagerService.WorkerHandler handler = mock(
                 NotificationManagerService.WorkerHandler.class);
         mService.setHandler(handler);
+        when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true);
 
         Bundle signals = new Bundle();
         signals.putInt(KEY_TYPE, TYPE_NEWS);
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/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 0019b3e..4b94e10 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -493,6 +493,22 @@
     }
 
     @Test
+    public void testZenOn_RepeatCallers_CallTypesBlocked() {
+        mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O});
+        // Any call allowed but no repeat callers
+        mZenModeHelper.mConsolidatedPolicy = new Policy(PRIORITY_CATEGORY_CALLS,
+                PRIORITY_SENDERS_ANY, 0, 0, 0);
+        mZenModeHelper.applyRestrictions();
+
+        verifyApplyRestrictions(true, true,
+                AudioAttributes.USAGE_NOTIFICATION_RINGTONE);
+        verifyApplyRestrictions(true, true,
+                AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST);
+    }
+
+
+    @Test
     public void testZenOn_StarredCallers_CallTypesBlocked() {
         mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O});
@@ -501,7 +517,7 @@
                 | PRIORITY_CATEGORY_MEDIA | PRIORITY_CATEGORY_MESSAGES
                 | PRIORITY_CATEGORY_CONVERSATIONS | PRIORITY_CATEGORY_CALLS
                 | PRIORITY_CATEGORY_ALARMS | PRIORITY_CATEGORY_EVENTS | PRIORITY_CATEGORY_REMINDERS
-                | PRIORITY_CATEGORY_SYSTEM,
+                | PRIORITY_CATEGORY_SYSTEM | PRIORITY_CATEGORY_REPEAT_CALLERS,
                 PRIORITY_SENDERS_STARRED,
                 PRIORITY_SENDERS_ANY, 0, CONVERSATION_SENDERS_ANYONE);
         mZenModeHelper.applyRestrictions();
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 f0b1c5c..1e9038e 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
@@ -397,7 +397,7 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_KEYBOARD_A11Y_SHORTCUT_CONTROL)
+    @EnableFlags(Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES)
     @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
     public void testToggleTalkbackPress() {
         testShortcutInternal("Meta + Alt + T -> Toggle talkback",
@@ -745,7 +745,7 @@
     }
 
     @Test
-    @EnableFlags(com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SHORTCUT_CONTROL)
+    @EnableFlags(com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES)
     public void testKeyGestureToggleTalkback() {
         Assert.assertTrue(
                 sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK));
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/telecomm/java/android/telecom/Log.java b/telecomm/java/android/telecom/Log.java
index a34094c..98949d0c 100644
--- a/telecomm/java/android/telecom/Log.java
+++ b/telecomm/java/android/telecom/Log.java
@@ -68,7 +68,7 @@
     // Used to synchronize singleton logging lazy initialization
     private static final Object sSingletonSync = new Object();
     private static EventManager sEventManager;
-    private static SessionManager sSessionManager;
+    private static volatile SessionManager sSessionManager;
     private static Object sLock = null;
 
     /**
@@ -379,6 +379,23 @@
         return sSessionManager;
     }
 
+    @VisibleForTesting
+    public static SessionManager setSessionManager(Context context,
+            java.lang.Runnable cleanSessionRunnable) {
+        // Checking for null again outside of synchronization because we only need to synchronize
+        // during the lazy loading of the session logger. We don't need to synchronize elsewhere.
+        if (sSessionManager == null) {
+            synchronized (sSingletonSync) {
+                if (sSessionManager == null) {
+                    sSessionManager = new SessionManager(cleanSessionRunnable);
+                    sSessionManager.setContext(context);
+                    return sSessionManager;
+                }
+            }
+        }
+        return sSessionManager;
+    }
+
     public static void setTag(String tag) {
         TAG = tag;
         DEBUG = isLoggable(android.util.Log.DEBUG);
diff --git a/telecomm/java/android/telecom/Logging/SessionManager.java b/telecomm/java/android/telecom/Logging/SessionManager.java
index 00e344c..ac1e69e 100644
--- a/telecomm/java/android/telecom/Logging/SessionManager.java
+++ b/telecomm/java/android/telecom/Logging/SessionManager.java
@@ -62,9 +62,7 @@
 
     @VisibleForTesting
     public final ConcurrentHashMap<Integer, Session> mSessionMapper = new ConcurrentHashMap<>(64);
-    @VisibleForTesting
-    public java.lang.Runnable mCleanStaleSessions = () ->
-            cleanupStaleSessions(getSessionCleanupTimeoutMs());
+    private final java.lang.Runnable mCleanStaleSessions;
     private final Handler mSessionCleanupHandler = new Handler(Looper.getMainLooper());
 
     // Overridden in LogTest to skip query to ContentProvider
@@ -110,29 +108,39 @@
     }
 
     public SessionManager() {
+        mCleanStaleSessions = () -> cleanupStaleSessions(getSessionCleanupTimeoutMs());
+    }
+
+    @VisibleForTesting
+    public SessionManager(java.lang.Runnable cleanStaleSessionsRunnable) {
+        mCleanStaleSessions = cleanStaleSessionsRunnable;
     }
 
     private long getSessionCleanupTimeoutMs() {
         return mSessionCleanupTimeoutMs.get();
     }
 
-    private synchronized void resetStaleSessionTimer() {
+    private void resetStaleSessionTimer() {
         if (!Flags.endSessionImprovements()) {
-            mSessionCleanupHandler.removeCallbacksAndMessages(null);
-            // Will be null in Log Testing
-            if (mCleanStaleSessions != null) {
-                mSessionCleanupHandler.postDelayed(mCleanStaleSessions,
-                        getSessionCleanupTimeoutMs());
-            }
-        } else {
-            if (mCleanStaleSessions != null
-                    && !mSessionCleanupHandler.hasCallbacks(mCleanStaleSessions)) {
+            resetStaleSessionTimerOld();
+            return;
+        }
+        // Will be null in Log Testing
+        if (mCleanStaleSessions == null) return;
+        synchronized (mSessionCleanupHandler) {
+            if (!mSessionCleanupHandler.hasCallbacks(mCleanStaleSessions)) {
                 mSessionCleanupHandler.postDelayed(mCleanStaleSessions,
                         getSessionCleanupTimeoutMs());
             }
         }
     }
 
+    private synchronized void resetStaleSessionTimerOld() {
+        if (mCleanStaleSessions == null) return;
+        mSessionCleanupHandler.removeCallbacksAndMessages(null);
+        mSessionCleanupHandler.postDelayed(mCleanStaleSessions, getSessionCleanupTimeoutMs());
+    }
+
     /**
      * Determines whether or not to start a new session or continue an existing session based on
      * the {@link Session.Info} info passed into startSession. If info is null, a new Session is
diff --git a/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java b/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java
index 30cc002..bfce3d2 100644
--- a/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java
+++ b/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java
@@ -168,32 +168,28 @@
 
         builder.getAggregateBatteryConsumerBuilder(
                 BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
-                .setConsumedPower(123)
-                .setConsumedPower(
-                        BatteryConsumer.POWER_COMPONENT_CPU, 10100)
-                .setConsumedPower(
-                        BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10200)
-                .setUsageDurationMillis(
-                        BatteryConsumer.POWER_COMPONENT_CPU, 10300)
-                .setUsageDurationMillis(
-                        BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10400);
+                .addConsumedPower(123)
+                .addConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 10100)
+                .addConsumedPower(BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10200)
+                .addUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CPU, 10300)
+                .addUsageDurationMillis(BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10400);
 
         for (int i = 0; i < 1000; i++) {
             final UidBatteryConsumer.Builder consumerBuilder =
                     builder.getOrCreateUidBatteryConsumerBuilder(i)
                             .setPackageWithHighestDrain("example.packagename" + i)
-                            .setTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND, i * 2000)
-                            .setTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND, i * 1000);
+                            .setTimeInProcessStateMs(UidBatteryConsumer.STATE_FOREGROUND, i * 2000)
+                            .setTimeInProcessStateMs(UidBatteryConsumer.STATE_BACKGROUND, i * 1000);
             for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
                     componentId++) {
-                consumerBuilder.setConsumedPower(componentId, componentId * 123.0,
+                consumerBuilder.addConsumedPower(componentId, componentId * 123.0,
                         BatteryConsumer.POWER_MODEL_POWER_PROFILE);
-                consumerBuilder.setUsageDurationMillis(componentId, componentId * 1000);
+                consumerBuilder.addUsageDurationMillis(componentId, componentId * 1000);
             }
 
             consumerBuilder
-                    .setConsumedPower(BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 1234)
-                    .setUsageDurationMillis(BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 4321);
+                    .addConsumedPower(BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 1234)
+                    .addUsageDurationMillis(BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 4321);
         }
         return builder.build();
     }
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 f2d3229..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)
diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
index 0b147d6..1574d1b 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,34 +1250,112 @@
                     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) {
-        var handleEvents = mutableListOf<KeyGestureEvent>()
+    class TouchpadTestData(
+        val name: String,
+        val touchpadGestureType: Int,
+        val expectedKeyGestureType: Int,
+        val expectedAction: Int,
+        val expectedAppLaunchData: AppLaunchData? = null,
+    ) {
+        override fun toString(): String = name
+    }
+
+    @Keep
+    private fun customTouchpadGesturesTestArguments(): Array<TouchpadTestData> {
+        return arrayOf(
+            TouchpadTestData(
+                "3 Finger Tap -> Go Home",
+                InputGestureData.TOUCHPAD_GESTURE_TYPE_THREE_FINGER_TAP,
+                KeyGestureEvent.KEY_GESTURE_TYPE_HOME,
+                KeyGestureEvent.ACTION_GESTURE_COMPLETE
+            ),
+            TouchpadTestData(
+                "3 Finger Tap -> Launch app",
+                InputGestureData.TOUCHPAD_GESTURE_TYPE_THREE_FINGER_TAP,
+                KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+                KeyGestureEvent.ACTION_GESTURE_COMPLETE,
+                AppLaunchData.createLaunchDataForComponent("com.test", "com.test.BookmarkTest")
+            ),
+        )
+    }
+
+    @Test
+    @Parameters(method = "customTouchpadGesturesTestArguments")
+    fun testCustomTouchpadGesture(test: TouchpadTestData) {
+        setupKeyGestureController()
+        val builder = InputGestureData.Builder()
+            .setKeyGestureType(test.expectedKeyGestureType)
+            .setTrigger(InputGestureData.createTouchpadTrigger(test.touchpadGestureType))
+        if (test.expectedAppLaunchData != null) {
+            builder.setAppLaunchData(test.expectedAppLaunchData)
+        }
+        val inputGestureData = builder.build()
+
+        keyGestureController.addCustomInputGesture(0, inputGestureData.aidlData)
+
+        val handledEvents = mutableListOf<KeyGestureEvent>()
         val handler = KeyGestureHandler { event, _ ->
-            handleEvents.add(KeyGestureEvent(event))
+            handledEvents.add(KeyGestureEvent(event))
             true
         }
         keyGestureController.registerKeyGestureHandler(handler, 0)
-        handleEvents.clear()
+        handledEvents.clear()
 
-        sendKeys(keyGestureController, test.keys)
+        keyGestureController.handleTouchpadGesture(test.touchpadGestureType)
+
+        assertEquals(
+            "Test: $test doesn't produce correct number of key gesture events",
+            1,
+            handledEvents.size
+        )
+        val event = handledEvents[0]
+        assertEquals(
+            "Test: $test doesn't produce correct key gesture type",
+            test.expectedKeyGestureType,
+            event.keyGestureType
+        )
+        assertEquals(
+            "Test: $test doesn't produce correct key gesture action",
+            test.expectedAction,
+            event.action
+        )
+        assertEquals(
+            "Test: $test doesn't produce correct app launch data",
+            test.expectedAppLaunchData,
+            event.appLaunchData
+        )
+
+        keyGestureController.unregisterKeyGestureHandler(handler, 0)
+    }
+
+    private fun testKeyGestureInternal(test: TestData) {
+        val handledEvents = mutableListOf<KeyGestureEvent>()
+        val handler = KeyGestureHandler { event, _ ->
+            handledEvents.add(KeyGestureEvent(event))
+            true
+        }
+        keyGestureController.registerKeyGestureHandler(handler, 0)
+        handledEvents.clear()
+
+        sendKeys(test.keys)
 
         assertEquals(
             "Test: $test doesn't produce correct number of key gesture events",
             test.expectedActions.size,
-            handleEvents.size
+            handledEvents.size
         )
-        for (i in handleEvents.indices) {
-            val event = handleEvents[i]
+        for (i in handledEvents.indices) {
+            val event = handledEvents[i]
             assertArrayEquals(
                 "Test: $test doesn't produce correct key gesture keycodes",
                 test.expectedKeys,
@@ -1162,28 +1386,20 @@
         keyGestureController.unregisterKeyGestureHandler(handler, 0)
     }
 
-    private fun testKeyGestureNotProduced(
-        keyGestureController: KeyGestureController,
-        testName: String,
-        testKeys: IntArray
-    ) {
-        var handleEvents = mutableListOf<KeyGestureEvent>()
+    private fun testKeyGestureNotProduced(testName: String, testKeys: IntArray) {
+        var handledEvents = mutableListOf<KeyGestureEvent>()
         val handler = KeyGestureHandler { event, _ ->
-            handleEvents.add(KeyGestureEvent(event))
+            handledEvents.add(KeyGestureEvent(event))
             true
         }
         keyGestureController.registerKeyGestureHandler(handler, 0)
-        handleEvents.clear()
+        handledEvents.clear()
 
-        sendKeys(keyGestureController, testKeys)
-        assertEquals("Test: $testName should not produce Key gesture", 0, handleEvents.size)
+        sendKeys(testKeys)
+        assertEquals("Test: $testName should not produce Key gesture", 0, handledEvents.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 +1408,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 +1421,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 +1429,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/PackageWatchdog/Android.bp b/tests/PackageWatchdog/Android.bp
index 096555e..91483eb 100644
--- a/tests/PackageWatchdog/Android.bp
+++ b/tests/PackageWatchdog/Android.bp
@@ -35,7 +35,13 @@
         "services.core",
         "services.net",
         "truth",
-    ],
+    ] + select(soong_config_variable("ANDROID", "release_crashrecovery_module"), {
+        "true": [
+            "service-crashrecovery.impl",
+            "framework-crashrecovery.impl",
+        ],
+        default: [],
+    }),
     libs: ["android.test.runner.stubs.system"],
     jni_libs: [
         // mockito-target-extended dependencies
diff --git a/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java b/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java
index 8d143b6..05a0f8f 100644
--- a/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java
@@ -224,39 +224,39 @@
         PackageWatchdog watchdog = createWatchdog();
         RescuePartyObserver rescuePartyObserver = setUpRescuePartyObserver(watchdog);
 
-        verify(rescuePartyObserver, never()).executeBootLoopMitigation(1);
+        verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(1);
 
         for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
             watchdog.noteBoot();
         }
 
-        verify(rescuePartyObserver).executeBootLoopMitigation(1);
-        verify(rescuePartyObserver, never()).executeBootLoopMitigation(2);
+        verify(rescuePartyObserver).onExecuteBootLoopMitigation(1);
+        verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2);
 
         watchdog.noteBoot();
 
-        verify(rescuePartyObserver).executeBootLoopMitigation(2);
-        verify(rescuePartyObserver, never()).executeBootLoopMitigation(3);
+        verify(rescuePartyObserver).onExecuteBootLoopMitigation(2);
+        verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(3);
 
         watchdog.noteBoot();
 
-        verify(rescuePartyObserver).executeBootLoopMitigation(3);
-        verify(rescuePartyObserver, never()).executeBootLoopMitigation(4);
+        verify(rescuePartyObserver).onExecuteBootLoopMitigation(3);
+        verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(4);
 
         watchdog.noteBoot();
 
-        verify(rescuePartyObserver).executeBootLoopMitigation(4);
-        verify(rescuePartyObserver, never()).executeBootLoopMitigation(5);
+        verify(rescuePartyObserver).onExecuteBootLoopMitigation(4);
+        verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(5);
 
         watchdog.noteBoot();
 
-        verify(rescuePartyObserver).executeBootLoopMitigation(5);
-        verify(rescuePartyObserver, never()).executeBootLoopMitigation(6);
+        verify(rescuePartyObserver).onExecuteBootLoopMitigation(5);
+        verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(6);
 
         watchdog.noteBoot();
 
-        verify(rescuePartyObserver).executeBootLoopMitigation(6);
-        verify(rescuePartyObserver, never()).executeBootLoopMitigation(7);
+        verify(rescuePartyObserver).onExecuteBootLoopMitigation(6);
+        verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(7);
     }
 
     @Test
@@ -265,14 +265,14 @@
         RollbackPackageHealthObserver rollbackObserver =
                 setUpRollbackPackageHealthObserver(watchdog);
 
-        verify(rollbackObserver, never()).executeBootLoopMitigation(1);
+        verify(rollbackObserver, never()).onExecuteBootLoopMitigation(1);
 
         for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
             watchdog.noteBoot();
         }
 
-        verify(rollbackObserver).executeBootLoopMitigation(1);
-        verify(rollbackObserver, never()).executeBootLoopMitigation(2);
+        verify(rollbackObserver).onExecuteBootLoopMitigation(1);
+        verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
 
         // Update the list of available rollbacks after executing bootloop mitigation once
         when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_HIGH,
@@ -280,15 +280,15 @@
 
         watchdog.noteBoot();
 
-        verify(rollbackObserver).executeBootLoopMitigation(2);
-        verify(rollbackObserver, never()).executeBootLoopMitigation(3);
+        verify(rollbackObserver).onExecuteBootLoopMitigation(2);
+        verify(rollbackObserver, never()).onExecuteBootLoopMitigation(3);
 
         // Update the list of available rollbacks after executing bootloop mitigation once
         when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_MANUAL));
 
         watchdog.noteBoot();
 
-        verify(rollbackObserver, never()).executeBootLoopMitigation(3);
+        verify(rollbackObserver, never()).onExecuteBootLoopMitigation(3);
     }
 
     @Test
@@ -299,61 +299,61 @@
         RollbackPackageHealthObserver rollbackObserver =
                 setUpRollbackPackageHealthObserver(watchdog);
 
-        verify(rescuePartyObserver, never()).executeBootLoopMitigation(1);
-        verify(rollbackObserver, never()).executeBootLoopMitigation(1);
+        verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(1);
+        verify(rollbackObserver, never()).onExecuteBootLoopMitigation(1);
         for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
             watchdog.noteBoot();
         }
-        verify(rescuePartyObserver).executeBootLoopMitigation(1);
-        verify(rescuePartyObserver, never()).executeBootLoopMitigation(2);
-        verify(rollbackObserver, never()).executeBootLoopMitigation(1);
+        verify(rescuePartyObserver).onExecuteBootLoopMitigation(1);
+        verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2);
+        verify(rollbackObserver, never()).onExecuteBootLoopMitigation(1);
 
         watchdog.noteBoot();
 
-        verify(rescuePartyObserver).executeBootLoopMitigation(2);
-        verify(rescuePartyObserver, never()).executeBootLoopMitigation(3);
-        verify(rollbackObserver, never()).executeBootLoopMitigation(2);
+        verify(rescuePartyObserver).onExecuteBootLoopMitigation(2);
+        verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(3);
+        verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
 
         watchdog.noteBoot();
 
-        verify(rescuePartyObserver, never()).executeBootLoopMitigation(3);
-        verify(rollbackObserver).executeBootLoopMitigation(1);
-        verify(rollbackObserver, never()).executeBootLoopMitigation(2);
+        verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(3);
+        verify(rollbackObserver).onExecuteBootLoopMitigation(1);
+        verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
         // Update the list of available rollbacks after executing bootloop mitigation once
         when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_HIGH,
                 ROLLBACK_INFO_MANUAL));
 
         watchdog.noteBoot();
 
-        verify(rescuePartyObserver).executeBootLoopMitigation(3);
-        verify(rescuePartyObserver, never()).executeBootLoopMitigation(4);
-        verify(rollbackObserver, never()).executeBootLoopMitigation(2);
+        verify(rescuePartyObserver).onExecuteBootLoopMitigation(3);
+        verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(4);
+        verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
 
         watchdog.noteBoot();
 
-        verify(rescuePartyObserver).executeBootLoopMitigation(4);
-        verify(rescuePartyObserver, never()).executeBootLoopMitigation(5);
-        verify(rollbackObserver, never()).executeBootLoopMitigation(2);
+        verify(rescuePartyObserver).onExecuteBootLoopMitigation(4);
+        verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(5);
+        verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
 
         watchdog.noteBoot();
 
-        verify(rescuePartyObserver).executeBootLoopMitigation(5);
-        verify(rescuePartyObserver, never()).executeBootLoopMitigation(6);
-        verify(rollbackObserver, never()).executeBootLoopMitigation(2);
+        verify(rescuePartyObserver).onExecuteBootLoopMitigation(5);
+        verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(6);
+        verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
 
         watchdog.noteBoot();
 
-        verify(rescuePartyObserver, never()).executeBootLoopMitigation(6);
-        verify(rollbackObserver).executeBootLoopMitigation(2);
-        verify(rollbackObserver, never()).executeBootLoopMitigation(3);
+        verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(6);
+        verify(rollbackObserver).onExecuteBootLoopMitigation(2);
+        verify(rollbackObserver, never()).onExecuteBootLoopMitigation(3);
         // Update the list of available rollbacks after executing bootloop mitigation
         when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_MANUAL));
 
         watchdog.noteBoot();
 
-        verify(rescuePartyObserver).executeBootLoopMitigation(6);
-        verify(rescuePartyObserver, never()).executeBootLoopMitigation(7);
-        verify(rollbackObserver, never()).executeBootLoopMitigation(3);
+        verify(rescuePartyObserver).onExecuteBootLoopMitigation(6);
+        verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(7);
+        verify(rollbackObserver, never()).onExecuteBootLoopMitigation(3);
 
         moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_DEESCALATION_WINDOW_MS + 1);
         Mockito.reset(rescuePartyObserver);
@@ -361,8 +361,8 @@
         for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
             watchdog.noteBoot();
         }
-        verify(rescuePartyObserver).executeBootLoopMitigation(1);
-        verify(rescuePartyObserver, never()).executeBootLoopMitigation(2);
+        verify(rescuePartyObserver).onExecuteBootLoopMitigation(1);
+        verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2);
     }
 
     @Test
@@ -373,37 +373,37 @@
         RollbackPackageHealthObserver rollbackObserver =
                 setUpRollbackPackageHealthObserver(watchdog);
 
-        verify(rescuePartyObserver, never()).executeBootLoopMitigation(1);
-        verify(rollbackObserver, never()).executeBootLoopMitigation(1);
+        verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(1);
+        verify(rollbackObserver, never()).onExecuteBootLoopMitigation(1);
         for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
             watchdog.noteBoot();
         }
-        verify(rescuePartyObserver).executeBootLoopMitigation(1);
-        verify(rescuePartyObserver, never()).executeBootLoopMitigation(2);
-        verify(rollbackObserver, never()).executeBootLoopMitigation(1);
+        verify(rescuePartyObserver).onExecuteBootLoopMitigation(1);
+        verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2);
+        verify(rollbackObserver, never()).onExecuteBootLoopMitigation(1);
 
         watchdog.noteBoot();
 
-        verify(rescuePartyObserver, never()).executeBootLoopMitigation(2);
-        verify(rollbackObserver).executeBootLoopMitigation(1);
-        verify(rollbackObserver, never()).executeBootLoopMitigation(2);
+        verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2);
+        verify(rollbackObserver).onExecuteBootLoopMitigation(1);
+        verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
         // Update the list of available rollbacks after executing bootloop mitigation once
         when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_HIGH,
                 ROLLBACK_INFO_MANUAL));
 
         watchdog.noteBoot();
 
-        verify(rescuePartyObserver, never()).executeBootLoopMitigation(2);
-        verify(rollbackObserver).executeBootLoopMitigation(2);
-        verify(rollbackObserver, never()).executeBootLoopMitigation(3);
+        verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2);
+        verify(rollbackObserver).onExecuteBootLoopMitigation(2);
+        verify(rollbackObserver, never()).onExecuteBootLoopMitigation(3);
         // Update the list of available rollbacks after executing bootloop mitigation
         when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_MANUAL));
 
         watchdog.noteBoot();
 
-        verify(rescuePartyObserver).executeBootLoopMitigation(2);
-        verify(rescuePartyObserver, never()).executeBootLoopMitigation(3);
-        verify(rollbackObserver, never()).executeBootLoopMitigation(3);
+        verify(rescuePartyObserver).onExecuteBootLoopMitigation(2);
+        verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(3);
+        verify(rollbackObserver, never()).onExecuteBootLoopMitigation(3);
 
         moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_DEESCALATION_WINDOW_MS + 1);
         Mockito.reset(rescuePartyObserver);
@@ -411,8 +411,8 @@
         for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
             watchdog.noteBoot();
         }
-        verify(rescuePartyObserver).executeBootLoopMitigation(1);
-        verify(rescuePartyObserver, never()).executeBootLoopMitigation(2);
+        verify(rescuePartyObserver).onExecuteBootLoopMitigation(1);
+        verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2);
     }
 
     @Test
@@ -435,46 +435,46 @@
                 Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH);
 
         // Mitigation: SCOPED_DEVICE_CONFIG_RESET
-        verify(rescuePartyObserver).execute(versionedPackageA,
+        verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageA,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
-        verify(rescuePartyObserver, never()).execute(versionedPackageA,
+        verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
-        verify(rollbackObserver, never()).execute(versionedPackageA,
+        verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
 
         raiseFatalFailureAndDispatch(watchdog,
                 Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH);
 
         // Mitigation: ALL_DEVICE_CONFIG_RESET
-        verify(rescuePartyObserver).execute(versionedPackageA,
+        verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageA,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
-        verify(rescuePartyObserver, never()).execute(versionedPackageA,
+        verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 3);
-        verify(rollbackObserver, never()).execute(versionedPackageA,
+        verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
 
         raiseFatalFailureAndDispatch(watchdog,
                 Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH);
 
         // Mitigation: WARM_REBOOT
-        verify(rescuePartyObserver).execute(versionedPackageA,
+        verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageA,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 3);
-        verify(rescuePartyObserver, never()).execute(versionedPackageA,
+        verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 4);
-        verify(rollbackObserver, never()).execute(versionedPackageA,
+        verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
 
         raiseFatalFailureAndDispatch(watchdog,
                 Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH);
 
         // Mitigation: Low impact rollback
-        verify(rollbackObserver).execute(versionedPackageA,
+        verify(rollbackObserver).onExecuteHealthCheckMitigation(versionedPackageA,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
-        verify(rescuePartyObserver, never()).execute(versionedPackageA,
+        verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 4);
 
         // update available rollbacks to mock rollbacks being applied after the call to
-        // rollbackObserver.execute
+        // rollbackObserver.onExecuteHealthCheckMitigation
         when(mRollbackManager.getAvailableRollbacks()).thenReturn(
                 List.of(ROLLBACK_INFO_HIGH, ROLLBACK_INFO_MANUAL));
 
@@ -482,9 +482,9 @@
                 Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH);
 
         // DEFAULT_MAJOR_USER_IMPACT_LEVEL_THRESHOLD reached. No more mitigations applied
-        verify(rescuePartyObserver, never()).execute(versionedPackageA,
+        verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 4);
-        verify(rollbackObserver, never()).execute(versionedPackageA,
+        verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
     }
 
@@ -510,24 +510,24 @@
                 Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH);
 
         // Mitigation: WARM_REBOOT
-        verify(rescuePartyObserver).execute(versionedPackageA,
+        verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageA,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
-        verify(rescuePartyObserver, never()).execute(versionedPackageA,
+        verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
-        verify(rollbackObserver, never()).execute(versionedPackageA,
+        verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
 
         raiseFatalFailureAndDispatch(watchdog,
                 Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH);
 
         // Mitigation: Low impact rollback
-        verify(rollbackObserver).execute(versionedPackageA,
+        verify(rollbackObserver).onExecuteHealthCheckMitigation(versionedPackageA,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
-        verify(rescuePartyObserver, never()).execute(versionedPackageA,
+        verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
 
         // update available rollbacks to mock rollbacks being applied after the call to
-        // rollbackObserver.execute
+        // rollbackObserver.onExecuteHealthCheckMitigation
         when(mRollbackManager.getAvailableRollbacks()).thenReturn(
                 List.of(ROLLBACK_INFO_HIGH, ROLLBACK_INFO_MANUAL));
 
@@ -535,9 +535,9 @@
                 Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH);
 
         // DEFAULT_MAJOR_USER_IMPACT_LEVEL_THRESHOLD reached. No more mitigations applied
-        verify(rescuePartyObserver, never()).execute(versionedPackageA,
+        verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
-        verify(rollbackObserver, never()).execute(versionedPackageA,
+        verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
     }
 
@@ -567,48 +567,48 @@
                 Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
 
         // Mitigation: SCOPED_DEVICE_CONFIG_RESET
-        verify(rescuePartyObserver).execute(versionedPackageUi,
+        verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageUi,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
-        verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+        verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
-        verify(rollbackObserver, never()).execute(versionedPackageUi,
+        verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
 
         raiseFatalFailureAndDispatch(watchdog,
                 Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
 
         // Mitigation: ALL_DEVICE_CONFIG_RESET
-        verify(rescuePartyObserver).execute(versionedPackageUi,
+        verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageUi,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
-        verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+        verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 3);
-        verify(rollbackObserver, never()).execute(versionedPackageUi,
+        verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
 
         raiseFatalFailureAndDispatch(watchdog,
                 Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
 
         // Mitigation: WARM_REBOOT
-        verify(rescuePartyObserver).execute(versionedPackageUi,
+        verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageUi,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 3);
-        verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+        verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 4);
-        verify(rollbackObserver, never()).execute(versionedPackageUi,
+        verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
 
         raiseFatalFailureAndDispatch(watchdog,
                 Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
 
         // Mitigation: Low impact rollback
-        verify(rollbackObserver).execute(versionedPackageUi,
+        verify(rollbackObserver).onExecuteHealthCheckMitigation(versionedPackageUi,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
-        verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+        verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 4);
-        verify(rollbackObserver, never()).execute(versionedPackageUi,
+        verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
 
         // update available rollbacks to mock rollbacks being applied after the call to
-        // rollbackObserver.execute
+        // rollbackObserver.onExecuteHealthCheckMitigation
         when(mRollbackManager.getAvailableRollbacks()).thenReturn(
                 List.of(ROLLBACK_INFO_HIGH, ROLLBACK_INFO_MANUAL));
 
@@ -616,44 +616,44 @@
                 Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
 
         // Mitigation: RESET_SETTINGS_UNTRUSTED_DEFAULTS
-        verify(rescuePartyObserver).execute(versionedPackageUi,
+        verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageUi,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 4);
-        verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+        verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 5);
-        verify(rollbackObserver, never()).execute(versionedPackageUi,
+        verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
 
         raiseFatalFailureAndDispatch(watchdog,
                 Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
 
         // Mitigation: RESET_SETTINGS_UNTRUSTED_CHANGES
-        verify(rescuePartyObserver).execute(versionedPackageUi,
+        verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageUi,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 5);
-        verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+        verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 6);
-        verify(rollbackObserver, never()).execute(versionedPackageUi,
+        verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
 
         raiseFatalFailureAndDispatch(watchdog,
                 Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
 
         // Mitigation: RESET_SETTINGS_TRUSTED_DEFAULTS
-        verify(rescuePartyObserver).execute(versionedPackageUi,
+        verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageUi,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 6);
-        verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+        verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 7);
-        verify(rollbackObserver, never()).execute(versionedPackageUi,
+        verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
 
         raiseFatalFailureAndDispatch(watchdog,
                 Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
 
         // Mitigation: Factory reset. High impact rollbacks are performed only for boot loops.
-        verify(rescuePartyObserver).execute(versionedPackageUi,
+        verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageUi,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 7);
-        verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+        verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 8);
-        verify(rollbackObserver, never()).execute(versionedPackageUi,
+        verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
     }
 
@@ -685,26 +685,26 @@
                 Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
 
         // Mitigation: WARM_REBOOT
-        verify(rescuePartyObserver).execute(versionedPackageUi,
+        verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageUi,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
-        verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+        verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
-        verify(rollbackObserver, never()).execute(versionedPackageUi,
+        verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
 
         raiseFatalFailureAndDispatch(watchdog,
                 Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
 
         // Mitigation: Low impact rollback
-        verify(rollbackObserver).execute(versionedPackageUi,
+        verify(rollbackObserver).onExecuteHealthCheckMitigation(versionedPackageUi,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
-        verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+        verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
-        verify(rollbackObserver, never()).execute(versionedPackageUi,
+        verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
 
         // update available rollbacks to mock rollbacks being applied after the call to
-        // rollbackObserver.execute
+        // rollbackObserver.onExecuteHealthCheckMitigation
         when(mRollbackManager.getAvailableRollbacks()).thenReturn(
                 List.of(ROLLBACK_INFO_HIGH, ROLLBACK_INFO_MANUAL));
 
@@ -712,17 +712,17 @@
                 Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
 
         // Mitigation: Factory reset. High impact rollbacks are performed only for boot loops.
-        verify(rescuePartyObserver).execute(versionedPackageUi,
+        verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageUi,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
-        verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+        verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 3);
-        verify(rollbackObserver, never()).execute(versionedPackageUi,
+        verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
     }
 
     RollbackPackageHealthObserver setUpRollbackPackageHealthObserver(PackageWatchdog watchdog) {
         RollbackPackageHealthObserver rollbackObserver =
-                spy(new RollbackPackageHealthObserver(mSpyContext, mApexManager));
+                spy(new RollbackPackageHealthObserver(mSpyContext));
         when(mSpyContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
         when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_LOW,
                 ROLLBACK_INFO_HIGH, ROLLBACK_INFO_MANUAL));
@@ -785,7 +785,7 @@
         Handler handler = new Handler(mTestLooper.getLooper());
         PackageWatchdog watchdog =
                 new PackageWatchdog(mSpyContext, policyFile, handler, handler, controller,
-                        mConnectivityModuleConnector, mTestClock);
+                        mTestClock);
         mockCrashRecoveryProperties(watchdog);
 
         // Verify controller is not automatically started
diff --git a/tests/PackageWatchdog/src/com/android/server/ExplicitHealthCheckServiceTest.java b/tests/PackageWatchdog/src/com/android/server/ExplicitHealthCheckServiceTest.java
index 2fbfeba..cd2ab86 100644
--- a/tests/PackageWatchdog/src/com/android/server/ExplicitHealthCheckServiceTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/ExplicitHealthCheckServiceTest.java
@@ -35,6 +35,8 @@
 
     private ExplicitHealthCheckService mExplicitHealthCheckService;
     private static final String PACKAGE_NAME = "com.test.package";
+    private static final String EXTRA_HEALTH_CHECK_PASSED_PACKAGE =
+            "android.service.watchdog.extra.health_check_passed_package";
 
     @Before
     public void setup() throws Exception {
@@ -50,7 +52,7 @@
         IBinder binder = mExplicitHealthCheckService.onBind(new Intent());
         CountDownLatch countDownLatch = new CountDownLatch(1);
         RemoteCallback callback = new RemoteCallback(result -> {
-            assertThat(result.get(ExplicitHealthCheckService.EXTRA_HEALTH_CHECK_PASSED_PACKAGE))
+            assertThat(result.get(EXTRA_HEALTH_CHECK_PASSED_PACKAGE))
                     .isEqualTo(PACKAGE_NAME);
             countDownLatch.countDown();
         });
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index 0364781a..a540a8d 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -1754,7 +1754,7 @@
         Handler handler = new Handler(mTestLooper.getLooper());
         PackageWatchdog watchdog =
                 new PackageWatchdog(mSpyContext, policyFile, handler, handler, controller,
-                        mConnectivityModuleConnector, mTestClock);
+                         mTestClock);
         mockCrashRecoveryProperties(watchdog);
 
         // Verify controller is not automatically started
@@ -1869,8 +1869,8 @@
             return mImpact;
         }
 
-        public boolean execute(VersionedPackage versionedPackage, int failureReason,
-                int mitigationCount) {
+        public boolean onExecuteHealthCheckMitigation(VersionedPackage versionedPackage,
+                int failureReason, int mitigationCount) {
             mMitigatedPackages.add(versionedPackage.getPackageName());
             mMitigationCounts.add(mitigationCount);
             mLastFailureReason = failureReason;
@@ -1893,7 +1893,7 @@
             return mImpact;
         }
 
-        public boolean executeBootLoopMitigation(int level) {
+        public boolean onExecuteBootLoopMitigation(int level) {
             mMitigatedBootLoop = true;
             mBootMitigationCounts.add(level);
             return true;
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() {